The LP Model

## 18.1 The LP Model

In modeling this problem as a MILP we could choose to introduce a variable xj for each feasible way of cutting a board of length l into boards of length li with coefficients aij representing the number of boards of length li obtained from the cutting associated with xj and a constraint Σj=1naijxj≥ bi specifying the number of boards bi required for each length li; for realistic problems there will frequently be very many feasible cuttings and associated variables xj and as these must be enumerated before problem solution can begin this approach may be impractical. We could instead introduce for each stock board used a set of variables xi,j for each demand i indicating the cutting required, a variable wj representing the resulting waste and a constraint Σi=1mlixi,j + wj = l ensuring the cutting is valid. Although we do not know how many boards will be required in the optimal solution, we do have an upper bound on this number K0=Σi=1m⌈bi/⌊l/li⌋⌉ and introduce the above variable sets and constraint for K0 boards. The constraints Σj=1K0xij≥ bi specify the number of boards bi required for each length li. Since all K0 boards may not be required we introduce a variable xj denoting whether a board is used and modify the valid cutting constraint

m
Σ
i=1
| lixij+wj=lxj

so that unused boards have zero cost in the objective function. The complete problem formulation is then:

P: minimize z | = |
K0
Σ
j=1
| wj
subject to |
K0
Σ
j=1
| xij
≥
bi
| ∀ i
m
Σ
i=1
| lixi,j+wj
wj
xi,j
xj
=
∈
∈
∈
lxj
{ | 0,…,l | }
{ | 0,…,hi | } | ∀ i
{ | 0, 1 | }
|  | ⎫ ⎪ ⎬ ⎪ ⎭ | ∀ j

where hi=⌊l/li⌋. This problem formulation is modeled and solved in ECLiPSe as follows:

            :- lib(eplex).

            % eplex instance creation
            :- eplex_instance(cut_stock).

            lp_cut_stock(Lengths, Demands, StockLength, Vars, Cost) :-
                (
                    foreach(Li, Lengths),
                    foreach(Bi, Demands),
                    foreach([], XijVars0),
                    foreach(Maxi, Bounds),
                    fromto(0, KIn, KOut, K0),
                    param(StockLength)
                do
                    KOut is KIn + fix(ceiling(Bi/floor(StockLength/Li))),
                    Maxi is fix(floor(StockLength/Li))
                ),
                (
                    for(J, 1, K0),
                    foreach(Wj, Obj),
                    foreach(Xj:Used, Vars),
                    fromto(XijVars0, VIn, VOut, XijVars),
                    param(Lengths, StockLength, Bounds)
                do
                    cut_stock:integers([Xj,Wj]),
                    % Xj variable bounds
                    cut_stock:(Xj::0..1),
                    % Wj variable bounds
                    cut_stock:(Wj::0..StockLength),
                    (
                        foreach(Li, Lengths),
                        foreach(Xij, Used),
                        foreach(Li*Xij, Knapsack),
                        foreach(XiVars, VIn),
                        foreach([Xij|XiVars], VOut),
                        foreach(Maxi, Bounds),
                        param(Xj)
                    do
                        % Xij variable bounds
                        cut_stock:integers(Xij),
                        cut_stock:(Xij::0..Maxi)
                    ),
                    % cutting knapsack constraint
                    cut_stock:(sum(Knapsack) + Wj =:= StockLength*Xj)
                ),
                (
                    foreach(Bi, Demands),
                    foreach(Xijs, XijVars)
                do
                    % demand constraint
                    cut_stock:(sum(Xijs) >= Bi)
                ),
                cut_stock:eplex_solver_setup(min(sum(Obj))),
                % optimization call
                cut_stock:eplex_solve(Cost).