总的说明
具体题目可以直接去官网下载,子问题一主要是一个二维装箱问题,对大的矩形长宽都有限制,求解这类问题主要思路是排样规则和排样顺序两个方面。排样规则方面有BL、BLF、最低水平线和填充算法;排样顺序主要通过启发式算法来解决,比如粒子群算法、遗传算法等。笔者这次比赛使用了最低水平线搜索算法和基于遗传的改进填充算法。
相关链接:
roadef_2018这是2018年欧洲那边的相似比赛
宽度有限但长度无限的最低水平线搜索算法笔者在这个基础上改成了多块板材并且长宽都有限制的输出
代码段
import pandas as pd
import random
# 水平线类(起始x位置,终止x位置,高度)
class OutLine:
def __init__(self, origin, end, height):
self.origin = origin
self.end = end
self.height = height
def __str__(self):
return "OutLine:origin:{}, end:{}, height:{}".format(self.origin, self.end, self.height)
# 矩形物品类(宽度,高度)
class Product:
def __init__(self, w, h,num):
self.w = w
self.h = h
self.num=num
def __str__(self):
return "product:w:{}, h:{}, num:{}".format(self.w, self.h,self.num)
# 根据长度搜索矩形件(找出宽度小于目标长度的首个矩形件)
@staticmethod
def search_by_width(target_width, data):
for index, p in enumerate(data):
if p.w <= target_width:
return index
return None
# 根据长度搜索矩形件(找出宽度或高度小于目标长度的首个矩形件)
@staticmethod
def search_by_size(target_width, data):
for index, p in enumerate(data):
if p.w <= target_width or p.h <= target_width:
return index
return None
# 旋转90度并返回新对象
def rotate_new(self):
return Product(self.h, self.w,self.num)
# 布局类
class RectLayout:
def __init__(self, width, height,material,line_list=[]):
self.width = width
self.height = height
# 水平线集合(起始x位置,终止x位置,高度)
self.line_list = line_list
# 水平线(起始x位置,终止x位置,高度)
self.lowest_line = None
# 水平线索引(起始x位置,终止x位置,高度)
self.lowest_line_idx = 0
# 最终位置结果[[板材材质,板材号,矩形件编号,左下角横坐标,左下角纵坐标,矩形件宽度,矩形件高度], ...]
self.result_pos = []
# 板材利用率
self.ratio = 0.0
# 板材的序号
self.metal_num=0
#原片材质
self.m=material
# 初始化水平线集合(起始x位置,终止x位置,高度)
def init_line_list(self, origin, end, height):
init_line = OutLine(origin, end, height)
self.line_list = [init_line]
self.lowest_line = init_line
self.lowest_line_idx = 0
# 提升最低水平线
def enhance_line(self, index):
if len(self.line_list) > 1:
# 获取高度较低的相邻水平线索引,并更新水平线集
neighbor_idx = 0
if index == 0:
neighbor_idx = 1
elif index + 1 == len(self.line_list):
neighbor_idx = index - 1
else:
# 左边相邻水平线
left_neighbor = self.line_list[index - 1]
# 右边相邻水平线
right_neighbor = self.line_list[index + 1]
# 选择高度较低的相邻水平线,左右相邻水平线高度相同时,选择左边相邻的水平线
if left_neighbor.height < right_neighbor.height:
neighbor_idx = index - 1
elif left_neighbor.height == right_neighbor.height:
if left_neighbor.origin < right_neighbor.origin:
neighbor_idx = index - 1
else:
neighbor_idx = index + 1
else:
neighbor_idx = index + 1
# 选中的高度较低的相邻水平线
old = self.line_list[neighbor_idx]
if old.height >=self.height:
self.metal_num += 1
self.init_line_list(0, container_width, 0)
return
# 更新相邻水平线
if neighbor_idx < index:
self.line_list[neighbor_idx] = OutLine(old.origin, old.end + self.line_width(index), old.height)
else:
self.line_list[neighbor_idx] = OutLine(old.origin - self.line_width(index), old.end, old.height)
# 删除当前水平线
del self.line_list[index]
# 按位置更新水平线
def update_line_list(self, index, new_line):
self.line_list[index] = new_line
# 按位置插入水平线(插在某索引位置后面)
def insert_line_list(self, index, new_line):
new_lists = []
if len(self.line_list) == index + 1:
new_lists = self.line_list + [new_line]
else:
new_lists = self.line_list[:index + 1] + [new_line] + self.line_list[index + 1:]
self.line_list = new_lists
# 计算水平线宽度
def line_width(self, index):
line = self.line_list[index]
return line.end - line.origin
# 找出最低水平线(如果最低水平线不止一条则选取最左边的那条)
def find_lowest_line(self):
# 最低高度
lowest = min([_l.height for _l in self.line_list])
# 最低高度时,最小开始横坐标
origin = min([_l.origin for _l in self.line_list if _l.height == lowest])
for _idx, _line in enumerate(self.line_list):
if _line.height == lowest and _line.origin == origin:
self.lowest_line_idx = _idx
self.lowest_line = _line
# 清空水平线集合
def empty_line_list(self):
self.line_list.clear()
# 计算最高水平线高度,即所用板材最大高度
def cal_high_line(self):
max_height = max([ll.height for ll in self.line_list])
return max_height
# 将矩形物品排样
def packing(self, _pro: Product):
# 最低水平线宽度
lowest_line_width = self.line_width(self.lowest_line_idx)
# 更新水平线集
new_line1 = OutLine(self.lowest_line.origin, self.lowest_line.origin + _pro.w, self.lowest_line.height + _pro.h)
#当超过板材上限高度时,进行水平线重置并且将板材序号加1,返回失败表示当前排样未成功
if new_line1.height>self.height:
self.metal_num+=1
self.init_line_list(0, container_width, 0)
return False
# 对矩形件排样
self.result_pos.append([self.m,self.metal_num,_pro.num,self.lowest_line.origin, self.lowest_line.height, _pro.w, _pro.h])
new_line2 = OutLine(self.lowest_line.origin + _pro.w, self.lowest_line.origin + lowest_line_width, self.lowest_line.height)
self.update_line_list(self.lowest_line_idx, new_line1)
if lowest_line_width - _pro.w > 0:
self.insert_line_list(self.lowest_line_idx, new_line2)
return True
# 计算板材利用率
def cal_used_ratio(self):
# 计算板材利用率
used_area = 0
for _p in self.result_pos:
used_area += _p[5]*_p[6]
# # 板材使用最大高度
# max_high = self.cal_high_line()
# 利用率
# if max_high > 0:
self.ratio = round((used_area * 100) / (self.width * self.height* (self.metal_num+1)), 2)
return used_area, self.ratio
def reset(self):
#针对不同的个体,我们要重置计算其利用率作为损失函数
self.init_line_list(0, self.width, 0)
self.result_pos = []
self.ratio = 0.0
self.metal_num = 0
# 主方法
if __name__ == "__main__":
#设置文件路径
path='./data/a/data'
seq=4
a1 = pd.read_csv(path+'A'+str(seq)+'.csv')
# 板材宽度和高度32.8
container_width = 2440
container_height=1220
# 矩形物品数量
item_num = len(a1)
# 初始化矩形物品尺寸,也可以随机生成,保证width>height
width=list(a1.iloc[:,3])
height=list(a1.iloc[:,4])
idx=list(a1.iloc[:,0])
item_sizes=[list(y) for y in zip(width,height,idx)]
# 按面积对矩形物品尺寸排序,并且保存矩形件序号
_item_sizes = sorted(item_sizes, key=lambda x: x[0]*x[1], reverse=True)
random.shuffle(_item_sizes)
print(_item_sizes)
# # 排样序号
# ran = [i + 1 for i in range(item_num)]
# print(ran)
# 矩形物品列表
products = []
for idx in range(item_num):
products.append(Product(_item_sizes[idx][0], _item_sizes[idx][1],_item_sizes[idx][2]))
# 初始化布局类
layout = RectLayout(width=container_width,height=container_height,material=a1.iloc[0,1])
# 初始化水平线集
layout.init_line_list(0, container_width, 0)
while products:
# 最低水平线及其索引
layout.find_lowest_line()
# 可用长度
available_width = layout.line_width(layout.lowest_line_idx)
# 候选物品索引
# candidate_idx = Product.search_by_width(available_width, products)
candidate_idx = Product.search_by_size(available_width, products)
if candidate_idx is not None:
# 候选物品
pro = products[candidate_idx]
# 宽度放不下时,对矩形件进行旋转
if pro.w > available_width >= pro.h:
pro = pro.rotate_new()
# 将候选物品排样
flag = layout.packing(pro)
if flag == False:
continue
# 剔除已经排样的物品
products.pop(candidate_idx)
else:
# 最低水平线宽度小于要排样矩形宽度,提升最低水平线
layout.enhance_line(layout.lowest_line_idx)
# 计算板材利用率
_area, _ratio = layout.cal_used_ratio()
print("used_area: {}".format(_area))
print("ratio: {}%".format(_ratio))
df=pd.DataFrame(columns=['原片材质','原片序号','产品id','产品x坐标','产品y坐标','产品x方向长度','产品y方向长度'])
num=df.index.size
for idx,val in enumerate(layout.result_pos):
df.loc[num]=val
df.index = df.index + 1
print(df.head())
#结果保存
df.to_csv(path+'A'+str(seq)+'res.csv',index=None)