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)
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.
---- 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
| Items to compare |
| 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}\] |
- We can order turning on \(\color{darkred}x_{k,n}\) by \(n\): \[\color{darkred}x_{k,n-1}\ge\color{darkred}x_{k,n}\]
- 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}\]
- 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}}\]
---- 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)
![]() |
| 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
- 2d bin packing with Google OR-Tools CP-SAT, how to fix?, https://yetanothermathprogrammingconsultant.blogspot.com/2021/02/2d-bin-packing-with-google-or-tools-cp.html
Appendix 1: GAMS covering model
$ontext |
Appendix 2: GAMS continuous size model
$ontext |

