New feature: API changes for extension engine classes

Describes the API changes for CP Optimizer.

For V12.8.0, the C++ interface of CP Optimizer has undergone some API changes which you need to know about, especially if you sub-class any of CP Optimizer's classes to modify its behavior, or if you access any object of a type prefixed by Ilc. The main principle of the change is to cleanly separate the services that are available at the top level outside the search process (such as extracting a model, getting a solution, and changing parameters) from those services available in search on objects such as goals, constraints, propagators, choosers, and evaluators.

Background

CP Optimizer works as follows: when asked to perform a solve, the master object, an instance of IloCP, will create a number of workers (as specified by the IloCP::Workers parameter) to perform the solution search. In most cases, the operation of these workers is hidden. However, if you have extended CP Optimizer with your own code (by subclassing goals, constraints, propagators, choosers, or evaluators), then when your code gets called, you have access to a worker engine object, unique to the worker that called your code. In CP Optimizer V12.7.1, this worker engine object is an instance of IloCP, just like the master, but operating in a different worker mode. This creates some confusion, because the IloCP class contains two sets of APIs: one set which makes sense to use when the IloCP object is the master, and another set which makes sense to use when the IloCP object is a worker engine. So, at the top (master) level, the "in-search" services on IloCP are illegal, and, likewise inside search, the top-level services cannot be used.

With V12.8.0, the two groups of services have been logically separated: the top-level master services stay with the IloCP object, with the in-search services being moved to a new worker engine class, IloCPEngine. The IloCPEngine class manages objects created through "Ilc" APIs, such as objects of type IlcConstraintI, IlcRevInt, IlcGoalI (which take IloCPEngine in their constructors), as well as objects such as IloIntVarEvalI and IloIntVarChooserI, related to evaluators and choosers. IloCPEngine provides services such as getting the reversible heap (IloCPEngine::getHeap), adding a reversible action (IloCPEngine::addReversibleAction), or performing a local solve in a depth-first search manner (IloCPEngine::solve). IloCPEngine also provides methods to access the current domains of Concert Technology variables inside the current worker (for example IloCPEngine::getMin(IloIntVar)), and a way to get statistics on the local worker (like number of branches taken) through the IloCPEngine::getInfo member function.

Code impact for the C++ API

If you do not subclass any CP Optimizer classes in your code, the impact on your code should be minimal. The most evident change is that accessors to Ilc variables are no longer available on the IloCP object in C++. Let's assume your code does the following:

IloIntVarArray x(env, 100, 0, 1);
IloModel model(env);
// ... create model ..
IloCP cp(model);
cp.solve();
IloInt total = 0; 
for (IloInt i = 0; i < 100; i++)
    total += cp.getIntVar(x[i]).getValue();

Here, you only need to update the last line of the code, using the direct accessor, cp.getValue(x[i]) instead of getting the IlcIntVar object:

    total += cp.getValue(x[i]);  

There are accessors available to get the values or domains of variables directly on the IloCP object. For example, after calling cp.propagate(), the following incomplete list can be useful to get back domains.

IloCP::getMin(IloIntVar y)
IloCP::getMax(IloIntVar y)
IloCP::IntVarIterator IloCP::iterator(IloIntVar y)
IloCP::getStartMin(IloIntervalVar itv)
IloCP::getEndMax(IloIntervalVar itv)
IloCP::isPresent(IloIntervalVar itv)

Domains can be printed easily too. Instead of writing:

cout << cp.getIntVar(x[0]) << endl;

you can write:

cout << cp.domain(x[0]) << endl;

If you write your own goals, there are also some small changes to make. Let's examine the following code:

ILCGOAL1(MyInst, IlcIntVar, var) {
   if (var.isFixed()) return 0;
   IlcInt v = var.getMin();
   return IlcOr(var == v, IlcAnd(var != v, this));
}

ILCGOAL1(MyGen, IlcIntVarArray, x) {
   IloCP cp = getCP();
   IlcInt choice = IlcChooseFirstUnboundInt(x);
   if (choice < 0) return 0;
   return IlcAnd(MyInst(cp, x[choice]), this);
}

ILOCPGOALWRAPPER1(MyGoal, cp, IloIntVarArray, x) {
   return MyGen(cp, cp.getIntVarArray(x)); // 'cp' is of type IloCP here
}

IloEnv env;
IloIntVarArray x(env, 10, 0, 1);
IloModel mdl(env);
mdl.add(x);
IloCP cp(mdl);
cp.solve(MyGoal(env, x));
cout << cp.domain(x) << endl;

This code creates a model with 10 variables with domains {0,1} and creates a simple goal to fix their values. Of interest to us here are the ILCGOAL MyGen and the goal wrapper MyGoal. In version 12.7.1, goals are allocated on the local worker engine IloCP object. Likewise, the goal wrapper that converts an IloGoal to an IlcGoal MyGoal takes a second parameter cp for the ILOCPGOALWRAPPER macro. In the body following the macro, the cp variable has type IloCP. In CP Optimizer V12.8.0, we would write:

ILCGOAL1(MyGen, IlcIntVarArray, x) {
   IlcCPEngine cp = getCPEngine(); // <-- Change here...
   IlcInt choice = IlcChooseMinSizeInt(x);
   if (choice < 0) return 0;
   return IlcAnd(MyInst(cp, x[choice]), this);
}

ILOCPGOALWRAPPER1(MyGoal, cp, IloIntVarArray, x) {
   return MyGen(cp, cp.getIntVarArray(x)); // <-- cp is of type IloCPEngine here
}

Note that in MyGen, IloCP cp = getCP(); has been replaced by IlcCPEngine cp = getCP();. Normally, this is the only change you would have to make in a typical goal. All the API relevant to goals which was on IloCP prior to version 12.8.0 is now available on IlcCPEngine, so the remainder of your code should compile and work correctly. When we call the subgoal MyInst, note that we now pass an object of type IlcCPEngine instead of IloCP. The actual code of the goal wrapper MyGoal remains unchanged, but we duplicate here to note that the type of the cp parameter is now of IloCPEngine.

The pattern for classes IlcConstraintI and IlcDemonI (with or without macros ILCDEMON or ILCCTDEMON) are very similar. Both of these classes now accept an object of type IlcCPEngine in their constructor and instead of getCP() in the body, one should instead use getCPEngine().

The pattern for macro ILCPCONSTRAINTWRAPPER is very similar to that of ILOCPGOALWRAPPER - essentially the macro's second parameter, which is the name of the engine, is now of type IloCPEngine instead of IloCP.

If you subclass any of the following:
  • IloIntVarChooserI
  • IloIntValueChooserI
  • IloIntVarEvalI
  • IloIntValueEvalI
then you will need to make a small change to your code. Specifically, some signatures have changed in the following way:
Table 1. Changes in choosers and evaluators
Before V12.8.0 V12.8.0 and later
IloIntVarChooserI::choose(IloCP, IloIntVarArray) IloIntVarChooserI::choose(IloCPEngine, IloIntVarArray)
IloIntValueChooserI::choose(IloCP, IloIntVarArray, IloInt) IloIntValueChooserI::choose(IloCPEngine, IloIntVarArray, IloInt)
IloIntVarEvalI::eval(IloCP, IloIntVar) IloIntVarEval::eval(IloCPEngine, IloIntVar)
IloIntValueEvalI::eval(IloCP, IloIntVar, IloInt) IloIntValueEval::eval(IloCPEngine, IloIntVar, IloInt)
Since all relevant API from IloCP is now present on IloCPEngine, the body of the remainder of your "choose" or "eval" functions should be largely unchanged.

One technical point you should be aware of, which should not affect you in practice, is that some of the functionality of IloCPEngine is made available through its base case IlcCPEngine. You may use IloCPEngine everywhere without worry, but keep in mind that you will find documentation for some of the functionality that IloCPEngine provides on the IlcCPEngine class.

Displaced methods

The following is a comprehensive list of methods that are no longer available for the IloCP object in C++ because of the split in services between IloCP and the new IloCPEngine class. They have been moved to IloCPEngine.

IloCP::getHeap() const
IloCP::getCumulElement(const IloCumulFunction) const
IloCP::getFloatArray(const IloNumArray) const
IloCP::getIntArray(const IloIntArray) const
IloCP::getIntervalSequence(const IloIntervalSequenceVar) const
IloCP::getInterval(const IloIntervalVar) const
IloCP::getIntTupleSet(const IloIntTupleSet) const
IloCP::getIntVarArray(const IloIntVarArray) const
IloCP::IlcIntVar getIntVar(const IloNumVar) const
IloCP::add(const IlcConstraint) const
IloCP::add(const IlcConstraintArray) const
IloCP::addReversibleAction(const IlcGoal goal) const
IloCP::exitSearch() const
IloCP::fail(IloAny label=0) const
IloCP::startNewSearch(const IlcGoal) const