// (c) 2024-2025 Fair Isaac Corporation #include #include // 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 width = { 17, 21, 22.5, 24, 29,5 }; // Possible widths const std::vector demand = { 150, 96, 48, 108, 227 }; // Demand per width XpressProblem& prob; std::vector patternVars; // Nr of rolls made with vPattern[i] std::vector 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 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 c, std::vector a, double R, std::vector d, std::vector& xbest) { XpressProblem subProb; std::vector 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 sol = subProb.getSolution(); // Return values for (int i=0; i> patterns(NWIDTHS, std::vector(nrInitialPatterns)); for (int w=0; w 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(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 subProbSolX(NWIDTHS); std::cout << "Generating columns" << std::endl; for (int c=0; c rowstat(prob.attributes.getRows()); std::vector 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); // 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; } }