ILOG CPLEX 10.1 User's Manual > Advanced Programming Techniques > Using Goals > Branch & Cut with Goals

Goals allow you to take control of the branch & cut search procedure used by ILOG CPLEX to solve MIP problems. To help you understand how to use goals with ILOG CPLEX, this section reviews how this procedure works.

The branch & cut search procedure manages a search tree consisting of nodes. Every node represents a subproblem to be solved, and the root node of the tree represents the entire problem. Nodes are called active if they have not yet been processed.

The tree is first initialized to contain the root node as the only active node. IloCplex processes active nodes from the tree until either no more active nodes are available or some limit has been reached. Once a node has been processed, it is no longer active.

When processing a node, IloCplex starts by solving the continuous relaxation of its subproblem--that is, the subproblem without integrality constraints. If the solution violates any cuts, IloCplex adds them to the node problem and re-solves. This is iterated until no more violated cuts are found by IloCplex. If at any point the relaxation becomes infeasible, the node is pruned, that is, it is removed from the tree.

To solve the node problem, IloCplex checks whether the solution satisfies the integrality constraints. If so, and if its objective value is better than that of the current incumbent, the solution of the node problem is used as the new incumbent. Otherwise, IloCplex splits the node problem into one or two smaller subproblems, typically by branching on a variable that violates its integrality condition. These subproblems are added to the tree as active nodes and the current node is deactivated.

The primary use of goals is to take control of these last steps, namely the integer-feasibility test and the creation of subproblems. However, as discussed later, goals also allow you to add local and global cuts.

Note
The discussion of the details of using goals will be presented mainly in terms of the C++ API. The Java and .NET APIs follow the same design and are thus equivalent at this level of discussion. In cases where a difference between these APIs needs to be observed, the point will be raised. Where the difference is only in syntax, the other syntax will be mentioned in parentheses following the C++ syntax.

In C++, goals are implemented in objects of type IloCplex::GoalI (having handle class IloCplex::Goal). In Java, goals are implemented in objects of type IloCplex.Goal (and there are no handle classes). In .NET, goals are implemented by the class Cplex.Goal. The method IloCplex::GoalI::execute (IloCplex.Goal.execute) is where the control is implemented. This method is called by IloCplex after a node relaxation has been solved and all cuts have been added. Invoking the method execute of a goal is often referred to as executing a goal. When the method execute is executed, other methods of the class IloCplex::GoalI (IloCplex.Goal or Cplex.Goal) can be called to query information about the current node problem and the solution of its relaxation.

Typically, the implementation of the method execute will perform the following steps:

  1. Check feasibility. An interesting possibility here is that the feasibility check may include more than verifying integrality of the solution. This allows you to enforce constraints that could not reasonably be expressed using linear constraints through cuts or branching. In other words, this allows you to use goals in a way that makes them part of the model to be solved. Such a use is common in Constraint Programming, but it is less frequently used in Mathematical Programming.
  2. Optionally find local or global cuts to be added. Local cuts will be respected only for the subtree below the current node, whereas global cuts will be enforced for all nodes from then on.
  3. Optionally construct a solution and pass it to IloCplex.
  4. Instruct IloCplex how to proceed. Instructing IloCplex how to proceed is done through the return value of the method execute, which is another goal. IloCplex simply continues by executing this goal.

IloCplex provides a selection of special goals that can be used to specify how to proceed:

Since IloCplex::GoalI::OrGoal (IloCplex.or or Cplex.Or) and IloCplex::GoalI::AndGoal (IloCplex.and or Cplex.And) take other goals as parameters, goals can be combined into aggregate goals. In fact, this is how goals are typically used for specifying a branching strategy. A typical return goal of a user-written execute method for C++ looks like this:

return AndGoal(OrGoal(var <= IloFloor(val), var >= IloFloor(val)+1), this);

and for Java, it looks like this:

return cplex.and(cplex.or(cplex.leGoal(var, Math.floor(val)),
                          cplex.geGoal(var, Math.floor(val)+1)), this);

and for C#.NET, it looks like this:

return cplex.And(
            cplex.Or(cplex.GeGoal(_vars[bestj], System.Math.Floor(x[bestj])+1),
                     cplex.LeGoal(_vars[bestj], System.Math.Floor(x[bestj]))), 
            this);

For the C++ case, note that since this statement would be called from the execute method of a subclass of IloCplex::GoalI, the full method name IloCplex::GoalI::OrGoal can be abbreviated to OrGoal (and likewise AndGoal).

This return statement returns an And goal that first executes the Or goal and then the current goal itself specified by the this parameter. When the Or goal is executed next, it will create two subnodes. In the first subnode, the first local cut goal representing images/goalsa.gif (where images/goalsa2.gif denotes the floor of val) will be executed, thus adding the constraint images/goalsa3.gif for the subtree of this node. Similarly, the second subnode will be created, and when executing its constraint goal the constraint var images/goalsa4.gif will be added for the subtree. this is then executed on each of the nodes that have just been created; the same goal is used for both subtrees. Further details on how goals are processed will be discussed later.

Consider the following example to clarify the discussions of goals. This example is available as ilogoalex1.cpp in the examples/src subdirectory of your ILOG CPLEX distribution. The equivalent Java implementation can be found as GoalEx1.java in the same location. The C#.NET version is in Goalex1.cs and the VB.NET version is in Goalex1.vb.

This example shows how to implement and use a goal for controlling the branch strategy used by IloCplex. As discussed, goals are implemented as subclasses of the class IloCplex::GoalI (IloCplex.Goal or Cplex.Goal). The C++ implementation of that example uses the macro

ILOCPLEXGOAL1(MyBranchGoal, IloNumVarArray, vars)

instead. This macro defines two things, class MyBranchGoalI and the function

IloCplex::Goal MyBranchGoal(IloEnv env, IloNumVarArray vars);

The class MyBranchGoalI is defined as a subclass of class IloCplex::GoalI (IloCplex.Goal or Cplex.Goal) and has a private member IloNumVarArray vars. The function MyBranchGoal creates an instance of class MyBranchGoalI, initializes the member vars to the parameter vars passed to the function, and returns a handle to the new goal object. The curly brackets "{ ... }" following the macro enclose the implementation of the method MyBranchGoalI::execute containing the actual code of the goal.

The use of the macro is very convenient as the amount of user code is equivalent to the amount for defining a function, but with a slightly unusual syntax. IloCplex provides seven such macros that can be used for defining goals with 0 to 6 private members. If more than 6 members are needed, IloCplex::GoalI (IloCplex.Goal or Cplex.Goal) must be subclassed by hand.

Since the Java programming language does not provide macros, a subclass of IloCplex.Goal must always be implemented by hand. In this example, this class is called MyBranchGoal and there is no helper function for creating an instance of that class (as the macro does in the case of C++).

The goal is then used for solving the extracted node by calling:

cplex.solve(MyBranchGoal(env, var));

for C++, or for Java:

cplex.solve(new MyBranchGoal(var));

instead of the usual cplex.solve. The rest of the main function contains nothing new and will not be discussed any further.

In the implementation of the goal, or more precisely its method execute, starts by declaring and initializing some arrays. These arrays are then used by methods of class IloCplex::GoalI (IloCplex.Goal or Cplex.Goal) to query information about the node subproblem and the solution of its relaxation. The method getValues is used to query the solution values for the variables in vars, method getObjCoefs is used to query the linear objective function coefficients for these variables, and method getFeasibilities is used to query feasibility statuses for them. The feasibility status of a variable indicates whether IloCplex considers the current solution value of the variable to be integer feasible or not. IloCplex::GoalI (IloCplex.Goal or Cplex.Goal) provides a wealth of other query methods. For details, see the ILOG CPLEX Reference Manuals.

Once you have gathered information about the variables, their objective coefficients, and their current feasibility statuses, compute the index of an integer infeasible variable in vars that has the largest objective coefficients among the variables with largest integer infeasibility. That index is recorded in variable bestj.

Then create a new goal handle object res. By default, this is initialized to an empty goal. However, if an integer infeasible variable was found among those in vars, then variable bestj will be 0 and a nonempty goal will be assigned to res:

res = AndGoal(OrGoal(vars[bestj] >= IloFloor(x[bestj])+1,
                     vars[bestj] <= IloFloor(x[bestj])),
                     this);

This goal creates two branches, one for images/goalsa5.gif and one for images/goalsa6.gif and continues branching in both subtrees with the same goal this. Finally, call method end for all temporary arrays and return goal res.

Since Java objects are garbage collected, there is no need for the variable res. Instead, depending on the availability of an integer infeasible variable, the null goal is returned or the returned goal is created in the return statement itself:

return cplex.and(cplex.or(cplex.geGoal(_vars[bestj],
                          Math.floor(x[bestj]))+1,
                          cplex.leGoal(_vars[bestj],
                          Math.floor(x[bestj]))),
                 this);