FICO
FICO Xpress Optimization Examples Repository
FICO Optimization Community FICO Xpress Optimization Home
Back to examples browserPrevious exampleNext example

Adding MIP solutions to the Optimizer

Description
At each node of the tree search a variable fixing heuristic is applied to a copy of the problem. If an integer solution is found for the modified (sub)problem then this solution is added to the original problem.

addmipsol_java.zip[download all files]

Source Files
By clicking on a file name, a preview is opened at the bottom of this page.
AddMipSol.java[download]

Data Files





AddMipSol.java

/***********************************************************************
   Xpress Optimizer Examples
   =========================

   file AddMipSol.java
   ````````````
   Apply an LP rounding heuristic at each node and inject feasible
   solutions found.

   (c) 2020-2024 Fair Isaac Corporation
***********************************************************************/

import com.dashoptimization.AbstractOptNodeListener;
import com.dashoptimization.DefaultMessageListener;
import com.dashoptimization.XPRS;
import com.dashoptimization.XPRSoptNodeListener;
import com.dashoptimization.XPRSprob;
import static com.dashoptimization.XPRSenumerations.CutStrategy;

public class AddMipSol {

    /** Callback that runs a simple rounding heuristic at each node.
     *
     * The callback fixes all integer fractional variables to their rounded
     * values and solves the resulting problem. If that leads to a feasible
     * solution then that solution is injected back into the solver as a
     * new incumbent.
     */
    private static final class OptNode extends AbstractOptNodeListener {

        /** Problem name. */
        final String probName;
        /** Number of columns. */
        final int nCol;
        /** Number of MIP entities. */
        final int nMipEnt;
        /** Information about MIP entities. */
        final XPRSprob.MIPEntityInfo mipEntInfo;

        public OptNode(XPRSprob prob) {
            // Capture some global statistics that will be needed in the
            // heuristic.
            probName = prob.getProbName();
            nCol = prob.attributes().getCols();
            nMipEnt = prob.attributes().getMIPEnts();
            mipEntInfo = prob.getDiscreteCols();
        }

        /** Callback main method.
         * This is the method that is invoked by the solver.
         */
        public int optNodeEvent(XPRSprob prob, int feas) {
            // Only run once per node (since adding a solution will call
            // the callback to be fired again)
            if (prob.attributes().getCallbackCount_OptNode() > 1)
                return feas;

            // Only run the search for solutions with small numbers of
            // integer infeasibilities */
            if (prob.attributes().getMIPInfeas() > 0.25 * nMipEnt)
                return feas;

            fixedSearch(prob);

            return feas;
        }

        /** Run the heuristic that fixes all fractional integer variables
         * to their rounded values.
         */
        private void fixedSearch(XPRSprob parent) {
            // Create resources we need
            int[] bndInd = new int[nCol];
            double[] bndVal = new double[nCol];
            byte[] bndType = new byte[nCol];
            Thread me = Thread.currentThread(); // to get unique names

            // Create a problem containing the original problem
            try (XPRSprob prob = new XPRSprob(null)) {
                prob.copyProb(parent);
                prob.postSolve();

                // Reset the controls of the problem.
                prob.setDefaults();

                // Rename the problem so we don't get collisions with
                // temporary files produced by the optimizer
                prob.setProbname(probName + "_" + me);

                // Output all messages.
                prob.addMessageListener(DefaultMessageListener::console);

                // Get information from the solution at the current node
                // of 'parent' problem */
                boolean hasMipIncumbent = parent.attributes().getMIPSols() != 0;
                double incumbentObj = parent.attributes().getMIPObjVal();
                double[] x = parent.getCallbackSolution();

                // Initialise bound counter
                int nBnd = 0;
                double mipTol = parent.controls().getMIPTol();

                // Go through MIP entities
                for (int i = 0; i < nMipEnt; i++) {
                    // Test whether each is a binary or integer variable
                    if (mipEntInfo.coltype[i] == 'B' || mipEntInfo.coltype[i] == 'I') {
                        int j = mipEntInfo.colind[i];

                        // Fix variables that are fractional
                        double rounded = Math.round(x[j]);
                        if (Math.abs(rounded - x[j]) <= mipTol) {
                            continue;
                        }
                        bndInd[nBnd] = j;
                        bndVal[nBnd] = rounded;
                        bndType[nBnd] = 'B';
                        nBnd++;
                    }
                }

                // Instruct Optimizer to change the bounds
                prob.chgBounds(nBnd, bndInd, bndType, bndVal);
                System.out.println(me + ": After MIP search, " + nBnd +
                                   " binary and integer variables were fixed");

                // Ensure we only do a small search in the fixed problem and
                // use the current incumbent objective from the main problem
                prob.controls().setMaxNode(100);
                prob.controls().setMaxMIPSol(1);
                if (hasMipIncumbent) {
                    // Set a cut-off to help the fixed search
                    prob.controls().setMIPAbsCutoff(incumbentObj);
                }
                prob.controls().setMIPLog(3);

                // Read the basis for the LP relaxation to reduce run time
                prob.readBasis(probName, "");

                // Run the optimizer
                prob.mipOptimize();

                // If we found a solution then load it into the main problem
                if (prob.attributes().getMIPSols() > 0) {
                    x = prob.getSolution();
                    parent.addMipSol(nCol, x, null, null);
                }
            }
        }
    }

    public static void main(String[] args) {
        // Get problem name from command line or use a default problem.
        String model = args.length == 0 ? "../data/addmipsol" : args[0];

        try (XPRSprob prob = new XPRSprob(null)) {
            prob.readProb(model, "");
            prob.addMessageListener(DefaultMessageListener::console);
            // Register the callback that will perform heuristic search.
            prob.addOptNodeListener(new OptNode(prob));

            // Make the problem difficult for the optimizer to solve.
            prob.controls().setCutStrategy(CutStrategy.NONE);
            prob.controls().setHeurEmphasis(0);
            prob.controls().setSBBest(0);
            prob.controls().setMIPThreads(20);

            // Log every node in the branch-and-bound search.
            prob.controls().setMIPLog(3);

            // Solve the root node relaxation and write out the basis
            // (to reduce solve time of the fixed problems) */
            prob.mipOptimize("l");
            prob.writeBasis("", "");

            // Run the MIP search
            prob.mipOptimize("");

            System.out.printf("Optimal objective value: %f%n",
                              prob.attributes().getMIPObjVal());

        }
    }
}

Back to examples browserPrevious exampleNext example