| |||||||||||
Cutstk - Column generation for a cutting stock problem Description This example features iteratively adding new variables,
basis in/output and working with subproblems. The column
(=cutting pattern)
generation algorithm is implemented as a loop over the
root node of the MIP problem.
Source Files By clicking on a file name, a preview is opened at the bottom of this page.
CuttingStock.cpp #include <xpress.hpp> #include <stdexcept> // For throwing exceptions using namespace xpress; using namespace xpress::objects; using xpress::objects::utils::scalarProduct; /** * Cutting stock problem. The problem is solved by column (= cutting pattern) * generation heuristic looping over the root node. */ class CuttingStock { public: const std::vector<double> width = { 17, 21, 22.5, 24, 29.5 }; // Possible widths const std::vector<int> demand = { 150, 96, 48, 108, 227 }; // Demand per width XpressProblem& prob; std::vector<Variable> patternVars; // Nr of rolls made with vPattern[i] std::vector<Inequality> demandConstraints; // 5 demand constraints LinTermMap objExpr; // Objective function CuttingStock(XpressProblem& prob) : prob(prob) {}; // Class constructor void printSolution(); // To print the solution to console void modelCutStock(); // Initial problem definition with few variables void solveCutStock(); // Iteratively adds variables (i.e. columns) until optimality private: const int NWIDTHS = 5; // Number of unique widths to produce const int MAXWIDTH = 94; // Length of each roll / raw material const int MAXCOL = 10; // Stop after generating 10 extra columns const double EPS = 1e-6; // Epsilon, for some inprecision tolerance }; void CuttingStock::printSolution() { std::vector<double> sol = prob.getSolution(); std::cout << "Objective Value: " << prob.attributes.getObjVal() << std::endl; std::cout << "Variables:" << std::endl << "\t"; std::ios_base::fmtflags oldFlags(std::cout.flags()); std::cout << std::fixed << std::setprecision(2); for (Variable v : prob.getVariables()) { std::cout << "[" << v.getName() << ": " << v.getValue(sol) << "]"; } std::cout.flags(oldFlags); std::cout << std::endl; } /* ************************************************************************ */ /* Integer Knapsack Algorithm for solving the integer knapsack problem */ /* z = max{cx : ax <= R, x <= d, x in Z^N} */ /* */ /* Input data: */ /* N: Number of item types */ /* c[i]: Unit profit of item type i, i=1..n */ /* a[i]: Unit resource use of item type i, i=1..n */ /* R: Total resource available */ /* d[i]: Demand for item type i, i=1..n */ /* Return values: */ /* xbest[i]: Number of items of type i used in optimal solution */ /* zbest: Value of optimal solution */ /* ************************************************************************ */ double solveKnapsack(int N, std::vector<double> c, std::vector<double> a, double R, std::vector<int> d, std::vector<int>& xbest) { XpressProblem subProb; std::vector<Variable> x = subProb.addVariables(N) // Variable for each unique width to produce .withType(ColumnType::Integer) .withName("x_%d") .withUB([&](int i) { return double(d[i]); }) .toArray(); subProb.addConstraint(scalarProduct(x, a) <= R); subProb.setObjective(scalarProduct(x, c), ObjSense::Maximize); subProb.optimize(); // Check the solution status if (subProb.attributes.getSolStatus() != SolStatus::Optimal && subProb.attributes.getSolStatus() != SolStatus::Feasible) { std::ostringstream oss; oss << subProb.attributes.getSolStatus(); // Convert xpress::SolStatus to String throw std::runtime_error("Optimization of subproblem failed with status " + oss.str()); } double zbest = subProb.attributes.getObjVal(); std::vector<double> sol = subProb.getSolution(); // Return values for (int i=0; i<N; i++) xbest[i] = int (std::round(x[i].getValue(sol))); return zbest; } // Function to initially model the cutting stock problem void CuttingStock::modelCutStock() { // Generate NWIDTHS initial basic patterns. The basic patterns only use one unique width // i.e. (1,0,0,0,0), (0,2,0,0,0), (0,0,6,0,0), etc. int nrInitialPatterns = NWIDTHS; std::vector<std::vector<int>> patterns(NWIDTHS, std::vector<int>(nrInitialPatterns)); for (int w=0; w<NWIDTHS; w++) { // Here we generate the initial trivial patterns. patterns[w][w] = int(std::floor(double(MAXWIDTH) / width[w])); } patternVars = prob.addVariables(nrInitialPatterns) .withType(ColumnType::Integer) .withLB(0) .withUB([&](int p) {return std::ceil( double(demand[p]) / patterns[p][p] ); }) .withName([&](int p) {return xpress::format("pat_%d", p + 1); }) .toArray(); // Minimize total number of rolls std::for_each(patternVars.begin(), patternVars.end(), [&](Variable pattern) { objExpr.addTerm(pattern); }); prob.setObjective(objExpr); // Satisfy the demand per width; // Sum of patterns producing width[w] must exceed demand[w]. The patterns p producing the // width[w] are exactly those p for which patterns[w][p] is nonzero, so we can use scalarProduct xpress::Iterable<Inequality> constraintsIter = prob.addConstraints(NWIDTHS, [&](int w) { return (scalarProduct(patternVars, patterns[w]) >= demand[w]).setName("Demand_" + std::to_string(w)); }); // Save the Inequality objects for access later demandConstraints = std::vector<Inequality>(constraintsIter.begin(), constraintsIter.end()); } /* ************************************************************************ */ /* Column generation loop at the top node: */ /* solve the LP-relaxation and save the basis */ /* generate new column(s) (=cutting pattern) */ /* add the new column to the master problem */ /* load the modified problem and load the saved basis */ /* ************************************************************************ */ void CuttingStock::solveCutStock() { // Variable we will contain the return value of the knapsack subproblem solution's x-variables std::vector<int> subProbSolX(NWIDTHS); std::cout << "Generating columns" << std::endl; for (int c=0; c<MAXCOL; c++) { // Solve the LP-relaxation of the problem. (This is so we can get access to dual values, // as for MIPs dual values do not exist) prob.lpOptimize(); // Check the solution status if (prob.attributes.getSolStatus() != SolStatus::Optimal && prob.attributes.getSolStatus() != SolStatus::Feasible) { std::ostringstream oss; oss << prob.attributes.getSolStatus(); // Convert xpress::SolStatus to String throw std::runtime_error("Optimization failed with status " + oss.str()); } // Save the current basis (so we can reload it for faster resolve) std::vector<int> rowstat(prob.attributes.getRows()); std::vector<int> colstat(prob.attributes.getCols()); prob.getBasis(rowstat, colstat); // Solve subproblem double z = solveKnapsack(NWIDTHS, prob.getDuals(demandConstraints), width, MAXWIDTH, demand, subProbSolX); if (z < 1 + EPS) { std::cout << " No profitable column found." << std::endl; break; } else { std::cout << " New pattern found with marginal cost " << z - 1 << std::endl; // Create a new variable for this pattern: Variable new_var = prob.addVariable(ColumnType::Integer, xpress::format("pat_%d", prob.attributes.getCols() + 1)); // Add the basis-status of the new variable to colstat. The status=0 means non-basic // The new variable is non-basic since it has value 0 (i.e. pattern is unused so far) colstat.push_back(0); // Add new var. to the objective new_var.chgObj(1.0); // Add new var. to demand constraints for (int i = 0; i < NWIDTHS; ++i) { if (subProbSolX[i] > 0) { demandConstraints[i].chgCoef(new_var, subProbSolX[i]); } } // Load back the previous optimal basis for faster resolve prob.loadBasis(rowstat, colstat); } } // Write the problem to .lp file prob.writeProb("CuttingStock.lp", "l"); // After adding all the columns to the LP-relaxation, now solve the MIP std::cout << "Solving the final MIP problem" << std::endl; prob.mipOptimize(); // Check the solution status if (prob.attributes.getSolStatus() != SolStatus::Optimal && prob.attributes.getSolStatus() != SolStatus::Feasible) { std::ostringstream oss; oss << prob.attributes.getSolStatus(); // Convert xpress::SolStatus to String throw std::runtime_error("Optimization failed with status " + oss.str()); } } int main() { try { // Create a problem instance XpressProblem prob; // Output all messages prob.callbacks.addMessageCallback(XpressProblem::console); prob.controls.setOutputLog(0); // Disable outputlog // Model, Solve, and Print the cutting stock problem CuttingStock cutStockProb = CuttingStock(prob); cutStockProb.modelCutStock(); cutStockProb.solveCutStock(); cutStockProb.printSolution(); return 0; } catch (std::exception& e) { std::cout << "Exception: " << e.what() << std::endl; return -1; } } | |||||||||||
© Copyright 2024 Fair Isaac Corporation. |