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, everytimeBetweenDisplays
seconds.TimeTicked
: your function will be called periodically, everytimeBetweenTicks
seconds.IterationTicked
: your function will be called periodically, everyiterationBetweenTicks
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. Note that the effect
of stop()
will be taken into account after the end of the callback.
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. Note that the effect
of stop()
will be taken into account after the end of the callback.
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. Note that the effect
of Stop()
will be taken into account after the end of the callback.
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.TimeTicked);
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. Note that the effect of stop()
will be
taken into account after the end of the callback.