Callbacks and events¶
Hexaly Optimizer 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.
When adding a callback function to Hexaly Optimizer (with the
HexalyOptimizer::addCallback()
function) you can specify to which
event your function must react, thanks to the HxCallbackType
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 HxParam
object.
When a callback is called, the optimizer 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 (Hexaly Modeler, 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).
To add callbacks with Hexaly Modeler, you will need to use the main mode which will allow you to use the modeler as an ordinary programming language. In this situation, a callback is simply a function taking two parameters: the Hexaly Optimizer object that triggers the event and the type of the callback. It is possible to use the same callback method for multiple events or multiple Hexaly Optimizer instances.
In the following exampe, we create a dedicated function referring to two
global values defined in the main function, lastBestValue
and
lastBestRunningTime
. The callback method uses these two values with
the statistics of Hexaly Optimizer to decide to stop or to continue the
search. The callback is registered with the method
HexalyOptimizer.addCallback()
:
use hexaly;
function callbackExample(optimizer, cbType) {
local stats = optimizer.statistics;
local obj = optimizer.model.objectives[0];
if (obj.value > lastBestValue) {
lastBestRunningTime = stats.runningTime;
lastBestValue = obj.value;
}
if (stats.runningTime - lastBestRunningTime > 5) {
println(">>>>>>> No improvement during 5 seconds: resolution is stopped");
optimizer.stop();
} else {
println(">>>>>> Objective " + obj.value);
}
}
function main(args) {
lastBestValue = 0;
lastBestRunningTime = 0;
with(optimizer = hexaly.create()) {
optimizer.addCallback("ITERATION_TICKED", callbackExample);
}
}
Each time this callback will be invoked by Hexaly Optimizer (namely
every TimeBetweenTicks
seconds) it retrieves the statistics of the
search and considers 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 Python, a callback is simply a function or a method passed to a Hexaly Optimizer. A callback takes two parameters: the Hexaly Optimizer 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 Hexaly Optimizer 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 Hexaly Optimizer to decide
to stop or to continue the search. The callback is registered with the
method HexalyOptimizer.add_callback()
:
class CallbackExample:
def __init__(self):
self.last_best_value = 0
self.last_best_running_time = 0
def my_callback(self, optimizer, cb_type):
stats = optimizer.statistics
obj = optimizer.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")
optimizer.stop()
else:
print(">>>>>> Objective %d" % obj.value)
optimizer = HexalyOptimizer()
cb = CallbackExample()
optimizer.add_callback(HxCallbackType.TIME_TICKED, cb.my_callback)
Each time this callback will be invoked by Hexaly Optimzier (namely
every HxParameter.time_between_ticks
seconds) it retrieves
the statistics of the search and considers 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 Hexaly Optimizer as an object
extending the HxCallback
class. This class has a single
virtual method HxCallback::callback()
taking two parameters:
the Hexaly Optimizer 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 Hexaly Optimizer 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 Hexaly Optimizer to decide to
stop or to continue the search. The callback is registered with the
method HexalyOptimizer::addCallback()
:
class MyCallback : public HxCallback {
private:
int lastBestRunningTime;
hxint lastBestValue;
public:
MyCallback() {
lastBestRunningTime = 0;
lastBestValue = 0;
}
void callback(HexalyOptimizer& optimizer, HxCallbackType type) {
HxStatistics stats = optimizer.getStatistics();
HxExpression obj = optimizer.getModel().getObjective(0);
if(obj.getValue() > lastBestValue) {
lastBestRunningTime = stats.getRunningTime();
lastBestValue = obj.getValue();
}
if(stats.getRunningTime() - lastBestRunningTime > 5) {
std::cout << ">>>>>>> No improvement during 5 seconds: resolution is stopped" << std::endl;
optimizer.stop();
} else {
std::cout << ">>>>>> Objective improved by " << (obj.getValue() - lastBestValue) << std::endl;
}
}
};
HexalyOptimizer optimizer;
MyCallback cb;
optimizer.addCallback(CT_TimeTicked, &cb);
Each time this callback will be invoked by Hexaly Optimizer (namely
every timeBetweenTicks
seconds) it retrieves the statistics of the
search and considers 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 Hexaly Optimizer as a delegate method taking two parameters: the Hexaly Optimizer 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 Hexaly Optimizer 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 optimizer.AddCallback(
HxCallbackType.TimeTicked, cb.MyCallback);
, while MyCallback
is
defined as follows:
class MyCallbackClass {
private int lastBestRunningTime;
private long lastBestValue;
public void MyCallback(HexalyOptimizer optimizer, HxCallbackType type)
{
HxStatistics stats = optimizer.GetStatistics();
HxExpression obj = optimizer.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");
optimizer.Stop();
}
else
{
Console.WriteLine(">>>>>> Objective " + (obj.GetValue()));
}
}
}
HexalyOptimizer optimizer = new HexalyOptimizer();
MyCallbackClass cb = new MyCallbackClass();
optimizer.AddCallback(HxCallbackType.TimeTicked, cb.MyCallback);
Each time this callback will be invoked by Hexaly Optimizer (namely
every TimeBetweenTicks
seconds) it retrieves the statistics of the
search and considers 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 Hexaly Optimizer as an object
implementing the HxCallback interface. This interface has a single
method callback
taking two parameters: the Hexaly Optimizer 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
Hexaly Optimizer instances.
The callback class can be conveniently introduced as an anonymous class:
HexalyOptimizer optimizer = new HexalyOptimizer();
optimizer.addCallback(HxCallbackType.TimeTicked, new HxCallback() {
private long lastBestValue = 0;
private int lastBestRunningTime = 0;
public void callback(HexalyOptimizer optimizer, HxCallbackType type) {
HxStatistics stats = optimizer.getStatistics();
HxExpression obj = optimizer.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");
optimizer.stop();
} else {
System.out.println(">>>>>> Objective improved by " + (obj.getValue() - lastBestValue));
}
}
});
In the above code, an anonymous HxCallback
object is introduced,
with two numeric fields (lastBestValue
and lastBestRunningTime
).
Each time this callback will be invoked by Hexaly Optimizer (namely
every timeBetweenTicks
seconds) it retrieves the statistics of the
search and considers 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.