我有一个包含50,000个订单的数据集。每个订单有~20个产品。存在产品体积和重量(以及x,y,z尺寸)。我有定容量V_max和最大重量容量W_max的装运箱。每个订单我想最小化在V<约束条件下使用的框的数量。 V_max,W< W_max。
在搜索网络时,我遇到了许多binpacking算法,但似乎没有一个能够解决问题。有谁知道一个优雅(和快速)python算法来解决这个问题?
答案 0 :(得分:1)
以下是使用cvxpy(1.0分支!)和CoinOR's Cbc MIP-solver到cylp的快速原型。 (一切都是开源的)
我正在使用cvxpy,因为它可以进行漂亮的简洁建模(需要付出一些代价,因为cvxpy不仅仅是建模!)。在实际实现中,可以直接提供这些解算器(不太好的代码),这也将提高性能(cvxpy没有时间;我们可以使用Simplex功能,如专用变量边界)。这也允许针对您的问题调整求解器(例如,Cbc / Cgl的切割平面设置)。如果您的实例太硬(NP-硬度!),您还可以使用时间限制或MIPgaps来获得良好的近似值。
提高性能的第一种方法(使用cvxpy;此版本中没有求解器选项)将是某种对称减少(使用前N个框;不要扰乱那些N<< M box) 。 编辑:添加了最简单的方法 - >见下文!
由于您似乎有一个无限量供应的相同盒子,优化订单是独立的!使用了这个事实,代码解决了优化单个订单的问题! (如果你有不同的盒子和基数,并且某些订单使用某些盒子不允许在其他订单中使用,这会改变)。独立解决遵循该理论。在实践中,当外语是python时,可能有一些优点可以同时对所有命令进行一次大解决(求解器会在一定程度上识别独立性;但很难说这是否可以尝试)。
此代码:
(安装在非Linux系统上可能很麻烦;在这种情况下:将其作为一种方法而不是现成的代码)
import numpy as np
import cvxpy as cvx
from timeit import default_timer as time
# Data
order_vols = [8, 4, 12, 18, 5, 2, 1, 4]
order_weights = [5, 3, 2, 5, 3, 4, 5, 6]
box_vol = 20
box_weight = 12
N_ITEMS = len(order_vols)
max_n_boxes = len(order_vols) # real-world: heuristic?
""" Optimization """
M = N_ITEMS + 1
# VARIABLES
box_used = cvx.Variable(max_n_boxes, boolean=True)
box_vol_content = cvx.Variable(max_n_boxes)
box_weight_content = cvx.Variable(max_n_boxes)
box_item_map = cvx.Variable((max_n_boxes, N_ITEMS), boolean=True)
# CONSTRAINTS
cons = []
# each item is shipped once
cons.append(cvx.sum(box_item_map, axis=0) == 1)
# box is used when >=1 item is using it
cons.append(box_used * M >= cvx.sum(box_item_map, axis=1))
# box vol constraints
cons.append(box_item_map * order_vols <= box_vol)
# box weight constraints
cons.append(box_item_map * order_weights <= box_weight)
problem = cvx.Problem(cvx.Minimize(cvx.sum(box_used)), cons)
start_t = time()
problem.solve(solver='CBC', verbose=True)
end_t = time()
print('time used (cvxpys reductions & solving): ', end_t - start_t)
print(problem.status)
print(problem.value)
print(box_item_map.value)
""" Reconstruct solution """
n_boxes_used = int(np.round(problem.value))
box_inds_used = np.where(np.isclose(box_used.value, 1.0))[0]
print('N_BOXES USED: ', n_boxes_used)
for box in range(n_boxes_used):
print('Box ', box)
raw = box_item_map[box_inds_used[box]]
items = np.where(np.isclose(raw.value, 1.0))[0]
vol_used = 0
weight_used = 0
for item in items:
print(' item ', item)
print(' vol: ', order_vols[item])
print(' weight: ', order_weights[item])
vol_used += order_vols[item]
weight_used += order_weights[item]
print(' total vol: ', vol_used)
print(' total weight: ', weight_used)
Welcome to the CBC MILP Solver
Version: 2.9.9
Build Date: Jan 15 2018
command line - ICbcModel -solve -quit (default strategy 1)
Continuous objective value is 0.888889 - 0.00 seconds
Cgl0006I 8 SOS (64 members out of 72) with 0 overlaps - too much overlap or too many others
Cgl0009I 8 elements changed
Cgl0003I 0 fixed, 0 tightened bounds, 19 strengthened rows, 0 substitutions
Cgl0004I processed model has 32 rows, 72 columns (72 integer (72 of which binary)) and 280 elements
Cutoff increment increased from 1e-05 to 0.9999
Cbc0038I Initial state - 9 integers unsatisfied sum - 2.75909
Cbc0038I Pass 1: suminf. 1.60000 (5) obj. 3 iterations 10
Cbc0038I Pass 2: suminf. 0.98824 (5) obj. 3 iterations 5
Cbc0038I Pass 3: suminf. 0.90889 (5) obj. 3.02 iterations 12
Cbc0038I Pass 4: suminf. 0.84444 (3) obj. 4 iterations 8
Cbc0038I Solution found of 4
Cbc0038I Before mini branch and bound, 60 integers at bound fixed and 0 continuous
Cbc0038I Full problem 32 rows 72 columns, reduced to 0 rows 0 columns
Cbc0038I Mini branch and bound did not improve solution (0.01 seconds)
Cbc0038I Round again with cutoff of 2.97509
Cbc0038I Pass 5: suminf. 1.62491 (7) obj. 2.97509 iterations 2
Cbc0038I Pass 6: suminf. 1.67224 (8) obj. 2.97509 iterations 7
Cbc0038I Pass 7: suminf. 1.24713 (5) obj. 2.97509 iterations 3
Cbc0038I Pass 8: suminf. 1.77491 (5) obj. 2.97509 iterations 9
Cbc0038I Pass 9: suminf. 1.08405 (6) obj. 2.97509 iterations 8
Cbc0038I Pass 10: suminf. 1.57481 (7) obj. 2.97509 iterations 12
Cbc0038I Pass 11: suminf. 1.15815 (6) obj. 2.97509 iterations 1
Cbc0038I Pass 12: suminf. 1.10425 (7) obj. 2.97509 iterations 17
Cbc0038I Pass 13: suminf. 1.05568 (8) obj. 2.97509 iterations 17
Cbc0038I Pass 14: suminf. 0.45188 (6) obj. 2.97509 iterations 15
Cbc0038I Pass 15: suminf. 1.67468 (8) obj. 2.97509 iterations 22
Cbc0038I Pass 16: suminf. 1.42023 (8) obj. 2.97509 iterations 2
Cbc0038I Pass 17: suminf. 1.92437 (7) obj. 2.97509 iterations 15
Cbc0038I Pass 18: suminf. 1.82742 (7) obj. 2.97509 iterations 8
Cbc0038I Pass 19: suminf. 1.31741 (10) obj. 2.97509 iterations 15
Cbc0038I Pass 20: suminf. 1.01947 (6) obj. 2.97509 iterations 12
Cbc0038I Pass 21: suminf. 1.57481 (7) obj. 2.97509 iterations 14
Cbc0038I Pass 22: suminf. 1.15815 (6) obj. 2.97509 iterations 1
Cbc0038I Pass 23: suminf. 1.10425 (7) obj. 2.97509 iterations 15
Cbc0038I Pass 24: suminf. 1.08405 (6) obj. 2.97509 iterations 1
Cbc0038I Pass 25: suminf. 3.06344 (10) obj. 2.97509 iterations 13
Cbc0038I Pass 26: suminf. 2.57488 (8) obj. 2.97509 iterations 10
Cbc0038I Pass 27: suminf. 2.43925 (7) obj. 2.97509 iterations 1
Cbc0038I Pass 28: suminf. 0.91380 (3) obj. 2.97509 iterations 6
Cbc0038I Pass 29: suminf. 0.46935 (3) obj. 2.97509 iterations 6
Cbc0038I Pass 30: suminf. 0.46935 (3) obj. 2.97509 iterations 0
Cbc0038I Pass 31: suminf. 0.91380 (3) obj. 2.97509 iterations 8
Cbc0038I Pass 32: suminf. 1.96865 (12) obj. 2.97509 iterations 23
Cbc0038I Pass 33: suminf. 1.40385 (6) obj. 2.97509 iterations 13
Cbc0038I Pass 34: suminf. 1.90833 (7) obj. 2.79621 iterations 16
Cbc0038I No solution found this major pass
Cbc0038I Before mini branch and bound, 42 integers at bound fixed and 0 continuous
Cbc0038I Full problem 32 rows 72 columns, reduced to 20 rows 27 columns
Cbc0038I Mini branch and bound improved solution from 4 to 3 (0.06 seconds)
Cbc0038I After 0.06 seconds - Feasibility pump exiting with objective of 3 - took 0.06 seconds
Cbc0012I Integer solution of 3 found by feasibility pump after 0 iterations and 0 nodes (0.06 seconds)
Cbc0001I Search completed - best objective 3, took 0 iterations and 0 nodes (0.06 seconds)
Cbc0035I Maximum depth 0, 0 variables fixed on reduced cost
Cuts at root node changed objective from 2.75 to 2.75
Probing was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Gomory was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Knapsack was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Clique was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
MixedIntegerRounding2 was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
FlowCover was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
TwoMirCuts was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Result - Optimal solution found
Objective value: 3.00000000
Enumerated nodes: 0
Total iterations: 0
Time (CPU seconds): 0.07
Time (Wallclock seconds): 0.05
Total time (CPU seconds): 0.07 (Wallclock seconds): 0.05
和更有趣的部分:
time used (cvxpys reductions & solving): 0.07740794896380976
optimal
3.0
[[0. 0. 0. 1. 0. 0. 1. 0.]
[0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 1. 1. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 1. 0. 0. 0. 0. 1.]
[0. 0. 0. 0. 0. 0. 0. 0.]]
N_BOXES USED: 3
Box 0
item 3
vol: 18
weight: 5
item 6
vol: 1
weight: 5
total vol: 19
total weight: 10
Box 1
item 0
vol: 8
weight: 5
item 4
vol: 5
weight: 3
item 5
vol: 2
weight: 4
total vol: 15
total weight: 12
Box 2
item 1
vol: 4
weight: 3
item 2
vol: 12
weight: 2
item 7
vol: 4
weight: 6
total vol: 20
total weight: 11
跟随您的维度的实例,如:
order_vols = [8, 4, 12, 18, 5, 2, 1, 4, 6, 5, 3, 2, 5, 11, 17, 15, 14, 14, 12, 20]
order_weights = [5, 3, 2, 5, 3, 4, 5, 6, 3, 11, 3, 8, 12, 3, 1, 5, 3, 5, 6, 7]
box_vol = 20
box_weight = 12
当然会有更多的工作:
Result - Optimal solution found
Objective value: 11.00000000
Enumerated nodes: 0
Total iterations: 2581
Time (CPU seconds): 0.78
Time (Wallclock seconds): 0.72
Total time (CPU seconds): 0.78 (Wallclock seconds): 0.72
N_BOXES USED: 11
使用不同的配方真的表明,有时很难说 - 先验是什么有帮助,有什么不有!
但是一个廉价的小对称利用变化应该始终有效(如果订单的大小不是太大:20是可以的;在30它可能开始是关键的)。这种方法被称为词典扰动(整数线性规划中的对称性|FrançoisMargot)。
我们可以添加一个变量修复(如果有解决方案,这个修复将始终存在相同成本的解决方案):
cons.append(box_item_map[0,0] == 1)
我们改变了目标:
# lex-perturbation
c = np.power(2, np.arange(1, max_n_boxes+1))
problem = cvx.Problem(cvx.Minimize(c * box_used), cons)
# small change in reconstruction due to new objective
n_boxes_used = int(np.round(np.sum(box_used.value)))
对于上述N=20
问题,我们现在实现:
Result - Optimal solution found
Objective value: 4094.00000000
Enumerated nodes: 0
Total iterations: 474
Time (CPU seconds): 0.60
Time (Wallclock seconds): 0.44
Total time (CPU seconds): 0.60 (Wallclock seconds): 0.44
time used (cvxpys reductions & solving): 0.46845901099732146
N_BOXES USED: 11