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

Sangraal - Scheduling problem with indicator constraints

Description
When the Sangraal (Holy Grail) is almost won the hero arrives at a castle where he finds 8 imprisoned knights. He is facing the task to bring the largest possible number of knights for the arrival of the Sangraal in twenty minutes' time. The time required for freeing a knight depends on his state of binding. A freed knight then needs a given amount of time to wash and recover himself physically. For every knight, the durations of freeing and preparing are given.

sangraal_cpp.zip[download all files]

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





Sangraalind.cpp

// (c) 2024-2024 Fair Isaac Corporation

/**
 * Holy Grail. When the Sangraal (Holy Grail) is almost won the hero arrives at
 * a castle where he finds 8 imprisoned knights. He is facing the task to bring
 * the largest possible number of knights for the arrival of the Sangraal in
 * twenty minutes' time. The time required for freeing a knight depends on his
 * state of binding. A freed knight then needs a given amount of time to wash
 * and recover himself physically.
 *
 * Model formulation using indicator constraints.
 *
 * Description and original model by M. Chlond:
 * http://ite.pubs.informs.org/Vol4No3/Chlond
 */

#include <xpress.hpp>

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

/** A knight. */
struct Knight {
  /** The name of the knight. */
  std::string const name;
  /** How long it takes to free the knight. */
  double const freeTime;
  /** How long it takes the knight to wash and prepare himself. */
  double const washTime;

  Knight(std::string const &name, double freeTime, double washTime)
      : name(name), freeTime(freeTime), washTime(washTime) {}
};

/** The knights used in this example. */
Knight const knightArray[] = {
    Knight{"Agravain", 1, 15}, Knight{"Bors", 1, 5},
    Knight{"Caradoc", 2, 15},  Knight{"Dagonet", 2, 5},
    Knight{"Ector", 3, 10},    Knight{"Feirefiz", 4, 15},
    Knight{"Gareth", 5, 10},   Knight{"Harry", 6, 5}};
/** The number of nights in this example. */
int const knightCount = sizeof(knightArray) / sizeof(knightArray[0]);

/** The resource constraints used in this example. */
double const maxTime = 20.0;

int main() {
  XpressProblem prob;

  // Output all messages.
  prob.callbacks.addMessageCallback(XpressProblem::console);

  /**** VARIABLES ****/

  // Whether to free knight i as the j-th knight
  auto order = prob.addVariables(knightCount, knightCount)
                   .withName([](auto i, auto j) {
                     return xpress::format("order_%d_%d", i, j);
                   })
                   .withType(ColumnType::Binary)
                   .toArray();

  // Whether the knight who was freed as the i-th knight will be ready in time
  auto onTime =
      prob.addVariables(knightCount)
          .withName([](auto i) { return xpress::format("onTime_%d", i); })
          .withType(ColumnType::Binary)
          .toArray();

  // At what time the i-th freed knight will be ready
  auto readyTime =
      prob.addVariables(knightCount)
          .withName([](auto i) { return xpress::format("readyTime_%d", i); })
          .toArray();

  // Maximize the number of knights that are ready after 20 minutes
  Expression totalReady = sum(onTime);

  prob.setObjective(totalReady, ObjSense::Maximize);

  // Exactly one knight should be freed as the i-th knight
  prob.addConstraints(knightCount, [&](auto i) {
    return sum(knightCount, [&](auto j) { return order[i][j]; }) == 1.0;
  });

  // Each knight should be freed exactly once
  prob.addConstraints(knightCount, [&](auto i) {
    return sum(knightCount, [&](auto j) { return order[j][i]; }) == 1.0;
  });

  // The time each knight is ready, computed as the sum of times it took to free
  // all
  // previous knights, plus the time it took to free this knight plus the time
  // it takes the knight to wash and get ready

  // loop over all positions
  for (int p = 0; p < knightCount; p++) {
    LinExpression timeBeforeFree = LinExpression::create();
    // loop over all knights
    for (int k = 0; k < knightCount; k++) {
      // loop over all positions before the current one
      for (int q = 0; q < p; q++) {
        // If this knight was freed before the current position, add the time it
        // took to free them
        timeBeforeFree.addTerm(order[k][q] * knightArray[k].freeTime);
      }
      // if knight k was freed in this position, add the time it took to free
      // them and for them to prepare
      timeBeforeFree.addTerm(
          order[k][p] * (knightArray[k].freeTime + knightArray[k].washTime));
    }
    // add the actual constraint
    prob.addConstraint(timeBeforeFree == readyTime[p]);
  }

  // The i-th freed knight will be ready iff they were ready by time 20
  for (int p = 0; p < knightCount; p++) {
    Inequality isReady = prob.addConstraint(readyTime[p] <= maxTime);
    prob.setIndicator(onTime[p], true, isReady);
  }

  // Dump the problem to disk so that we can inspect it.
  prob.writeProb("sangraalind.lp", "l");

  // Solve
  prob.optimize();
  if (prob.attributes.getSolStatus() != SolStatus::Optimal &&
      prob.attributes.getSolStatus() != SolStatus::Feasible)
    throw std::runtime_error("optimization failed with status " +
                             to_string(prob.attributes.getSolStatus()));
  auto sol = prob.getSolution();

  // Print the solution
  std::cout << static_cast<int>(round(prob.attributes.getObjVal()))
            << " knights ready in time:" << std::endl;
  for (int p = 0; p < knightCount; p++) {
    for (int k = 0; k < knightCount; k++) {
      if (order[k][p].getValue(sol) > 0.5) {
        std::string pos;
        if (p == 0) {
          pos = "1st";
        } else if (p == 1) {
          pos = "2nd";
        } else if (p == 2) {
          pos = "3rd";
        } else {
          pos = xpress::format("%dth", p + 1);
        }
        std::cout << pos << " freed knight: " << knightArray[k].name
                  << " ready at time " << readyTime[p].getValue(sol)
                  << std::endl;
      }
    }
  }

  return 0;
}

Back to examples browserPrevious exampleNext example