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

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.

cutstk_dnet.zip[download all files]

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





CuttingStock.cs

// (c) 2023-2024 Fair Isaac Corporation

using System;
using System.Linq;
using Optimizer;
using Optimizer.Objects;
using static Optimizer.Objects.Utils;
using static Optimizer.Objects.ConstantExpression;

namespace XpressExamples
{
    /// <summary>Cutting stock problem</summary>
    /// <remarks>
    ///   Solved by column (= cuttingpattern) generation heuristic looping
    ///   over the root node.
    /// </remarks>
    internal class CuttingStock
    {
        const int NWIDTHS = 5;
        const int MAXWIDTH = 94;
        const double EPS = 1e-6;
        const int MAXCOL = 10;

        /****Data****/
        static readonly double[] width = { 17, 21, 22.5, 24, 29.5 }; /* Possible widths */
        static readonly int[] demand = { 150, 96, 48, 108, 227 };  /* Demand per width */

        /****Shared Variables****/
        static Variable[] vPattern; /* Rolls per pattern */
        static Inequality[] cDemand; /* Demand constraints */
        static LinTermMap eObj; /* Objective function */

        private static void PrintProblemSolution(XpressProblem prob)
        {
            double[] sol = prob.GetSolution();
            Console.WriteLine("Objective: " + prob.ObjVal);
            Console.WriteLine("Variables:");
            Console.Write("\t");
            foreach (Variable v in prob.GetVariables())
            {
                Console.Write("[{0}: {1:f2}] ", v.GetName(), v.GetValue(sol));
            }
            Console.WriteLine();
        }


        /**************************************************************************/
        /* 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                                  */
        /**************************************************************************/
        private static double SolveKnapsack(int N, double[] c, double[] a, double R, int[] d, int[] xbest)
        {
            using (XpressProblem prob = new XpressProblem())
            {
                Variable[] x = prob.AddVariables(N)
                    .WithType(ColumnType.Integer)
                    .WithName(i => $"x_{i}")
                    .WithLB(i => d[i])
                    .ToArray();

                prob.AddConstraint(
                    ScalarProduct(x, a) <= R
                );

                prob.SetObjective(
                    ScalarProduct(x, c),
                    Optimizer.ObjSense.Maximize
                );

                prob.MipOptimize();
                double zbest = prob.ObjVal;
                double[] sol = prob.GetSolution();
                xbest = sol.Select(value => (int)Math.Round(value)).ToArray();

                return zbest;
            }
        }

        private static void ModCutStock(XpressProblem p)
        {
            // (Basic) cutting patterns
            int[,] patterns = new int[NWIDTHS, NWIDTHS];
            foreach (int j in Enumerable.Range(0, NWIDTHS))
            {
                patterns[j, j] = (int)Math.Floor((double)MAXWIDTH / width[j]);
            }

            vPattern = p.AddVariables(NWIDTHS)
                .WithUB(i => Math.Ceiling((double)demand[i] / patterns[i, i]))
                .WithName(i => $"pat_{i + 1}")
                .ToArray();

            // Minimize total number of rolls
            p.SetObjective(Sum(vPattern));


            // Satisfy the demand per width;
            // Sum of patterns must exceed demand
            cDemand = p.AddConstraints(NWIDTHS,
                    i => (Sum(NWIDTHS, j => vPattern[j] * patterns[i, j]) >= demand[i]).SetName($"Demand_{i}")).ToArray();
        }

        /**************************************************************************/
        /*  Column generation loop at the top node:                               */
        /*    solve the LP and save the basis                                     */
        /*    get the solution values                                             */
        /*    generate new column(s) (=cutting pattern)                           */
        /*    load the modified problem and load the saved basis                  */
        /**************************************************************************/
        private static void SolveCutStock(XpressProblem p)
        {
            // Basis information
            int[] rowstat = new int[p.Rows];
            int[] colstat = new int[p.Cols];
            int[] x = new int[NWIDTHS];
            long starttime = Environment.TickCount64;

            foreach (int npass in Enumerable.Range(0, MAXCOL))
            {
                p.LpOptimize();                // Solve the LP
                if (p.SolStatus != Optimizer.SolStatus.Optimal)
                    throw new Exception("failed to optimize with status " + p.SolStatus);
                p.GetBasis(rowstat, colstat);  // Save the current basis

                // solve integer knpsack problem
                double z = SolveKnapsack(NWIDTHS, p.GetDuals(cDemand), width, MAXWIDTH, demand, x);

                Console.WriteLine("Iteration {0:d}, {1:f2} sec",
                    npass + 1, (Environment.TickCount64 - starttime) / 1000.0
                );

                if (z < 1 + EPS)
                {
                    Console.WriteLine("No profitable column found.");
                    Console.WriteLine();
                }
                else
                {
                    Console.WriteLine("New pattern found with marginal cost {0}", z - 1);
                    // Create a new variable for this pattern:
                    Variable v = p.AddVariable(
                        0.0,
                        Double.PositiveInfinity,
                        ColumnType.Integer,
                        String.Format("pat_{0:d}", p.Cols + 1)
                    );
                    // Add new variable to the objective
                    v.ChgObj(1.0);
                    // Add new variable to demand constraints
                    double ub = 0.0;
                    for (int i = 0; i < NWIDTHS; ++i)
                    {
                        if (x[i] > 0)
                        {
                            cDemand[i].ChgCoef(v, x[i]);
                            ub = Math.Max(ub, Math.Ceiling(demand[i] / (double)x[i]));
                        }
                    }
                    // Change the upper bound on the new variable
                    v.SetUB(ub);
                    p.LoadBasis(rowstat, colstat);
                }
            }

            p.MipOptimize();
            if (p.SolStatus != Optimizer.SolStatus.Optimal)
                throw new Exception("failed to optimize with status " + p.SolStatus);
            PrintProblemSolution(p);
        }

        public static void Main(string[] args)
        {
            using (XpressProblem prob = new XpressProblem())
            {
                // Output all messages.
                prob.callbacks.AddMessageCallback(DefaultMessageListener.Console);
                prob.OutputLog = 0;
                prob.MIPLog = 0;

                ModCutStock(prob);
                SolveCutStock(prob);
            }
        }
    }
}

Back to examples browserPrevious exampleNext example