考虑体积重量的装箱问题(箱子装载平衡)— 基于遗传算法
1 前言
经典装箱问题要求把一定数量的物品放入容量相同的箱子中,在满足每个箱子装载物品的大小之和不超过箱子容量的约束下,最小化箱子数目。在上一篇装箱问题的博文【考虑体积重量的装箱问题(贪婪策略装箱)—— 基于遗传算法】中,以贪婪策略进行装箱,在最小化箱子数量的前提下,追求先装货物的箱子尽量装满(或称最后一个箱子的装载最少),本文研究另一个目标,即在最小化箱子使用数量的情况下,平衡箱子的装载量。
2 场景设计
已知货物的重量和体积,在满足箱子载重和容积约束的情况下,最小化箱子使用数量,并追求箱子装载量平衡。
3 遗传算法设计
3.1 算子设计
采用整数编码、锦标赛选择、顺序交叉、基本位变异、一对一生存者竞争。
染色体的整数编码如下图所示,使用箱子数目进行编码,编码对应货物序号,如以下编码中,1号箱子装载货物1,6,2号箱子装载货物2,7,3号箱子装载货物3,8,4号箱子装载货物4,5。这种编码需要先给出箱子的数量,箱子的数量可由需求量估计得到最小所需数量,当最小箱子数量无法找到满足约束的解时,再增加箱子的数量重新进行求解,代码如下所示。
#计算不考虑约束情况下最少说所需箱子数
boxNum = math.ceil(max(cargo_df.loc[:,"体积"].sum()/V,cargo_df.loc[:,"重量"].sum()/M))
#尝试进行求解
while True:
pop,fit,box,v_list,m_list = package_GA(cargo_df,generations,popsize,tournament_size,pc,pm,V,M,boxNum)
if fit>0:#大于0表示求得可行解
break
else:
boxNum += 1
3.2 适应度设计
Fit计算方法如下:
if num == boxNum:#判断是否满足约束
fit = 100/(np.var(v_sum) * np.var(m_sum))#构造方差积的倒数,常数100只是让结果数值显得大一点
else:
fit = -np.var(v_sum) * np.var(m_sum)#不满足约束做出惩罚
总体来说就是引入方差评价箱子装载的平衡,对于满足约束的情况,转换倒数后,fit越大说明箱子装载越平衡,而对于不满足约束的情况,fit越大表示不满足约束的情况较小,所以总的来看,倒数和“-”的作用就是使得满足约束或者不满足约束时,目标都是最大化fit。
3.3 遗传算法实现
# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
import random
import math
def tournament_select(pops,popsize,fit,tournament_size):
new_pops = []
while len(new_pops)<len(pops):
tournament_list = random.sample(range(0,popsize),tournament_size)
tournament_fit = [fit[i] for i in tournament_list]
#转化为df方便索引
tournament_df = pd.DataFrame([tournament_list,tournament_fit]).transpose().sort_values(by=1).reset_index(drop=True)
new_pop = pops[int(tournament_df.iloc[-1,0])]
new_pops.append(new_pop)
return new_pops
def crossover(popsize,parent1_pops,parent2_pops,pc):
child_pops = []
for i in range(popsize):
parent1 = parent1_pops[i]
parent2 = parent2_pops[i]
child = [-1 for i in range(len(parent1))]
if random.random() >= pc:
child = parent1.copy()#随机生成一个
random.shuffle(child)
else:
start_pos = random.randint(0,len(parent1)-1)
end_pos = random.randint(0,len(parent1)-1)
if start_pos>end_pos:start_pos,end_pos = end_pos,start_pos
child[start_pos:end_pos+1] = parent1[start_pos:end_pos+1].copy()
child[0:start_pos] = parent2[0:start_pos].copy()
child[end_pos+1:] = parent2[end_pos+1:].copy()
child_pops.append(child)
return child_pops
def mutate(populations,pm,boxNum):
"""
基本位变异
输入:populations-种群,pm-变异概率
返回:变异后种群-population_after_mutate
"""
population_after_mutate = []
for i in range(len(populations)):
pop = populations[i].copy()
for i in range(len(pop)):
if random.random() < pm:
randomList = list(range(1,boxNum+1))
randomList.remove(pop[i])
pop[i] = random.sample(randomList,1)[0]#随机生成另外一个箱子
population_after_mutate.append(pop)
return population_after_mutate
def package_calFitness(cargo_df,pop,max_v,max_m,boxNum):
'''
输入:cargo_df-货物信息,pop-个体,max_v-箱子容积,max_m-箱子在载重
输出:适应度-fit,boxes-解码后的个体
'''
boxes = [[] for i in range(boxNum)]
v_sum = [0]*boxNum
m_sum = [0]*boxNum
for j in range(len(pop)):
box_id = int(pop[j])-1
v_j = cargo_df[cargo_df['货物序号']==j]['体积'].iloc[0]
m_j = cargo_df[cargo_df['货物序号']==j]['重量'].iloc[0]
boxes[box_id].append(j)
v_sum[box_id] += v_j
m_sum[box_id] += m_j
num = 0#计数
for i in range(boxNum):
if (v_sum[i] <= max_v) & (m_sum[i] <= max_m):
num += 1
else:
break
if num == boxNum:
fit = 100/(np.var(v_sum) * np.var(m_sum))#构造方差和的倒数,100只是让结果数值显得大一点
else:
fit = -np.var(v_sum) * np.var(m_sum)#
return round(fit,4),boxes,v_sum,m_sum
def package_GA(cargo_df,generations,popsize,tournament_size,pc,pm,max_v,max_m,boxNum):
#初始化种群
cargo_list = list(cargo_df['货物序号'])
pops = [[random.randint(1,boxNum) for i in range(len(cargo_list))] for j in range(popsize)]#种群初始化
fit,boxes = [-1]*popsize,[-1]*popsize
v_sum,m_sum = [-1]*popsize,[-1]*popsize
for i in range(popsize):
fit[i],boxes[i],v_sum[i],m_sum[i] = package_calFitness(cargo_df,pops[i],max_v,max_m,boxNum)
best_fit = max(fit)
best_pop = pops[fit.index(max(fit))].copy()
best_box = boxes[fit.index(max(fit))].copy()
best_vsum = v_sum[fit.index(max(fit))].copy()
best_msum = m_sum[fit.index(max(fit))].copy()
if best_fit==1:return best_pop#1说明除最后一辆车都装满,已是最优解
iter = 0#迭代计数
while iter < generations:
pops1 = tournament_select(pops,popsize,fit,tournament_size)
pops2 = tournament_select(pops,popsize,fit,tournament_size)
new_pops = crossover(popsize,pops1,pops2,pc)
new_pops = mutate(new_pops,pm,boxNum)
iter += 1
new_fit,new_boxes = [-1]*popsize,[-1]*popsize#初始化
newv_sum,newm_sum = [-1]*popsize,[-1]*popsize
for i in range(popsize):
new_fit[i],new_boxes[i],newv_sum[i],newm_sum[i] = package_calFitness(cargo_df,new_pops[i],max_v,max_m,boxNum)#计算适应度
for i in range(len(pops)):
if fit[i] < new_fit[i]:
pops[i] = new_pops[i]
fit[i] = new_fit[i]
boxes[i] = new_boxes[i]
v_sum[i] = newv_sum[i]
m_sum[i] = newm_sum[i]
if best_fit < max(fit):#保留历史最优
best_fit = max(fit)
best_pop= pops[fit.index(max(fit))].copy()
best_box = boxes[fit.index(max(fit))].copy()
best_vsum = v_sum[fit.index(max(fit))].copy()
best_msum = m_sum[fit.index(max(fit))].copy()
print("第",iter,"代适应度最优值:",best_fit)
return best_pop,best_fit,best_box,best_vsum,best_msum
if __name__ == '__main__':
#数据
num = list(range(100))#货物编号
volumns = [1,6,7,8,1,2,3,1,8,8,10,1,9,3,4,3,5,7,4,6,5,5,9,5,6,3,9,9,6,3,4,2,1,3,5,9,6,6,8,5,6,2,7,9,5,1,7,5,10,6,
4,6,9,7,2,4,3,7,5,4,5,10,2,1,4,10,9,6,10,10,10,2,10,2,4,6,4,1,7,6,1,10,1,3,4,1,7,3,6,5,3,10,6,8,1,6,4,4,10,3]#体积
weight = [3,5,3,8,10,4,7,2,10,1,9,2,1,9,7,1,7,1,4,2,5,9,1,6,1,4,2,1,2,1,5,5,6,8,3,6,7,4,9,7,7,4,8,3,9,4,1,1,9,5,8,
4,10,3,5,1,7,8,8,2,8,7,1,10,3,3,8,2,4,6,8,3,5,8,10,5,7,5,7,1,9,1,5,9,9,2,10,2,9,3,7,10,5,1,2,1,9,8,6,9]#重量
cargo_df = pd.DataFrame({'货物序号':num, "体积":volumns, "重量":weight})
#参数
M,V= 100,100 #箱子载重容积
generations = 100
popsize = 40
tournament_size = 4
pc = 0.9
pm = 0.1
boxNum = math.ceil(max(cargo_df.loc[:,"体积"].sum()/V,cargo_df.loc[:,"重量"].sum()/M))
while True:
pop,fit,box,v_list,m_list = package_GA(cargo_df,generations,popsize,tournament_size,pc,pm,V,M,boxNum)
if fit>0:
break
else:
boxNum += 1
print("最优解:",box)
print("箱子载重分别为:",m_list)
print("箱子容积分别为:",v_list)
3.4 结果
最优解:[[6, 9, 14, 15, 18, 25, 27, 34, 48, 54, 55, 71, 74, 75, 78, 84, 89, 92], [10, 21, 28, 32, 35, 42, 50, 65, 66, 77, 79, 81, 88, 96], [0, 2, 3, 5, 17, 23, 43, 47, 52, 56, 58, 60, 64, 70, 82, 86], [1, 7, 12, 19, 20, 30, 31, 36, 39, 40, 46, 61, 76, 80, 83, 85, 87, 91], [8, 13, 16, 22, 29, 33, 37, 41, 49, 53, 57, 73, 90, 93, 95, 98], [4, 11, 24, 26, 38, 44, 45, 51, 59, 62, 63, 67, 68, 69, 72, 94, 97, 99]]
,总共需要6个箱子,箱子货物体积分别是[89, 88, 88, 89, 88, 88],重量分别是[85, 84, 88, 93, 83, 90]。
背包问题系列
考虑体积重量的装箱问题(贪婪策略装箱)— 基于遗传算法考虑体积重量的01背包问题—基于遗传算法
记录学习过程,欢迎指正