Extra Material: Cutting Stock
The cutting stock problem is familiar to anyone who has cut parts out of stock materials. In the one-dimensional case, the stock materials are available in predetermined lengths and prices. The task is to cut a specific list of parts from the stock materials. The problem is to determine which parts to cut from each piece of stock material to minimize cost. This problem applies broadly to commercial applications, including the allocation of non-physical resources like capital budgeting or resource allocation.
This notebook presents several models and solution algorithms for the cutting stock problem.
Preamble: Install Pyomo and a solver
This cell selects and verifies a global SOLVER for the notebook. In this notebook, we need both the MILO solver HiGHS and the MINLO solver couenne. We install the latter using the IDAES module and its extensions, which include the pre-compiled binary for couenne.
Problem formulation
Consider a set of available stock materials that can be cut to size to produce a set of finished parts. Each stock is characterized by a length , a cost per piece, and is available in unlimited quantity. A customer order is received to product a set of finished products . Each finished product is specified by a required number and length .
The cutting stock problem is to find a minimum cost solution to fulfill the customer order from the stock materials. The problem is illustrated is by data for an example given in the original paper by Gilmore and Gamory (1961).
Stocks
| stocks | length | cost |
|---|---|---|
| A | 5 | 6 |
| B | 6 | 7 |
| C | 9 | 10 |
Finished Parts
| finished parts | length | demand |
|---|---|---|
| S | 2 | 20 |
| M | 3 | 10 |
| L | 4 | 20 |
This information is represented in Python as nested dictionaries where the names for stocks and finished parts are used as indices.
Patterns
One approach to solving this problem is to create a list of all finished parts, a list of stocks for each length, and then use a set of binary decision variables to assign each finished product to a particular piece of stock. This approach will work well for a small problems, but the computational complexity scales much too rapidly with the size of the problem to be practical for business applications.
To address the issue of computational complexity, in 1961 Gilmore and Gamory introduced an additional data structure for the problem that is now referred to as "patterns". A pattern is a list of finished parts that can be cut from a particular stock item.
A pattern is specified by the stock assigned to the pattern and integers that specify how many finished parts of type are cut from stock . A pattern is feasible if
The function make_patterns defined below produces a partial list of feasible patterns for given sets of stocks and finished parts. Each pattern is represented as dictionary that specifies an associated stock item, and a dictionary of cuts that specify the finished parts cut from the stock. The algorithm is simple, it just considers every finished parts and stock items, then reports the number of parts that can be cut from stock item .
The function plot_patterns, defined below, displays a graphical depiction of the list of patterns.
Optimal cutting using known patterns
Given a list of patterns, the optimization problem is to compute how many copies of each pattern should be cut to meet the demand for finished parts at minimum cost.
Let the index denote the stock specified by pattern , and let denote the number pieces of stock is used. For a given list of patterns, the minimum cost optimization problem is a mixed integer linear optimization (MILO) subject to meeting demand constraints for each finished item.
The following cell is a Pyomo implementation of this optimization model.
The following function plot_nonzero_patterns is wrapper for plot_patterns that removes unused patterns from graphic, shows the number of times each pattern is used, and adds cost to the title.
Cutting Stock Problem: Bilinear reformulation
The cut_patterns model requires a known list of cutting patterns. This works well if the patterns comprising an optimal solution to the problem are known. But since they are not initially known, an optimization model is needed that simultaneously solves for an optimal patterns and the cutting list.
Let binary variable denote the assignment of stock to pattern , and let index a list of patterns. For sufficiently large , an optimal solution to the stock cutting problem is given by the model
Since there is no ordering of the patterns, without loss of generality an additional constraint can be added to reduce the symmetries present in the problem.
This is a challenging optimization problem with a cost objective that is bilinear with respect to the the decision variables and , and a set of constraints for the demand of finished parts that are bilinear in the decision variables and . Because the constraints are a lower bound on a positive sum of bilinear terms, a simple substitution to create rotated quadratic cones fails to produce a convex program.
The following model is a direct translation of the bilinear optimization model into Pyomo. A solution is attempted using a mixed-integer nonlinear optimization (MINLO) solver.
Pattern Generation: Bilinear Model
From limited testing, the bilinear model for the cutting stock problem appears to work well for small data sets, but does not scale well for larger problem instances, at least for the solvers included in the testing. This shouldn't be surprising given the non-convex nature of the problem, the exclusive use of integer and binary decision variables, and a high degree of symmetry in the model equations.
So rather than attempt to solve the full problem all at once, the following model assumes an initial list of patterns has been determined, perhaps using the make_patterns function defined above, then attempts to generate one more pattern that further reduces the objective function. The result remains a non-convex, bilinear optimization problem, but with fewer binary decision variables and at most one bilinear term in the objective and constraints.
The function generate_pattern_bilinear is a direct Pyomo implementation that uses a MINLO solver to create one additional feasible pattern that could be added to the list of known patterns.
Pattern Generation: Linear Dual
A common approach to pattern generation for stock cutting begins by relaxing the MILO optimization problem with known patterns. The integer variables are relaxed to non-negative reals.
Let be the dual variables associated with the demand constraints. A large positive value suggests a high value for including finished part in a new pattern. This motivates a set of dual optimization problems where the objective is to construct a new patterns that maximizes the the marginal value of each stock .
The pattern demonstrating the largest return is returned as a candidate to add the list of patterns.
The following cell compares the time required to generate a new pattern by the two methods for a somewhat larger data set.
A hybrid solution algorithm using pattern generation
The following cell incorporates the two methods of pattern generation into a hybrid algorithm to solve the cutting stock problem. The algorithm starts with with make_patterns to create an initial list of patterns, then uses the linear dual to adds patterns until no new patterns are found. In phase two of the algorithm, the bilinear model is used to find additional patterns, if any, that may further reduce the objective function. There has been no exhaustive testing or attempt to compare this empirical method with others.
Examples
Example from JuMP documentation for column generation
Example from Wikipedia
https://en.wikipedia.org/wiki/Cutting_stock_problem
The minimum number of rolls is 73.0.
Woodworking: Problem data from Google sheets
Find a minimum cost order of 2x4 lumber to build the "One Arm 2x4 Outdoor Sofa" described by Ana White.
Image source: www.ana-white.com
Purchase List
References
The one dimensional cutting stock problem addressed in this notebook is generally attributed to two classic papers by Gilmore and Gomory. This first paper considers the more general case of stocks available in multiple lengths, while the second paper specializes to the needs of a paper trimming operation.
Gilmore, P. C., & Gomory, R. E. (1961). A linear programming approach to the cutting-stock problem. Operations research, 9(6), 849-859. [jstor]
Gilmore, P. C., & Gomory, R. E. (1963). A linear programming approach to the cutting stock problem—Part II. Operations research, 11(6), 863-888. [jstor]
A useful survey of subsequent development of the cutting stock problem is given by:
Haessler, R. W., & Sweeney, P. E. (1991). Cutting stock problems and solution procedures. European Journal of Operational Research, 54(2), 141-150. [pdf]
Delorme, M., Iori, M., & Martello, S. (2016). Bin packing and cutting stock problems: Mathematical models and exact algorithms. European Journal of Operational Research, 255(1), 1-20. [sciencedirect]
The solution proposed by Gilmore and Gamory has been refined over time and now generally referred to as "column generation". A number of tutorial implemenations are available, these are representative:
More recently, the essential bilinear structure of the problem has been noted, and various convex transformations of the problem have been studied:
Harjunkoski, I., Westerlund, T., Pörn, R., & Skrifvars, H. (1998). Different transformations for solving non-convex trim-loss problems by MINLP. European Journal of Operational Research, 105(3), 594-603. [abo.fi][sciencedirect]
Harjunkoski, I., Pörn, R., & Westerlund, T. (1999). Exploring the convex transformations for solving non-convex bilinear integer problems. Computers & Chemical Engineering, 23, S471-S474. [sciencedirect