![]() | |||||||||||||
| |||||||||||||
Cutting stock Description A paper mill produces rolls of paper of a fixed width that
are subsequently cut into smaller rolls according to the
customer orders. The rolls can be cut into NWIDTHS different
sizes. The orders are given as demands for each width.
The objective of the paper mill is to satisfy the demand
with the smallest possible number of paper rolls in order
to minimize the losses.
Starting with just a basic set of cutting patterns a
column generation heuristic is employed to find more
profitable cutting patterns. This heuristic solves a
second optimization problem (a knapsack problem) that is
independent of the main, cutting stock problem. Further explanation of this example: 'Mosel User Guide', Section 11.2 'Column generation'; Xpress Whitepaper 'Embedding Optimization Algorithms'. Similar problem: 'Applications of optimization with Xpress-MP', Section 9.6 'Cutting steel bars for desk legs' (d6cutbar.mos)
Source Files By clicking on a file name, a preview is opened at the bottom of this page.
Data Files cutstock_graph.mos (!******************************************************* Mosel Example Problems ====================== file cutstock.mos ````````````````` TYPE: Cutting stock problem DIFFICULTY: 5 FEATURES: MIP problem solved by column generation, working with multiple problems, using 'sethidden', setting Optimizer and Mosel control parameters DESCRIPTION: A paper mill produces rolls of paper of a fixed width that are subsequently cut into smaller rolls according to the customer orders. The rolls can be cut into NWIDTHS different sizes. The orders are given as demands for each width. The objective of the paper mill is to satisfy the demand with the smallest possible number of paper rolls in order to minimize the losses. Starting with just a basic set of cutting patterns a column generation heuristic is employed to find more profitable cutting patterns. This heuristic solves a second optimization problem (a knapsack problem) that is independent of the main, cutting stock problem. FURTHER INFO: "Mosel User Guide", Section 11.2 "Column generation"; Xpress Whitepaper "Embedding Optimization Algorithms". Similar problem: `Applications of optimization with Xpress-MP', Section 9.6 `Cutting steel bars for desk legs' (c) 2008 Fair Isaac Corporation authors: Bob Daniel, S. Heipcke, J. Tebboth, 2001, rev. Nov. 2017 *******************************************************!) model "Cutting stock" uses "mmxprs", "mmsvg" parameters DATAFILE="cutstock.dat" NWIDTHS = 15 end-parameters forward procedure column_gen forward function knapsack(C:array(range) of real, A:array(range) of real, B:real, xbest:array(range) of integer, pass: integer): real forward procedure print_pat(dj:real, vx: array(range) of integer) forward function draw_pat(dj:real, vx: array(range) of integer, iter:integer):boolean (! The column generation algorithm requires the solution of a knapsack problem. We define the constraints of the knapsack problem globally because Mosel does not delete them at the end of a function (due to its principle of incremental model formulation). !) declarations WIDTHS = 1..NWIDTHS ! Range of widths RP: range ! Range of cutting patterns MAXWIDTH: integer ! Maximum roll width EPS = 0.001 ! Zero tolerance WIDTH: array(WIDTHS) of real ! Possible widths DEMAND: array(WIDTHS) of integer ! Demand per width PATTERN: array(WIDTHS, RP) of integer ! (Basic) cutting patterns PGRAPH: array(WIDTHS) of string ! Graph for width use: dynamic array(RP) of mpvar ! Rolls per pattern soluse: dynamic array(RP) of real ! Solution values for variables `use' Dem: array(WIDTHS) of linctr ! Demand constraints MinRolls: linctr ! Objective function KnapCtr, KnapObj: linctr ! Knapsack constraint+objective x: array(WIDTHS) of mpvar ! Knapsack variables end-declarations initializations from DATAFILE WIDTH DEMAND MAXWIDTH end-initializations ! Make basic patterns forall(j in WIDTHS) PATTERN(j,j) := floor(MAXWIDTH/WIDTH(j)) forall(j in WIDTHS) do create(use(j)) ! Create NWIDTHS variables `use' use(j) is_integer ! Variables are integer and bounded use(j) <= integer(ceil(DEMAND(j)/PATTERN(j,j))) end-do ! Objective: minimize the number of rolls MinRolls:= sum(j in WIDTHS) use(j) ! Satisfy all demands forall(i in WIDTHS) Dem(i):= sum(j in WIDTHS) PATTERN(i,j) * use(j) >= DEMAND(i) column_gen ! Column generation at top node minimize(MinRolls) ! Compute the best integer solution ! for the current problem (including ! the new columns) writeln("Best integer solution: ", getobjval, " rolls") write(" Rolls per pattern: ") forall(i in RP) write(getsol(use(i)),", ") writeln svgerase("msg") forall(i in RP) svgaddtext("msg", MAXWIDTH+15, 2*i, text(getsol(use(i)))) svgaddtext("msg", MAXWIDTH+10, 0, "Total: "+getobjval) svgrefresh svgsave("cutstock.svg") svgwaitclose("Close browser window to terminate model execution.", 1) !************************************************************************ ! 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 !************************************************************************ procedure column_gen declarations dualdem: array(WIDTHS) of real xbest: array(WIDTHS) of integer ubnd, zbest, objval: real bas: basis end-declarations setparam("zerotol", EPS) ! Set Mosel comparison tolerance setparam("XPRS_PRESOLVE", 0) ! Switch presolve off npatt:=NWIDTHS npass:=1 svgrefresh forall(j in WIDTHS) do PGRAPH(j):="W"+j svgaddgroup(PGRAPH(j), "Width "+WIDTH(j)) svgsetstyle(SVG_FILL,SVG_CURRENT) svgsetstyle(SVG_STROKE,SVG_GREY) forall(i in 1..PATTERN(j,j)) svgaddrectangle(10+(i-1)*WIDTH(j),j*2,WIDTH(j),1) end-do svgaddgroup("msg", "", SVG_BLACK) svgsetgraphviewbox(5,-1,MAXWIDTH+30,100) svgsetgraphscale(5) while(true) do minimize(XPRS_LIN, MinRolls) ! Solve the LP savebasis(bas) ! Save the current basis objval:= getobjval ! Get the objective value ! Get the solution values forall(j in 1..npatt) soluse(j):=getsol(use(j)) forall(i in WIDTHS) dualdem(i):=getdual(Dem(i)) ! Solve a knapsack problem zbest:= knapsack(dualdem, WIDTH, MAXWIDTH, xbest, npass) - 1.0 write("Pass ", npass, ": ") if zbest = 0 then writeln("no profitable column found.\n") break else print_pat(zbest, xbest) ! Print the new pattern npatt+=1 if not draw_pat(zbest, xbest, npatt) then break; end-if create(use(npatt)) ! Create a new var. for this pattern use(npatt) is_integer MinRolls+= use(npatt) ! Add new var. to the objective ubnd:=0 forall(i in WIDTHS) if xbest(i) > 0 then Dem(i)+= xbest(i)*use(npatt) ! Add new var. to demand constr.s ubnd:= maxlist(ubnd, ceil(DEMAND(i)/xbest(i) )) end-if use(npatt) <= ubnd ! Set upper bound on the new var. loadprob(MinRolls) ! Reload the problem loadbasis(bas) ! Load the saved basis end-if npass+=1 end-do writeln("Solution after column generation: ", objval, " rolls, ", getsize(RP), " patterns") write(" Rolls per pattern: ") forall(i in RP) write(soluse(i),", ") writeln setparam("XPRS_PRESOLVE", 1) ! Switch presolve on end-procedure !************************************************************************ ! Solve the integer knapsack problem ! z = max{cx : ax<=b, x in Z^N} ! with b=MAXWIDTH, N=NWIDTHS ! ! We reset the knapsack constraints to 0 at the end of this function ! so that they do not unnecessarily increase the size of the main ! problem. The same is true in the other sense: hiding the demand ! constraints while solving the knapsack problem makes life easier ! for the optimizer, but is not essential for getting the correct ! solution. !************************************************************************ function knapsack(C:array(range) of real, A:array(range) of real, B:real, xbest:array(range) of integer, pass: integer):real ! Hide the demand constraints forall(j in WIDTHS) sethidden(Dem(j), true) ! Define the knapsack problem KnapCtr := sum(j in WIDTHS) A(j)*x(j) <= B KnapObj := sum(j in WIDTHS) C(j)*x(j) ! Integrality condition if pass=1 then forall(j in WIDTHS) x(j) is_integer end-if maximize(KnapObj) returned:=getobjval forall(j in WIDTHS) xbest(j):=round(getsol(x(j))) ! Reset knapsack constraint and objective, unhide demand constraints KnapCtr := 0 KnapObj := 0 forall(j in WIDTHS) sethidden(Dem(j), false) end-function !************************************************************************ procedure print_pat(dj:real, vx: array(range) of integer) declarations dw: real end-declarations writeln("New pattern found with marginal cost ", dj) write(" Widths distribution: ") dw:=0 forall(i in WIDTHS) do write(WIDTH(i), ":", vx(i), " ") dw += WIDTH(i)*vx(i) end-do writeln("Total width: ", dw) end-procedure !************************************************************************ function draw_pat(dj:real, vx: array(range) of integer, iter:integer):boolean if svgclosing then returned:=false; return; end-if svgerase("msg") returned:=true cum:=10.0 forall(i in WIDTHS, j in 1..vx(i)) do svgaddrectangle(PGRAPH(i), cum, 2*iter, WIDTH(i), 1) cum+=WIDTH(i) end-do forall(j in 1..iter) svgaddtext("msg", MAXWIDTH+15, 2*j, text(soluse(j))) svgaddtext("msg", MAXWIDTH+10, 0, "Total: "+dj) svgrefresh svgpause end-function end-model
| |||||||||||||
© Copyright 2025 Fair Isaac Corporation. |