In [1] I looked at some MIP formulations for a 2d bin packing problem. OR-Tools has a nice model.AddNoOverlap2D constraint function. So, I was excited to try this out. (In these times I get quickly enthusiastic about these things). OK, so I started coding:
from ortools.sat.python import cp_model
#---------------------------------------------------
# data
#---------------------------------------------------
data = {'bin':{'h':60,'w':40},
'cat1':{'w': 7,'h':12,'items':10},
'cat2':{'w': 9,'h': 3,'items':10},
'cat3':{'w': 5,'h':14,'items':10},
'cat4':{'w':13,'h': 9,'items':10},
'cat5':{'w': 6,'h': 8,'items': 5},
'cat6':{'w':20,'h': 5,'items': 5}}
#
# extract data for easier access
#
# bin width and height
H = data['bin']['h']
W = data['bin']['w']
# h,w,cat for each item
h = [data[cat]['h'] for cat in data if cat!='bin'for i in range(data[cat]['items'])]
w = [data[cat]['w'] for cat in data if cat!='bin'for i in range(data[cat]['items'])]
cat = [cat for cat in data if cat!='bin'for i in range(data[cat]['items'])]
n = len(h) # number of items
m = 10# number of bins
#---------------------------------------------------
# or-tools model
#---------------------------------------------------
model = cp_model.CpModel()
#
# variables
#
# x1,x2 and y1,y2 are start and end
x1 = [model.NewIntVar(0,W-w[i],'x1{}'.format(i)) for i in range(n)]
x2 = [model.NewIntVar(w[i],W,'x2{}'.format(i)) for i in range(n)]
y1 = [model.NewIntVar(0,H-h[i],'y1{}'.format(i)) for i in range(n)]
y2 = [model.NewIntVar(h[i],H,'y2{}'.format(i)) for i in range(n)]
# interval variables
xival = [model.NewIntervalVar(x1[i],w[i],x2[i],'xival{}'.format(i)) for i in range(n)]
yival = [model.NewIntervalVar(y1[i],w[i],y2[i],'yival{}'.format(i)) for i in range(n)]
# bin numbers
b = [model.NewIntVar(0,m,'b{}'.format(i)) for i in range(n)]
# b2[(i,j)] = true if b[i]=b[j] for i<j
b2 = {(i,j):model.NewBoolVar('b2{}.{}'.format(i,j)) for j in range(n) for i in range(j)}
# used bins
u = [model.NewBoolVar('u{}'.format(k)) for k in range(m)]
#
# constraints
#
# no overlap for items in same bin
for j in range(n):
for i in range(j):
model.Add(b[i] != b[j]).OnlyEnforceIf(b2[(i,j)].Not())
model.AddNoOverlap2D([xival[i],xival[j]],[yival[i],yival[j]]).OnlyEnforceIf(b2[(i,j)])
# used bin
for i in range(n):
for k in range(m):
model.Add(b[i]<k).OnlyEnforceIf(u[k].Not())
# objective
model.Minimize(sum([u[k] for k in range(m)]))
#
# solve model
#
solver = cp_model.CpSolver()
rc = solver.Solve(model)
print(rc)
print(solver.StatusName())
Work in progress, so I expect to see problems. When I run this I see:
OK. That is not helpful. After a bit of trial and error, it looks like OnlyEnforceIf is not implemented for the AddNoOverlap2D constraints. Indeed, we can verify this with solver.parameters.log_search_progress = True [2]. At first, I did not see anything (I was running this in a Jupyter notebook on colab.google.com). Running it locally from the command line, I saw:
Enforcement literal not supported in constraint: enforcement_literal: 256 no_overlap_2d { x_intervals: 0 x_intervals: 1 y_intervals: 50 y_intervals: 51 }
This confirms this is not supported.
I am not sure how to fix this with a simple work-around. If desperate, I could try to use some of the MIP formulations we did earlier.
References
- 2d Bin Packing, https://yetanothermathprogrammingconsultant.blogspot.com/2021/02/2d-bin-packing.html
- 2d bin packing using or-tools: AddNoOverlap2D and OnlyEnforceIf gives MODEL_INVALID, https://stackoverflow.com/questions/66286586/2d-bin-packing-using-or-tools-addnooverlap2d-and-onlyenforceif-gives-model-inva
