Initializing help system before first use

Wagon - MIP start solution heuristic


Type: Loading problem
Rating: 3 (intermediate)
Description: Load balancing of train wagons. A heuristic solution obtained via a Longest processing time heuristic is loaded as start solution into the MIP solver.
File(s): Wagon.java


Wagon.java
// (c) 2023-2024 Fair Isaac Corporation

import static com.dashoptimization.objects.Utils.sum;

import java.util.Arrays;

import com.dashoptimization.ColumnType;
import com.dashoptimization.XPRSconstants;
import com.dashoptimization.XPRSenumerations;
import com.dashoptimization.objects.Variable;
import com.dashoptimization.objects.XpressProblem;

/**
 * Load balancing of train wagons. econd version, using heuristic solution as
 * start solution for MIP.
 */
public class Wagon {

    /* Box weights */
    private static final int[] WEIGHT = { 34, 6, 8, 17, 16, 5, 13, 21, 25, 31, 14, 13, 33, 9, 25, 25 };
    private static final int NBOXES = WEIGHT.length; /* Number of boxes */
    private static final int NWAGONS = 3; /* Number of wagons */
    private static final int WMAX = 100; /* Weight limit of the wagons */
    private static final int[] HeurSol = new int[NBOXES]; /* Heuristic solution: for each box */

    /* Variables */
    private static Variable[][] load;
    private static Variable maxweight;

    private static void model(XpressProblem p) {
        // Create load[box,wagon] (binary)
        load = p.addVariables(NBOXES, NWAGONS).withType(ColumnType.Binary)
                .withName((b, w) -> String.format("load_%d_%d", b + 1, w + 1)).toArray();

        // Create maxweight (continuous with lb=ceil((sum(b in BOXES) WEIGHT(b))/NBOXES)
        maxweight = p.addVariable(Math.ceil(Arrays.stream(WEIGHT).sum() / (double) NBOXES), XPRSconstants.PLUSINFINITY,
                ColumnType.Continuous, "maxweight");

        // Every box into one wagon: forall(b in BOXES) sum(w in WAGONS) load(b,w) = 1
        p.addConstraints(NBOXES, b -> sum(load[b]).eq(1.0));

        // Limit the weight loaded into every wagon: forall(w in WAGONS) sum(b in BOXES)
        // WEIGHT(b)*load(b,w) <= maxweight
        p.addConstraints(NWAGONS,
                w -> sum(NBOXES, b -> load[b][w].mul(WEIGHT[b])).leq(maxweight).setName(String.format("wmilit_%d", w)));

        // Minimize maxweight
        p.setObjective(maxweight, XPRSenumerations.ObjSense.MINIMIZE);
        p.writeProb("Wagon.lp", "l");
    }

    /**
     * LPT (Longest processing time) heuristic: One at a time, place the heaviest
     * unassigned box onto the wagon with the least load
     *
     * @return A dense vector with the heuristic solution.
     */
    private static double heuristic() {
        Integer[] ORDERW = new Integer[NBOXES]; // Box indices sorted in decreasing weight order
        int[] CurNum = new int[NWAGONS]; // For each wagon w, this is the number of boxes currently loaded
        int[] CurWeight = new int[NWAGONS]; // For each wagon w, this is the current weight, i.e. the sum of weights of
                                            // loaded boxes
        int[][] Load = new int[NWAGONS][NBOXES]; // Load[w][i] (for i=0..CurNum[w]-1) contains the box index of the i-th
                                                 // box loaded on wagon w

        // Copy the box indices into array ORDERW and sort them in decreasing
        // order of box weights (the sorted indices are returned in array ORDERW)
        for (int b = 0; b < NBOXES; b++)
            ORDERW[b] = b;
        Arrays.sort(ORDERW, (i1, i2) -> ((Integer) WEIGHT[i2]).compareTo(WEIGHT[i1]));

        // Distribute the loads to the wagons using the LPT heuristic
        for (int b = 0; b < NBOXES; b++) {
            int v = 0; /* Find wagon v with the smallest load */
            for (int w = 0; w < NWAGONS; w++)
                if (CurWeight[w] <= CurWeight[v])
                    v = w;
            Load[v][CurNum[v]] = ORDERW[b]; /* Add current box to wagon v */
            CurNum[v]++; /* Increase the counter of boxes on v */
            CurWeight[v] += WEIGHT[ORDERW[b]]; /* Update current weight of the wagon */
        }

        // Calculate the solution value
        double heurobj = 0; /* heuristic solution objective value (max wagon weight) */
        for (int w = 0; w < NWAGONS; w++)
            if (CurWeight[w] > heurobj)
                heurobj = CurWeight[w];

        // Solution printing
        System.out.printf("Heuristic solution:%n Max weight: %.0f\n", heurobj);

        for (int w = 0; w < NWAGONS; w++) {
            System.out.printf(" %d:", w + 1);
            for (int i = 0; i < CurNum[w]; i++)
                System.out.print(" " + (Load[w][i] + 1));
            System.out.printf(" (total weight: %d)\n", CurWeight[w]);
        }

        // Save the heuristic solution into the HeurSol array
        for (int w = 0; w < NWAGONS; w++)
            for (int i = 0; i < CurNum[w]; i++)
                HeurSol[Load[w][i]] = w;
        return heurobj;
    }

    private static void optimization(XpressProblem p) {
        // Get the solution from the heuristic solution we have found
        // Send the solution to the optimizer
        Variable[] heurmipvar = new Variable[NBOXES];
        double[] heurmipsol = new double[NBOXES];
        for (int b = 0; b < NBOXES; b++) {
            heurmipvar[b] = load[b][HeurSol[b]];
            heurmipsol[b] = 1.0;
        }
        p.addMipSol(heurmipsol, heurmipvar, "heuristic");
        p.optimize();
        if (p.attributes().getSolStatus() != XPRSenumerations.SolStatus.OPTIMAL
                && p.attributes().getSolStatus() != XPRSenumerations.SolStatus.FEASIBLE)
            throw new RuntimeException("failed to optimize with status " + p.attributes().getSolStatus());

        System.out.println(String.format(
                "Problem status:\n\tSolve status: %s\n\tLP status: %s\n\tMIP status: %s\n\tSol status: %s",
                p.attributes().getSolveStatus(), p.attributes().getLPStatus(), p.attributes().getMIPStatus(),
                p.attributes().getSolStatus()));

        // An integer solution has been found
        if (p.attributes().getMIPStatus() == XPRSenumerations.MIPStatus.OPTIMAL
                || p.attributes().getMIPStatus() == XPRSenumerations.MIPStatus.SOLUTION) {
            double[] sol = p.getSolution();
            System.out.printf("Optimal solution:\n Max weight: %.0f%n", p.attributes().getObjVal());
            for (int w = 0; w < NWAGONS; w++) {
                double tot_weight = 0.;
                System.out.printf(" %d:", w + 1);
                for (int b = 0; b < NBOXES; b++)
                    if (load[b][w].getValue(sol) * WEIGHT[b] > .5) {
                        System.out.printf(" %.0f", load[b][w].getValue(sol) * WEIGHT[b]);
                        tot_weight += load[b][w].getValue(sol) * WEIGHT[b];
                    }
                System.out.printf(" (total weight: %.0f)%n", tot_weight);
            }
        }
    }

    public static void main(String[] args) {
        if (heuristic() <= WMAX) {
            System.out.println("Heuristic solution fits capacity limits");
        } else {
            try (XpressProblem prob = new XpressProblem()) {
                /* Suppress outputs */
                model(prob);
                optimization(prob);
            }
        }
    }

}

© 2001-2024 Fair Isaac Corporation. All rights reserved. This documentation is the property of Fair Isaac Corporation (“FICO”). Receipt or possession of this documentation does not convey rights to disclose, reproduce, make derivative works, use, or allow others to use it except solely for internal evaluation purposes to determine whether to purchase a license to the software described in this documentation, or as otherwise set forth in a written software license agreement between you and FICO (or a FICO affiliate). Use of this documentation and the software described in it must conform strictly to the foregoing permitted uses, and no other use is permitted.