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, 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 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.