/***********************************************************************
Xpress Optimizer Examples
=========================
file MostViolated.java
```````````````````
A Branching rule branching on the most violated Integer/Binary
Demonstrate the Xpress-MP change branch callbacks.
(c) 2020-2025 Fair Isaac Corporation
***********************************************************************/
import com.dashoptimization.AbstractChangeBranchObjectListener;
import com.dashoptimization.DefaultMessageListener;
import com.dashoptimization.XPRS;
import com.dashoptimization.XPRSbranchobject;
import com.dashoptimization.XPRSprob;
import static com.dashoptimization.XPRSenumerations.CutStrategy;
import java.io.IOException;
/** Example for a branch callback that always branches on the most infeasible
* variable.
*
* <p>
* Here "most infeasible" means the variable with the biggest fractionality,
* i.e., the integer variable that is farthest away from an integral value.
* </p>
*
* <p>
* In order to find the variable with the biggest fractionality, we need the
* variable types (so that we don't end up branching on a fractional variable)
* and the values of the variables in the current relaxation. This data is
* typically queried into arrays <code>cType</code> and <code>x</code> at each
* node at which we have to find a branching variable.
* Allocation of these arrays may take time, so it is desirable to cache those
* arrays so that we don't have to (re)allocate them at each and every node.
* However, when caching these arrays a number of things have to be considered,
* most of which are implemented in this example:
* <dl>
* <dt>Multi-Threading</dt>
* <dd>
* In a multi-threaded solve, callbacks may execute in parallel. This means
* that each thread requires its own <code>x</code> array since otherwise
* multiple threads might read into the same array from different nodes.
* The <code>cType</code> array can be shared between threads since this is
* a read-only array.
* By default Xpress optimizer serializes the execution of callbacks
* using a mutex. That is, by default callbacks will not be executed
* simultaneously. In that case the threads can share the same
* <code>x</code> array. You can tell the XPress optimizer to not
* serialize callbacks by setting the <code>MUTEXCALLBACKS</code> control
* to 0. In that case, the threads cannot share the same <code>x</code>
* array and each thread has to allocate its own array.
* </dd>
* <dt>Presolved or original space</dt>
* <dd>
* We can choose whether we want to make branching decisions in the
* original problem space or in the presolved problem space. Branching
* in the original space has the advantage that variables have their
* original meaning and that all variables are still present. Given that
* internally the optimizer works with the presolved problem, a disadvantage
* is that relaxation values and branching decisions must be translated
* back and forth between the presolved and original space.
* Branching in the presolved space allows working directly with the same
* data as the optimizer does internally. This is usually faster but fewer
* assumptions can be made about the presolved model (variables may have
* a different meaning, may have disappeared, etc.).
* When setting up the <code>cType</code> an <code>x</code> arrays one
* has to be careful that they are setup for the right model.
* </dd>
* <dt>Restarts</dt>
* <dd>
* For performance reasons the optimizer may decide to discard the current
* search tree and restart optimization from scratch, exploting what it
* has learned about the problem so far. In this case, the presolved model
* may change (for example, it is common that more variables can be fixed
* and thus the size of the presolved model reduces). This means that in
* case of branching on the presolved model the arrays <code>cType</code>
* and <code>x</code> have to be resetup at a restart.
* </dd>
* <dt>Constraints by branching</dt>
* <dd>
* It is possible to not put all constraints into the initial model
* description but to enforce certain constraints by means of branching
* decisions. An example for this is the handling of symmetries. In that
* case the optimizer does not see all constraints in the presolve phase.
* However, by default the optimizer assumes that the submitted problem
* is complete. If it is not then you may have to disable certain
* reductions (in particular dual reductions) since otherwise you may
* end up with incorrect results. This special case is not illustrated
* in this example since the "branch on most fractional" strategy does not
* interfere with any presolve reductions.
* </dd>
* </dl>
* In this example we illustrate how to implement the callback in the different
* contexts mentioned above. For each combination of contexts we have a
* class that creates and caches the <code>cType</code> and <code>x</code>
* arrays in a way that is safe in the given context.
* </p>
*
* <p>
* Note that in this example we always go through the whole list of variables
* and check for each variable whether it is integer or not. This is for
* illustrative purposes and to keep things simple. In a production code you
* may want to consider using {@link XPRSprob#getMIPEntities()} or
* {@link XPRSprob#getDiscreteCols()} to get a list of integer variables.
* </p>
*/
public class MostViolated {
/** Branch callback that branches on the most fractional variable.
*
* The actual branching happens in function
* {@link #changeBranchObjectEvent(XPRSprob, XPRSbranchobject)}
* This function uses the two abstract functions {@link #getCType(XPRSprob)}
* and {@link #getX(XPRSprob)} to get the arrays of variable types and
* current relaxation values.
* We provide different subclasses of <code>BranchBase</code> that have
* different implementations of these functions, depending on the context
* in which the class can be used.
*/
private abstract static class BranchBase extends AbstractChangeBranchObjectListener {
/** Tolerance used for checking fractionality. */
private final double tolerance;
/** Flag to indicate whether branches are created in terms of
* the original model.
* If they are not created in terms of the original model, they are
* created in terms of the presolved model.
*/
private final boolean isOriginal;
/** Create a new branching callback for <code>prob</code>.
* @param prob The problem on which this callback will be invoked.
* @param isOriginal Flag that indicates whether the data returned
* by {@link #getX(XPRSprob)} and
* {@link #getCType(XPRSprob)} is in terms of the
* original problem.
*/
protected BranchBase(XPRSprob prob, boolean isOriginal) {
tolerance = prob.controls().getMatrixTol();
this.isOriginal = isOriginal;
}
/** Get the relaxation at the current node.
* @param prob The problem on which the callback is invoked.
* @return the relaxation vector for the current node.
*/
protected abstract double[] getX(XPRSprob prob);
/** Get the variable types at the current node.
* @param prob The problem on which the callback is invoked.
* @return the variable type vector at the current node.
*/
protected abstract byte[] getCType(XPRSprob prob);
/** Get the number of MIP threads available for use when solving a problem
* @param prob The problem to query.
* @return the number of MIP threads that may be used when solving the problem.
*/
protected int getNumThreads(XPRSprob prob)
{
int threads = prob.controls().getMIPThreads();
if (threads == -1)
// MIP threads set to -1 means use the Threads control
threads = prob.controls().getThreads();
if (threads == -1)
// If still at the default then optimizer will use as many
// threads as there are cores
threads = prob.attributes().getCoresDetected();
return threads;
}
/** Branch on the most fractional integer.
* This is the function that gets invoked by the optimizer whenever
* a branching decision has to be made.
* @param prob The problem on which the callback is invoked.
* @param oBranch The branch that the optimizer plans to perform.
* @return the branch we want the optimizer to perform (may be
* <code>oBranch</code> or <code>null</code> in which case the
* optimizer will perform <code>oBranch</code>).
*/
public XPRSbranchobject changeBranchObjectEvent(XPRSprob prob, XPRSbranchobject oBranch)
{
// Get the current relaxation
byte[] cType = getCType(prob);
double[] x = getX(prob);
// This is just a safeguard. The two arrays should always have the
// same length.
if (x.length != cType.length)
throw new IllegalArgumentException("Mismatch: " + cType.length + " vs. " + x.length);
if (oBranch == null)
return null;
// Default branch is the original branch.
XPRSbranchobject newBranch = oBranch;
// Find the most fractional variable.
double maxDist = 0.0;
int branchCol = -1;
double branchUp = 0;
double branchDown = 0;
for (int j = 0; j < x.length; ++j) {
if (cType[j] == 'I' || cType[j] == 'B') {
double upDist = Math.ceil(x[j]) - x[j];
double downDist = x[j] - Math.floor(x[j]);
double dist = Math.min(upDist, downDist);
if (downDist > tolerance && upDist > tolerance) {
if (dist > maxDist) {
branchCol = j;
branchUp = Math.ceil(x[j]);
branchDown = Math.floor(x[j]);
maxDist = dist;
}
}
}
}
// If we found a branching candidate then create the new branch
// object for branching on that candidate.
if (branchCol != -1) {
// Create a branch object for the presolved space.
// Wrap registering of the branch into try-catch so that
// we properly delete the object in case of any error.
// If there is no error then the optimizer will take care
// of cleaning up the branch object.
XPRSbranchobject bo = prob.createBranchObject(isOriginal);
try {
// Create the two branches.
bo.addBranches(2);
bo.addBounds(0, 1, new byte[]{'L'}, new int[]{branchCol},
new double[]{branchUp});
bo.addBounds(1, 1, new byte[]{'U'}, new int[]{branchCol},
new double[]{branchDown});
// Prefer the up branch.
bo.setPreferredBranch(0);
// Set a small priority so that the branch is actually used.
bo.setPriority(100);
// Submit the new branch to the solver and clear the
// bo reference since the object is now owned (and cleaned
// up) by the optimizer.
newBranch = bo;
bo = null;
}
finally {
if (bo != null)
bo.close();
}
}
return newBranch;
}
}
/** Branch callback implementation that branches in the original space.
* Instances of this class cache the <code>cType</code> array since the
* types of variables never change in the original model. They also cache
* the <code>x</code> array since the size of the original model never
* changes. The <code>x</code> array is shared between threads, so the
* the class cannot be used for parallel execution of callbacks.
*/
private static final class BranchOriginal extends BranchBase {
private final byte[] cType;
private final double[] x;
/** Create a new instance.
* @param prob The problem that this callback will be used for.
* @param origCType Column types in the <em>original</em> model.
*/
public BranchOriginal(XPRSprob prob, byte[] origCType) {
super(prob, true);
if (prob.controls().getMutexCallBacks() == 0)
throw new IllegalArgumentException("this implementation cannot be used for parallel execution");
cType = origCType;
x = new double[cType.length];
}
/** Get the current relaxation in terms of the <em>original</em> model.
*/
@Override
protected double[] getX(XPRSprob prob) {
prob.getCallbackSolution(null, x, 0, x.length - 1);
return x;
}
/** Get column types in terms of the <em>original</em> model. */
@Override
protected byte[] getCType(XPRSprob prob) {
return cType;
}
}
/** Branch callback implementation that branches in the original space.
* Instances of this class cache the <code>cType</code> array since the
* types of variables never change in the original model. They also cache
* the <code>x</code> array since the size of the original model never
* changes. Each thread has its own <code>x</code> array, so the class
* can be used when callbacks are allowed to run in parallel.
*/
private static final class BranchOriginalMT extends BranchBase {
private final byte[] cType;
private final double[][] threadX;
/** Create a new instance.
* @param prob The problem that this callback will be used for.
* @param origCType Column types in the <em>original</em> model.
*/
public BranchOriginalMT(XPRSprob prob, byte[] origCType) {
super(prob, true);
cType = origCType;
int threads = getNumThreads(prob);
threadX = new double[threads][];
}
/** Get an x vector buffer for the invoking thread. */
private synchronized double[] getThreadX(XPRSprob prob) {
// Get the thread id from the optimizer
int myId = prob.attributes().getMIPThreadID();
// Create (and cache) an x vector buffer
if (threadX[myId] == null)
threadX[myId] = new double[cType.length];
// Return the buffer for this thread
return threadX[myId];
}
/** Get the current relaxation in terms of the <em>original</em> model.
*/
@Override
protected double[] getX(XPRSprob prob) {
double[] x = getThreadX(prob);
prob.getCallbackSolution(null, x, 0, x.length - 1);
return x;
}
/** Get column types in terms of the <em>original</em> model. */
@Override
protected byte[] getCType(XPRSprob prob) {
return cType;
}
}
/** Branch callback implementation that branches in the presolved space.
* Instances of this class assume that there will be no restarts and thus
* cache the <code>cType</code> array. They also cache
* the <code>x</code> array since without restarts the size of the presolved
* model does not change. The <code>x</code> vector is shared between
* threads, so this class cannot be used if callbacks are invoked in
* parallel.
*/
private static final class BranchPresolved extends BranchBase {
private final byte[] cType;
private final double[] x;
/** Create a new callback.
* @param prob Problem for which the callback will be used. The active
* problem referenced by this must be the presolved problem.
*/
public BranchPresolved(XPRSprob prob) {
super(prob, false);
if (prob.controls().getMipRestart() != 0)
throw new IllegalArgumentException("this class is not compatible with restarts");
if (prob.controls().getMutexCallBacks() == 0)
throw new IllegalArgumentException("this implementation cannot be used for parallel execution");
int cols = prob.attributes().getCols();
cType = new byte[cols];
prob.getColType(cType, 0, cols - 1);
x = new double[cols];
}
/** Get the current relaxation in terms of the <em>presolved</em> model.
*/
@Override
protected double[] getX(XPRSprob prob) {
int cols = prob.attributes().getCols();
prob.getCallbackPresolveSolution(null, x, 0, cols - 1);
return x;
}
/** Get the current relaxation in terms of the <em>presolved</em> model.
*/
@Override
protected byte[] getCType(XPRSprob prob) {
return cType;
}
}
/** Branch callback implementation that branches in the presolved space.
* Instances of this class assume that there will be no restarts and thus
* cache the <code>cType</code> array. They also cache
* the <code>x</code> array since without restarts the size of the presolved
* model does not change. The class uses a different <code>x</code> array
* for each thread and is thus safe to use for parallel callback execution.
*/
private static final class BranchPresolvedMT extends BranchBase {
private final byte[] cType;
private final double[][] threadX;
/** Create a new callback.
* @param prob Problem for which the callback will be used. The active
* problem referenced by this must be the presolved problem.
*/
public BranchPresolvedMT(XPRSprob prob) {
super(prob, false);
if (prob.controls().getMipRestart() != 0)
throw new IllegalArgumentException("this class is not compatible with restarts");
int cols = prob.attributes().getCols();
cType = new byte[cols];
prob.getColType(cType, 0, cols - 1);
int threads = getNumThreads(prob);
threadX = new double[threads][];
}
/** Get an x vector buffer for the invoking thread. */
private synchronized double[] getThreadX(XPRSprob prob) {
// Get the thread id from the optimizer
int myId = prob.attributes().getMIPThreadID();
// Create (and cache) an x vector buffer
if (threadX[myId] == null)
threadX[myId] = new double[cType.length];
// Return the buffer for this thread
return threadX[myId];
}
/** Get the current relaxation in terms of the <em>presolved</em> model.
*/
@Override
protected double[] getX(XPRSprob prob) {
double[] x = getThreadX(prob);
int cols = prob.attributes().getCols();
prob.getCallbackPresolveSolution(null, x, 0, cols - 1);
return x;
}
/** Get the current relaxation in terms of the <em>presolved</em> model.
*/
@Override
protected byte[] getCType(XPRSprob prob) {
return cType;
}
}
/** Callback implementation that does not cache any arrays.
* The class recreates the <code>cType</code> and <code>x</code> arrays
* at every node and is thus safe to use in any context.
*/
private static final class BranchPresolvedUncached extends BranchBase {
/** Create a new callback.
* @param prob Problem for which the callback will be used.
*/
public BranchPresolvedUncached(XPRSprob prob) {
super(prob, false);
}
/** Get the current relaxation in terms of the <em>presolved</em> model.
*/
@Override
protected double[] getX(XPRSprob prob) {
int cols = prob.attributes().getCols();
double[] x = new double[cols];
prob.getCallbackPresolveSolution(null, x, 0, cols - 1);
return x;
}
/** Get the current relaxation in terms of the <em>presolved</em> model.
*/
@Override
protected byte[] getCType(XPRSprob prob) {
int cols = prob.attributes().getCols();
byte[] cType = new byte[cols];
prob.getColType(cType, 0, cols - 1);
return cType;
}
}
/** Run this example.
* Command line arguments are:
* <dl>
* <dt>-mt</dt> <dd>To invoke callbacks in parallel.</dd>
* <dt>-original</dt><dd>Make branching decisions in original space.</dd>
* <dt>-restart</dt> <dd>Allow restarts.</dd>
* <dt>model</dt> <dd>Path to the model file to solve.</dd>
* </dl>
*/
public static void main(String[] args) {
// Process the command line.
// By default we don't use parallel callbacks, don't branch on the
// original model and don't use restarts. Any of this can be changed
// by the corresponding command line argument.
boolean mt = false;
boolean orig = false;
boolean restart = false;
String model = "../data/burglar"; // default model
for (String arg : args) {
if (arg.equals("-mt"))
mt = true;
else if (arg.equals("-original"))
orig = true;
else if (arg.equals("-restart"))
restart = true;
else
model = arg;
}
try (XPRSprob prob = new XPRSprob(null)) {
prob.setLogFile("mvb.log");
prob.readProb(model);
// Install default output
prob.addMessageListener(DefaultMessageListener::console);
// Get the original column types (in case we need them later
// for branching in the original space)
int cols = prob.attributes().getCols();
byte[] origCType = new byte[cols];
prob.getColType(origCType, 0, cols - 1);
// If branching decisions are performed in the original space
// then we must be sure they can be translated to the presolved
// space, hence we must disable dual reductions in this case.
// And we also turn off dual reductions when branching in the
// presolved, space because they reduce this problem to an empty
// problem.
prob.controls().setMIPDualReductions(0);
// Solve the LP relaxation.
// Note that this will change the active model to the presolved
// model. So after this call to mipOptimize() all queries for
// attributes etc. will refer to the presolved model.
System.out.println("Start solving problem " + model);
prob.mipOptimize("l");
prob.controls().setMIPLog(3);
// Change some controls to make the problem "harder" for the
// optimizer. This is to make sure we actually need to branch
// to solve the problem.
prob.controls().setHeurEmphasis(0);
prob.controls().setCutStrategy(CutStrategy.NONE);
// Register the branch callback.
// Depending on what the user chose, we have to register a
// different implementation of the callback.
BranchBase callback = null;
if (orig) {
// User requested branching in the original space. In that
// case restarts do not matter since the number and types of
// the columns in the original space will never change. So
// we ignore the 'restart' flag and only check whether we
// want parallel execution of callbacks or not.
if (mt) {
prob.controls().setMutexCallBacks(0);
callback = new BranchOriginalMT(prob, origCType);
}
else
callback = new BranchOriginal(prob, origCType);
}
else {
// We are asked to branch on the presolved model.
if (restart) {
// Restarts are allowed. We use the "safe" implementation
// of the callback that re-allocates arrays at every
// invocation. Since it does not cache any arrays, that
// implementation is safe for parallel callbacks as well.
if (mt)
prob.controls().setMutexCallBacks(0);
callback = new BranchPresolvedUncached(prob);
}
else {
// No restarts. If there were restarts then the column count
// and type could change. Thus we would not be able to cache
// that information and would have to re-query it at each
// callback invocation. That would slow down things a lot.
prob.controls().setMipRestart(0);
if (mt) {
prob.controls().setMutexCallBacks(0);
callback = new BranchPresolvedMT(prob);
}
else
callback = new BranchPresolved(prob);
}
}
prob.addChangeBranchObjectListener(callback);
prob.mipOptimize("");
System.out.println("Solving complete.\n");
}
// Note: We don't catch XPRSexception or XPRSprobException here.
// Instead we rely on the JVM's default handling of an exception
// being thrown from main() which should terminate the program
// ungracefully without an appropriate message.
}
}
|