Quantcast
Channel: Yet Another Math Programming Consultant
Viewing all articles
Browse latest Browse all 809

2d knapsack problem

$
0
0

This post is about what sometimes is called a 2d knapsack problem. It can also be considered as a bin-packing problem.

So, we have one 2d container of a given size. Furthermore, we have a collection of items: rectangles of a given size, with a value. The goal is to pack items in our container without overlapping each other such that the total value is maximized.

To make things concrete. let's have a look at a small data set (randomly generated):


----     18 PARAMETER dataproblem data

width height available value value/size

k1 20.0004.0002.000338.9844.237
k2 12.00017.0006.000849.2464.163
k3 20.00012.0002.000524.0222.183
k4 16.0007.0009.000263.3032.351
k5 3.0006.0003.000113.4366.302
k6 13.0005.0003.000551.0728.478
k7 4.0007.0006.00086.1663.077
k8 6.00018.0008.000755.0946.992
k9 14.0002.0007.000223.5167.983
k10 9.00011.0005.000369.5603.733
container 30.00020.000


Our container is \(30\times 20\). We have 10 different types of items. The column available indicates the number of items of the current type. This means we have actually 51 items. Instead of value, it may be better to look at the value/size column. This is the relative value, normalized for size (i.e. height times width). The four most valuable items are k6, k9, k8, and k5. Hopefully, we will see them back in the solution.

An interesting wrinkle is: the items can be rotated. There are different ways of handling this. We can duplicate all items and add a rotated version. However, we need to make sure that the availability limit is applied to both. In the first MIP model below, I decided to add a rotation index \(r\) to indicate whether an item is rotated. This is almost the same as duplicating items. In the second model, I introduce an explicit binary variable to indicate whether the item is rotated.

I am not really asking for an arrangement of the selected items, but all the models I know of, produce an actual non-overlapping configuration. 


A covering model


For this model, we assume that the container is a grid of cells, and each item occupies a number of cells. In other words, I assume the width and height are integer-valued. The main decision variable is: \[\color{darkred}x_{k,r,i,j} = \begin{cases} 1 & \text{if an item of type $k$ and direction $r$ is placed at cell \((i,j)\)} \\ 0 & \text{otherwise}\end{cases}\] The index \(r\) has two elements: \({r,nr}\) indicating if the corresponding item \(k\) is rotated or not. My convention is: if \(\color{darkred}x_{k,r,i,j}=1\) for a non-rotated item, then cells \((i',j')\), with \(i'=i,..,i+\color{darkblue}{\mathit{width}}_k-1\) and \(j'=j,..,j+\color{darkblue}{\mathit{height}}_k-1\) are occupied by this item. We need some rule to precisely state what it means if an item \(k\) is placed at \((i,j)\).

The first thing I did was creating two sets: \(\color{darkblue}{\mathit{ok}}\) and \(\color{darkblue}{\mathit{cover}}\).

The first set is \(\color{darkblue}{\mathit{ok}}_{k,r,i,j}\) indicating the allowed cells \((i,j)\) where we can place an item of type \(k\) (rotated or not). Below we see the allowed assignments of item k3:


ok(k,r,i,j)

The second set is \(\color{darkblue}{\mathit{cover}}_{k,r,i,j,i',j'}\) indicating which cells \((i',j')\) are covered when placing item \(k\) on cell \((i,j)\). This is a large 6 dimensional set. It has 282,944 elements.


cover(k,r,i,j,ii,jj)


Here we see the coverage when we place item k1 at (r4,c6) (not rotated).


With this we can write our covering model:


Covering Model
\[\begin{align}\max&\>\color{darkred}{\mathit{totalValue}} = \sum_{k,r,i,j|\color{darkblue}{\mathit{ok}}(k,r,i,j)} \color{darkblue}{\mathit{value}}_k \cdot \color{darkred}x_{k,r,i,j} && &&\text{Maximize total value}\\ & \sum_{k,r,i,j|\color{darkblue}{\mathit{cover}}(k,r,i,j,i',j')}\color{darkred}x_{k,r,i,j} \le 1 && \forall i',j'&& \text{No overlap} \\ & \sum_{r,i,j|\color{darkblue}{\mathit{ok}}(k,r,i,j)} \color{darkred}x_{k,r,i,j} \le \color{darkblue}{\mathit{avail}}_k &&\forall k && \text{Availability} \\ & \color{darkred}x_{k,r,i,j} \in \{0,1\} \end{align}\]


I considered adding the constraint: \[\sum_{k,r,i,j|\color{darkblue}{\mathit{ok}}(k,r,i,j)} \color{darkblue}{\mathit{area}}_k \cdot \color{darkred}x_{k,r,i,j} \le \color{darkblue}{\mathit{containerSize}}\] This did not seem to be much of a help.

The model is not very small:


MODEL STATISTICS

BLOCKS OF EQUATIONS 3 SINGLE EQUATIONS 611
BLOCKS OF VARIABLES 2 SINGLE VARIABLES 4,273
NON ZERO ELEMENTS 291,489 DISCRETE VARIABLES 4,272

However, this model solves quickly: just 63 nodes were needed. The results are:
 
----     83 VARIABLE x.L  item k is placed at (i,j)

r1.c1 r1.c14 r1.c28 r3.c14 r5.c14 r6.c1 r6.c6 r7.c11 r7.c29

k5 .nr 1
k6 .nr 1
k6 .r 11
k8 .r 1
k9 .nr 111
k9 .r 1

+ r13.c11 r19.c1 r19.c15

k8 .r 1
k9 .nr 11


---- 83 VARIABLE totalValue.L = 4617.938objective (maximized)


This is a bit difficult to interpret. A plot can help here:

 

Notice that all four valuable items (k6, k9, k8 and k5) are present in the solution!

A continuous size model


Instead of using a grid, here we use continuous widths and heights. For this, we use pairwise no-overlap constraints.  Let's show the idea behind this.

Intermezzo: pairwise no-overlap constraints. These constraints are: \[\begin{align} \color{darkred}x_a  &\ge \color{darkred}x_b + \color{darkblue}w_b \\ {\bf or}\>\color{darkred}x_b &\ge \color{darkred}x_a + \color{darkblue}w_a \\ {\bf or}\>\color{darkred}y_a &\ge \color{darkred}y_b + \color{darkblue}h_b \\ {\bf or}\>\color{darkred}y_b &\ge \color{darkred}y_a + \color{darkblue}h_a \end{align}\] The or conditions can be implemented with additional binary variable and big-M constraints: \[\begin{align} & \color{darkred}x_a  \ge \color{darkred}x_b + \color{darkblue}w_b  - \color{darkblue}M \cdot (1-\color{darkred} \delta_{a,b,1})\\ & \color{darkred}x_b \ge \color{darkred}x_a + \color{darkblue}w_a - \color{darkblue}M \cdot (1-\color{darkred} \delta_{a,b,2})\\ &\color{darkred}y_a \ge \color{darkred}y_b + \color{darkblue}h_b - \color{darkblue}M \cdot (1-\color{darkred} \delta_{a,b,3})\\ & \color{darkred}y_b \ge \color{darkred}y_a + \color{darkblue}h_a - \color{darkblue}M \cdot (1-\color{darkred} \delta_{a,b,4}) \\ & \sum_k \color{darkred}\delta_{a,b,k} \ge 1 \end{align}\] We need to add one more detail: we want to have these constraints only active if both items a and b are selected to be in our container. So our constraints become:  \[\begin{align} & \color{darkred}x_a  \ge \color{darkred}x_b + \color{darkblue}w_b  - \color{darkblue}M \cdot (1-\color{darkred} \delta_{a,b,1}) - \color{darkblue}M\cdot (1-\color{darkred}{\mathit{selected}}_a) - \color{darkblue}M\cdot (1-\color{darkred}{\mathit{selected}}_b)\\ & \color{darkred}x_b \ge \color{darkred}x_a + \color{darkblue}w_a - \color{darkblue}M \cdot (1-\color{darkred} \delta_{a,b,2}) - \color{darkblue}M\cdot (1-\color{darkred}{\mathit{selected}}_a) - \color{darkblue}M\cdot (1-\color{darkred}{\mathit{selected}}_b)\\ &\color{darkred}y_a \ge \color{darkred}y_b + \color{darkblue}h_b - \color{darkblue}M \cdot (1-\color{darkred} \delta_{a,b,3})- \color{darkblue}M\cdot (1-\color{darkred}{\mathit{selected}}_a) - \color{darkblue}M\cdot (1-\color{darkred}{\mathit{selected}}_b) \\ & \color{darkred}y_b \ge \color{darkred}y_a + \color{darkblue}h_a - \color{darkblue}M \cdot (1-\color{darkred} \delta_{a,b,4}) - \color{darkblue}M\cdot (1-\color{darkred}{\mathit{selected}}_a) - \color{darkblue}M\cdot (1-\color{darkred}{\mathit{selected}}_b)\\ & \sum_k \color{darkred}\delta_{a,b,k} \ge 1 \end{align}\] The final complication is that we can rotate items. So the width can become the hight and vice versa.


We need to form pairs of items we may need to compare. I started with enumerating all possible items:

----     56 SET kn(k,n) combinations

n1 n2 n3 n4 n5 n6 n7 n8 n9

k1 YES YES
k2 YES YES YES YES YES YES
k3 YES YES
k4 YES YES YES YES YES YES YES YES YES
k5 YES YES YES
k6 YES YES YES
k7 YES YES YES YES YES YES
k8 YES YES YES YES YES YES YES YES
k9 YES YES YES YES YES YES YES
k10 YES YES YES YES YES


---- 56 PARAMETER numsequence numbers for (k,n)

n1 n2 n3 n4 n5 n6 n7 n8 n9

k1 12
k2 345678
k3 910
k4 111213141516171819
k5 202122
k6 232425
k7 262728293031
k8 3233343536373839
k9 40414243444546
k10 4748495051
  

We see we have 51 items to consider. The sequence number can be used to form pairs we need to compare. The big trick is to compare item a with item b only once. I created a set for this:

Items to compare

The model has the following decision variables: \[\color{darkred}x_{k,n} \in \{0,1\}\] indicates if item \((k,n)\) is placed in our container. Another binary variable \[\color{darkred}{\mathit{rot}}_{k,n} \in \{0,1\}\] telss us if item \((k,n)\) is rotated. In addition, we have a bunch of binary \(\color{darkred}\delta\) variables for the no-overlap constraints. Continuous variables are used to represent the location \[\color{darkred}{\mathit{pos}}_{k,n,xy} \ge 0\] and the end-point \[\color{darkred}{\mathit{pos2}}_{k,n,xy} \in [0,\color{darkblue}{\mathit{WH}}_{xy}]\] Here we use the set \(\color{darkblue}xy=\{x,y\}\) and the size of the container \(\color{darkblue}{\mathit{WH}}_{xy}\). Let's try to formulate a model:

Continuous size model
\[\begin{align} \max\> & \color{darkred}{\mathit{totalValue}} = \sum_{\color{darkblue}{\mathit{kn}}(k,n)} \color{darkblue}{\mathit{value}}_k \cdot \color{darkred}x_{k,n} && && \text{Maximize total value}\\ & \color{darkred}{\mathit{pos2}}_{k,n,xy}  = \color{darkred}{\mathit{pos}}_{k,n,xy} + \color{darkblue}{\mathit{wh}}_{k,R,xy}\cdot \color{darkred}{\mathit{rot}}_{k,n} + \color{darkblue}{\mathit{wh}}_{k,{\mathit{NR}},xy}\cdot (1-\color{darkred}{\mathit{rot}}_{k,n})  &&\forall \color{darkblue}{\mathit{kn}}_{k,n},xy&&\text{End-point of item} \\ & \color{darkred}{\mathit{pos}}_{k,n,xy} \ge \color{darkred}{\mathit{pos2}}_{k',n',xy} - \color{darkblue}M \cdot (3-\color{darkred}\delta_{k,n,k',n',xy,1}-\color{darkred}x_{k,n}-\color{darkred}x_{k',n'})&&\forall \color{darkblue}{\mathit{compare}}_{n,k,n',k'}, xy && \text{No overlap}\\ &\color{darkred}{\mathit{pos}}_{k',n',xy} \ge \color{darkred}{\mathit{pos2}}_{k,n,xy} - \color{darkblue}M \cdot (3-\color{darkred}\delta_{k,n,k',n',xy,2}-\color{darkred}x_{k,n}-\color{darkred}x_{k',n'})&&\forall \color{darkblue}{\mathit{compare}}_{n,k,n',k'},xy && \text{No overlap} \\ & \sum_{xy} \left( \color{darkred}\delta_{k,n,k',n',xy,1}+\color{darkred}\delta_{k,n,k',n',xy,2}\right)\ge 1 && \forall \color{darkblue}{\mathit{compare}}_{n,k,n',k'} && \text{No overlap}\end{align}\]


I further added three constraints. They are not essential but may help performance.
  1. We can order turning on \(\color{darkred}x_{k,n}\) by \(n\): \[\color{darkred}x_{k,n-1}\ge\color{darkred}x_{k,n}\] 
  2. We can order the variable \(\color{darkred}{\mathit{pos}}\) of the same type by their \(x\)-coordinate: \[\color{darkred}{\mathit{pos}}_{k,n-1,x}\le \color{darkred}{\mathit{pos}}_{k,n,x}\]
  3. We know that the total area of items cannot exceed the area of the container: \[\sum_{\color{darkblue}{\mathit{kn}}(k,n)}  \color{darkblue}{\mathit{area}}_k \cdot \color{darkred}x_{k,n} \le \color{darkblue}{\mathit{containerArea}}\]


The results of this model are:

----    127 VARIABLE x.L  item (k,n) is placed (rotated/non-rotated)

n1 n2 n3 n4 n5 n6

k5 1
k6 111
k8 11
k9 111111


---- 127 PARAMETER positionsrectangles (x1,y1)-(x2,y2)

x1 y1 x2 y2 rotated

k5.n1 3.0006.000
k6.n1 17.00030.0005.000
k6.n2 20.0005.00025.00018.0001.000
k6.n3 25.0005.00030.00018.0001.000
k8.n1 2.00012.00020.00018.0001.000
k8.n2 2.0006.00020.00012.0001.000
k9.n1 6.0002.00020.0001.000
k9.n2 2.00018.00016.00020.000
k9.n3 3.00017.0002.000
k9.n4 3.0004.00017.0006.000
k9.n5 3.0002.00017.0004.000
k9.n6 16.00018.00030.00020.000


---- 127 VARIABLE totalValue.L = 4617.938objective (maximized)


We see the optimal objective is identical to the results of the covering model. Implementing two very different models is a good way to certify results. This model takes a little bit more than 200 seconds.

 
Solution by continuous size model


Notes


  • Slightly different data can cause very different performance.
  • The covering model is a bit faster than the continuous size model.
  • The problem is very simple. The MIP models not so much.
  • I did not try to duplicate items to model rotation. Just have two items: one not rotated and one rotated. Impose a constraint that says we can only pick one. That may be a bit simpler than what I tried, especially in the second model. 
  • I tried using Google or-tools using a similar scheme as in [1]. Unfortunately, my implementation did not give good results. It reproduced the above solution after 13k seconds:
    13131.66s best:4617936 next:[4617937,21154899]  fixed_bools:0/1338
    Note that the objective coefficients were made integers (after multiplying by 1000). I am sure there are better formulations.

References


Appendix 1: GAMS covering model


$ontext

  
2d knapsack problem

  
Fill a container with most valuable rectangles.

$offtext

set
  dummy
'for display'/xmin,xmax,ymin,ymax,width,height,available,value/

  i
'rows in container'/r1*r20/
  j
'columns in container'/c1*c30/
  k
'rectangular items to place in container'/k1*k10/
  r
'rotate'/nr,r/
;

*---------------------------------------------------------------------
* data
*---------------------------------------------------------------------

parameter data(*,*) 'problem data';
data(
'container','height') = card(i);
data(
'container','width') = card(j);
data(k,
'height') = uniformint(1,20);
data(k,
'width') = uniformint(1,20);
data(k,
'value/size') = uniform(1,10);
data(k,
'value') = data(k,'value/size')*data(k,'height')*data(k,'width');
data(k,
'available') = uniformint(1,10);
display data;

*---------------------------------------------------------------------
* derived data
*---------------------------------------------------------------------

alias (i,ii),(j,jj);

parameters
   w(k,r)
'item width'
   h(k,r)
'item height'
;

w(k,r) = data(k,
'width')$sameas(r,'nr') + data(k,'height')$sameas(r,'r');
h(k,r) = data(k,
'height')$sameas(r,'nr') + data(k,'width')$sameas(r,'r');

sets
  ok(k,r,i,j)
'item (k,r) can be placed at (i,j)'
  cover(k,r,i,j,ii,jj)
'(ii,jj) is covered when item (k,r) is placed at (i,j)'
;
ok(k,r,i,j) =
ord(i)+h(k,r)-1 <= card(i)
             
andord(j)+w(k,r)-1 <= card(j);

cover(ok(k,r,i,j),ii,jj) =
          
ord(ii)>=ord(i) andord(ii)<ord(i)+h(k,r) and
          
ord(jj)>=ord(j) andord(jj)<ord(j)+w(k,r);


*-----------------------------------------------------------------------
* model
*-----------------------------------------------------------------------

binaryvariable x(k,r,i,j) 'item k is placed at (i,j)';
variable totalValue 'objective (maximized)';

equations
   noOverlap(i,j)
'only one item can cover (i,j)'
   count(k)      
'limit number of each item'
   objective     
'maximize total value'
;

noOverlap(ii,jj)..
sum(cover(k,r,i,j,ii,jj),x(k,r,i,j)) =l= 1;

count(k)..
sum(ok(k,r,i,j), x(k,r,i,j)) =l= data(k,'available');

objective.. totalValue =e=
sum(ok(k,r,i,j),x(k,r,i,j)*data(k,'value'));

model m /all/;
option optcr=0,threads=8;
solve m maximizing totalValue using mip;

*-----------------------------------------------------------------------
* reporting
*-----------------------------------------------------------------------

option x:0:2:2;
display x.l,totalValue.l;

parameter plotdata(k,r,i,j,*);
loop((k,r,i,j)$(x.l(k,r,i,j)>0.5),
    plotdata(k,r,i,j,
'xmin') = ord(j) - 1 + EPS;
    plotdata(k,r,i,j,
'xmax') = plotdata(k,r,i,j,'xmin') + w(k,r);
    plotdata(k,r,i,j,
'ymin') = ord(i) - 1 + EPS;
    plotdata(k,r,i,j,
'ymax') = plotdata(k,r,i,j,'ymin') + h(k,r);
    plotdata(k,r,i,j,
'value') = data(k,'value');
);
options plotdata:2:4:1;
display plotdata;




Appendix 2: GAMS continuous size model


$ontext

  
2d knapsack problem

  
Fill a container with most valuable rectangles.

  
Continuous size formulation: pairwise no-overlap constraints.

$offtext

*---------------------------------------------------------------
* data
*---------------------------------------------------------------

set
  dummy
'for display'/xmin,xmax,ymin,ymax,width,height,available,value/
  k
'rectangular items to place in container'/k1*k10/
  n
'item# for each k'/n1*n10/
  r
'rotate'/nr,r/
  xy
'coordinates'/x,y/
;

parameter data(*,*);
data(k,
'height') = uniformint(1,20);
data(k,
'width') = uniformint(1,20);
data(k,
'value/area') = uniform(1,10);
data(k,
'available') = uniformint(1,10);
data(
'container','height') = 20;
data(
'container','width') = 30;

data(k,
'area') = data(k,'height')*data(k,'width');
data(
'container','area') = data('container','height')*data('container','width');
data(k,
'value') = data(k,'value/area')*data(k,'area');
display data;

*---------------------------------------------------------------
* derived data
*---------------------------------------------------------------

parameter
   wh(k,r,xy)
'width or height'
   whn(k,n,r,xy)
'auxiliary'
   num(k,n)
'sequence numbers for (k,n)'
   cWH(xy)
'width or height of container'

;
wh(k,r,
'x') = data(k,'width')$sameas(r,'nr') + data(k,'height')$sameas(r,'r');
wh(k,r,
'y') = data(k,'height')$sameas(r,'nr') + data(k,'width')$sameas(r,'r');
whn(k,n,r,xy) = wh(k,r,xy);

cWH(xy) = data(
'container','width')$sameas(xy,'x') +
             data(
'container','height')$sameas(xy,'y');

set kn(k,n) '(k,n) combinations';
kn(k,n)$(
ord(n)<=data(k,'available')) = yes;
alias(kn,kn2);

scalar cnt /0/;
loop(kn,
   cnt = cnt+1;
   num(kn) = cnt;
);

option num:0;
display kn,num;

alias (k,kk),(n,nn),(r,rr);

set compare(k,n,kk,nn) 'we need no-overlap between these';
compare(kn,kn2) = num(kn) < num(kn2);


*---------------------------------------------------------------
* model
*---------------------------------------------------------------

binaryvariables
   x(k,n)
'item (k,n) is placed (rotated/non-rotated)'
   rot(k,n)
'item (k,n) is rotated'
   delta
'used in no-overlap constraints'
;
positivevariables
    pos(k,n,xy)
'position'
    pos2(k,n,xy)
'pos+width/height'
;
variable totalValue 'objective (maximized)';

pos.up(kn(k,n),xy) = cWH(xy) -
smin(r,wh(k,r,xy));
pos2.up(kn(k,n),xy) = cWH(xy);

equations
   epos2(k,n,xy)          
'pos+width/height'
   noOverlap1(k,n,k,n,xy) 
'no-overlap between items'
   noOverlap2(k,n,k,n,xy) 
'no-overlap between items'
   noOverlap3(k,n,k,n)    
'no-overlap between items'
   objective

* these are extras. They may help performance
   order
   order2
   area
;

epos2(kn(k,n),xy)..
   pos2(kn,xy) =e= pos(kn,xy) + rot(kn)*wh(k,
'r',xy) + (1-rot(kn))*wh(k,'nr',xy);

noOverlap1(compare(kn,kn2),xy)..
   pos(kn,xy) =g= pos2(kn2,xy)
                  - cWH(xy)*(3-delta(kn,kn2,xy,
'1')-x(kn)-x(kn2));
noOverlap2(compare(kn,kn2),xy)..
   pos(kn2,xy) =g= pos2(kn,xy)
                   - cWH(xy)*(3-delta(kn,kn2,xy,
'2')-x(kn)-x(kn2));
noOverlap3(compare(kn,kn2))..
   
sum(xy, delta(kn,kn2,xy,'1')+delta(kn,kn2,xy,'2')) =g= 1;

objective.. totalValue =e=
sum(kn(k,n),x(kn)*data(k,'value'));

order(k,n)$(kn(k,n)
and kn(k,n-1)).. x(k,n-1) =g= x(k,n);
order2(k,n)$(kn(k,n)
and kn(k,n-1)).. pos(k,n-1,'x') =l= pos(k,n,'x');

area..
sum(kn(k,n), x(kn)*data(k,'area')) =l= prod(xy,cWH(xy));

model m /all/;
option optcr=0, threads=8;
solve m maximizing totalValue using mip;


*---------------------------------------------------------------
* reporting
*---------------------------------------------------------------

parameter positions(k,n,*) 'rectangles (x1,y1)-(x2,y2)';
positions(kn,
'x1')$(x.l(kn)>0.5) = pos.l(kn,'x');
positions(kn,
'y1')$(x.l(kn)>0.5) = pos.l(kn,'y');
positions(kn,
'x2')$(x.l(kn)>0.5) = pos2.l(kn,'x');
positions(kn,
'y2')$(x.l(kn)>0.5) = pos2.l(kn,'y');
positions(kn,
'rotated')$(x.l(kn)>0.5) = rot.l(kn);

option x:0;
display x.l,positions,totalValue.l;

parameter plotdata(k,n,*);
loop(kn(k,n)$(x.l(kn)>0.5),
   plotdata(kn,
'xmin') = pos.l(kn,'x')+EPS;
   plotdata(kn,
'xmax') = pos2.l(kn,'x');
   plotdata(kn,
'ymin') = pos.l(kn,'y')+EPS;
   plotdata(kn,
'ymax') = pos2.l(kn,'y');
   plotdata(kn,
'value') = data(k,'value');
);
option plotdata:2:2:1;
display plotdata;


Viewing all articles
Browse latest Browse all 809

Trending Articles