![]() | |||||||||||||
| |||||||||||||
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.
Source Files By clicking on a file name, a preview is opened at the bottom of this page.
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; }
| |||||||||||||
© Copyright 2025 Fair Isaac Corporation. |