Callbacks and eventsΒΆ

LocalSolver allows you to react to specific events during the search by calling your own functions/procedures. It can be used for example to control when to stop the search or to display some specific information during the search.

Note

This functionality requires using LocalSolver through its APIs. It is not available in the LSP language.

When adding a callback function to LocalSolver (with the addCallback() function) you can specify to which event your function must react, thanks to the LSCallbackType enumeration:

  • PhaseEnded: your function will be called at the end of each search phase.
  • PhaseStarted: your function will be called at the beginning of each search phase.
  • Display: your function will be called periodically, after a display on the console or in the log file, every timeBetweenDisplays seconds.
  • TimeTicked: your function will be called periodically, every timeBetweenTicks seconds.
  • IterationTicked: your function will be called periodically, every iterationBetweenTicks iterations.

The same callback can be used for different events. The parameters timeBetweenDisplays, timeBetweenTicks and iterationBetweenTicks can be modified in the LSParam object.

When a callback is called, the solver is paused. In that state, you can call all the methods of the API marked as “allowed in state Paused”. For example, you can :

  • Stop the resolution.
  • Retrieve the current solution.
  • Retrieve the statistics of the search.

In this section, we detail how callback functions are introduced in each programming language (Python, C++, Java, .NET). To illustrate this description we will use a simple example where a callback function allows to stop the search when no improvement of the objective function is found during a period of 5 seconds (the solved problem is a random knapsack).

In Python, a callback is simply a function or a method passed to LocalSolver. A callback takes two parameters: the LocalSolver object that triggers the event and the type of the callback. It is possible to use the same callback method or object for multiple events or multiple LocalSolver instances. The method can be a static function or a method on a class.

In the following example, we create a dedicated class with two fields last_best_value and last_best_running_time. The callback method uses these two fields with the statistics of LocalSolver to decide to stop or to continue the search. The callback is registered with the method LocalSolver.add_callback():

class CallbackExample:
    def __init__(self):
        self.last_best_value = 0
        self.last_best_running_time = 0

    def my_callback(self, ls, cb_type):
        stats = ls.statistics
        obj = ls.model.objectives[0]
        if obj.value > self.last_best_value:
            self.last_best_running_time = stats.running_time
            self.last_best_value = obj.value
        if stats.running_time - self.last_best_running_time > 5:
            print(">>>>>>> No improvement during 5 seconds: resolution is stopped")
            ls.stop()
        else:
            print(">>>>>> Objective %d" % obj.value)


ls = localsolver.LocalSolver()
cb = CallbackExample()
ls.add_callback(localsolver.LSCallbackType.TIME_TICKED, cb.my_callback)

Each time this callback will be invoked by LocalSolver (namely every LSParameter.time_between_ticks seconds) it retrieves the statistics of the search and consider the total running time and the current best value of the objective function. If no improvement has been found during 5 consecutive seconds, it calls the stop() function to stop the search.

In C++, a callback function is passed to LocalSolver as on object extending the localsolver::LSCallback class. This class has a single virtual method localsolver::LSCallback::callback() taking two parameters: the LocalSolver object that triggers the event and the type of the callback. It is possible to use the same callback object for multiple events or multiple LocalSolver instances.

Here we create a small class MyCallback containing two fields lastBestValue and lastBestRunningTime. The callback method uses these two fields with the statistics of LocalSolver to decide to stop or to continue the search. The callback is registered with the method localsolver::LocalSolver::addCallback():

class MyCallback : public LSCallback {
private:
    int lastBestRunningTime;
    lsint lastBestValue;

public:
    MyCallback() {
        lastBestRunningTime = 0;
        lastBestValue = 0;
    }

    void callback(LocalSolver& ls, LSCallbackType type) {
        LSStatistics stats = ls.getStatistics();
        LSExpression obj = ls.getModel().getObjective(0);

        if(obj.getValue() > lastBestValue) {
            lastBestRunningTime = stats.getRunningTime();
            lastBestValue = obj.getValue();
        }

        if(stats.getRunningTime() - lastBestRunningTime > 5) {
            cout << ">>>>>>> No improvement during 5 seconds: resolution is stopped" << endl;
            ls.stop();
        } else {
            cout << ">>>>>> Objective improved by " << (obj.getValue() - lastBestValue) << endl;
        }
    }
};

LocalSolver ls;
MyCallback cb;
ls.addCallback(CT_TimeTicked, &cb);

Each time this callback will be invoked by LocalSolver (namely every timeBetweenTicks seconds) it retrieves the statistics of the search and consider the total running time and the current best value of the objective function. If no improvement has been found during 5 consecutive seconds, it calls the stop() function to stop the search.

In .NET, a callback function is passed to LocalSolver as a delegate method taking two parameters: the LocalSolver object that triggers the event and the type of the callback. It is possible to use the same callback object for multiple events or multiple LocalSolver instances. As any delegate, the callback can be a static method or an instance method.

In the example below, we use an instance method to use two user-defined fields lastBestValue and lastBestRunningTime. The callback is registered with the instruction ls.AddCallback(LSCallbackType.TimeTicked, cb.MyCallback);, while MyCallback is defined as follows:

class MyCallbackClass {
    private int lastBestRunningTime;
    private long lastBestValue;

    ...

    public void MyCallback(LocalSolver ls, LSCallbackType type)
    {
        LSStatistics stats = ls.GetStatistics();
        LSExpression obj = ls.GetModel().GetObjective(0);

        if (obj.GetValue() > lastBestValue)
        {
            lastBestRunningTime = stats.GetRunningTime();
            lastBestValue = obj.GetValue();
        }

        if (stats.GetRunningTime() - lastBestRunningTime > 5)
        {
            Console.WriteLine(">>>>>>> No improvement during 5 seconds: resolution is stopped");
            ls.Stop();
        }
        else
        {
            Console.WriteLine(">>>>>> Objective " + (obj.GetValue()));
        }
    }
}

LocalSolver ls = new LocalSolver();
MyCallbackClass cb = new MyCallbackClass();
ls.AddCallback(LSCallbackType.TimeTicked, cb.MyCallback);

Each time this callback will be invoked by LocalSolver (namely every TimeBetweenTicks seconds) it retrieves the statistics of the search and consider the total running time and the current best value of the objective function. If no improvement has been found during 5 consecutive seconds, it calls the Stop() function to stop the search.

In Java, a callback function is passed to LocalSolver as an object implementing the LSCallback interface. This interface has a single method callback taking two parameters: the LocalSolver object that triggers the event and the type of the callback. It is possible to use the same callback object for multiple events or multiple LocalSolver instances.

The callback class can be conveniently introduced as an anonymous class:

ls.addCallback(LSCallbackType.TimeTicked, new LSCallback() {
    private long lastBestValue = 0;
    private int lastBestRunningTime = 0;

    public void callback(LocalSolver ls, LSCallbackType type) {
        assert(type == LSCallbackType.Ticked);

        LSStatistics stats = ls.getStatistics();
        LSExpression obj = ls.getModel().getObjective(0);
        if(obj.getValue() > lastBestValue) {
            lastBestRunningTime = stats.getRunningTime();
            lastBestValue = obj.getValue();
        }

        if(stats.getRunningTime() - lastBestRunningTime > 5) {
            System.out.println(">>>>>>> No improvement during 5 seconds: resolution is stopped");
            ls.stop();
        } else {
            System.out.println(">>>>>> Objective improved by " + (obj.getValue() - lastBestValue));
        }
    }
});

In the above code, an anonymous LSCallback object is introduced, with two numeric fields (lastBestValue and lastBestRunningTime). Each time this callback will be invoked by LocalSolver (namely every timeBetweenTicks seconds) it retrieves the statistics of the search and consider the total running time and the current best value of the objective function. If no improvement has been found during 5 consecutive seconds, it calls the stop() function to stop the search.