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

Wagon - MIP start solution heuristic

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.


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





Wagon.cpp

// (c) 2024-2024 Fair Isaac Corporation

// Load balancing of train wagons. Second version, using heuristic solution as
// start solution for MIP.

#include <iostream>
#include <numeric>
#include <vector>
#include <xpress.hpp>

using namespace xpress;
using namespace xpress::objects;
using xpress::objects::utils::sum;

// Problem Data:
/** Box weights */
static int const WEIGHT[] = {34, 6,  8,  17, 16, 5, 13, 21,
                             25, 31, 14, 13, 33, 9, 25, 25};
/** Number of boxes. */
static int const NBOXES = sizeof(WEIGHT) / sizeof(WEIGHT[0]);
/** Number of wagons. */
static int const NWAGONS = 3;
/** Weight limit of the wagons. */
static int const WMAX = 100;

/** Create the optimization model in P.
 * @param p          The Xpress solver instance in which the problem is
 *                   created.
 * @param load       Where to store the load variables in the model.
 */
void model(XpressProblem &p, std::vector<std::vector<Variable>> &load) {
  // Create load[box,wagon] (binary)
  load = p.addVariables(NBOXES, NWAGONS)
             .withType(ColumnType::Binary)
             .withName([](auto b, auto w) {
               return xpress::format("load_%d_%d", b + 1, w + 1);
             })
             .toArray();

  // Create maxWeight, continuous with lb=ceil((sum(b in BOXES)
  // WEIGHT(b))/NBOXES)
  double lb = 0.0;
  for (int i = 0; i < NBOXES; ++i)
    lb += WEIGHT[i];
  lb /= static_cast<double>(NBOXES);
  Variable maxWeight =
      p.addVariable(lb, XPRS_PLUSINFINITY, ColumnType::Continuous, "maxWeight");

  // Every box into one wagon:
  //   forall(b in BOXES)
  //      sum(w in WAGONS) load(b,w) = 1
  p.addConstraints(NBOXES, [&](auto b) { return sum(load[b]) == 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, [&](auto w) {
    return sum(NBOXES, [&](auto b) { return load[b][w] * WEIGHT[b]; }) <=
           maxWeight;
  });

  // Minimize maxWeight
  p.setObjective(maxWeight, 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.
 * @param heurobj  The objective value of the heuristic solution is returned
 *                 here.
 * @return The heuristic solution vector.
 */
std::vector<int> heuristic(double &heurobj) {
  std::vector<int> orderW(
      NBOXES); // Box indices sorted in decreasing weight order
  std::vector<int> curNum(NWAGONS); // For each wagon w, this is the number of
                                    // boxes currently loaded
  std::vector<int> curWeight(NWAGONS); // For each wagon w, this is the current
                                       // weight, i.e. the sum of weights of
  // loaded boxes
  std::vector<std::vector<int>> load(
      NWAGONS, std::vector<int>(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)
  std::iota(orderW.begin(), orderW.end(), 0);
  std::sort(orderW.begin(), orderW.end(),
            [&](auto a, auto b) { return WEIGHT[b] - WEIGHT[a]; });

  // 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
  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
  std::cout << "Heuristic solution:" << std::endl
            << "Max weight: " << heurobj << std::endl;

  for (int w = 0; w < NWAGONS; w++) {
    std::cout << " " << (w + 1) << ": ";
    for (int i = 0; i < curNum[w]; i++)
      std::cout << " " << (load[w][i] + 1);
    std::cout << " (total weight: " << curWeight[w] << ")" << std::endl;
  }

  // Save the heuristic solution into the heurSol array
  std::vector<int> heurSol(NBOXES);
  for (int w = 0; w < NWAGONS; w++)
    for (int i = 0; i < curNum[w]; i++)
      heurSol[load[w][i]] = w;
  return heurSol;
}

static void optimization(XpressProblem &p,
                         std::vector<std::vector<Variable>> &load,
                         std::vector<int> const &heurSol) {
  // Get the solution from the heuristic solution we have found
  // Send the solution to the optimizer
  std::vector<Variable> heurmipvar(NBOXES);
  std::vector<double> heurmipsol(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() != SolStatus::Optimal &&
      p.attributes.getSolStatus() != SolStatus::Feasible)
    throw std::runtime_error("failed to optimize with status " +
                             to_string(p.attributes.getSolStatus()));

  std::cout << "Problem status:" << std::endl
            << "\tSolve status: " << to_string(p.attributes.getSolveStatus())
            << std::endl
            << "\tLP status: " << to_string(p.attributes.getLpStatus())
            << std::endl
            << "\tMIP status: " << to_string(p.attributes.getMipStatus())
            << std::endl
            << "\tSol status: " << to_string(p.attributes.getSolStatus())
            << std::endl;

  // An integer solution has been found
  if (p.attributes.getMipStatus() == MIPStatus::Optimal ||
      p.attributes.getMipStatus() == MIPStatus::Solution) {
    auto sol = p.getSolution();
    std::cout << "Optimal solution:" << std::endl
              << " Max weight: " << p.attributes.getObjVal() << std::endl;
    for (int w = 0; w < NWAGONS; w++) {
      double tot_weight = 0.;
      std::cout << " " << (w + 1) << ": ";
      for (int b = 0; b < NBOXES; b++)
        if (load[b][w].getValue(sol) * WEIGHT[b] > 0.5) {
          std::cout << " " << load[b][w].getValue(sol) * WEIGHT[b];
          tot_weight += load[b][w].getValue(sol) * WEIGHT[b];
        }
      std::cout << " (total weight: " << tot_weight << ")" << std::endl;
    }
  }
}

int main() {
  double heurobj;
  std::vector<int> heursol = heuristic(heurobj);
  if (heurobj <= WMAX) {
    std::cout << "Heuristic solution fits capacity limits" << std::endl;
  } else {
    XpressProblem prob;
    std::vector<std::vector<Variable>> load;
    model(prob, load);
    optimization(prob, load, heursol);
  }
  return 0;
}

Back to examples browserPrevious example