之前写在另一个账号上,结果那个账号登不上去了…于是我copy过来了。原文链接:(15条消息) 遗传算法解决柔性作业车间调度问题之编码_m0_57956994的博客-CSDN博客
但是原文代码有一些错误,所以直接看这篇就好了
一、遗传算法概念及基本步骤
在问题正式开始前,先简单介绍一下遗传算法。
遗传算法借用了生物遗传学的思想,以及自然界中的“物竞天择,适者生存”原则,将问题的解表示成“染色体”通过模拟自然选择、交叉、变异等操作,实现个体适应度的提高,不断迭代,逐步寻找最优解(或次优解)。遗传算法在求解问题时,从一组随机产生的初始种群开始搜索。种群中一个个体(individual)表示问题的一个解,称为染色体(chromosome)。种群通过连续的迭代进行进化,每一次迭代操作产生一代(generation)。在每一代中用适应度函数(fitnessfunction)或目标函数(objective function)对每个个体进行评价(evaluation)适应度值高的个体被选中的概率高。迭代过程的下一代称为子代(offspring),通过选择(selection)交叉(crossover)变异(mutation)等遗传算子产生子代。遗传算法有五个基本要素:编码和解码;种群初始化方法;适应度函数;遗传算子(主要包括选择、交叉、变异等);遗传参数设置(种群规模、遗传算子的概率等)等。
假设P表示种群规模,t表示当前代,P(t)和C(t)表示第t代的父代和子代,那么基本的遗传算法执行步骤如下:
步骤1:按照一定的初始化方法产生初始种群P(t)t=0。
步骤2:评价种群P(t)计算每个个体的适应度值。
步骤3:判断是否满足终止条件,如果满足则输出结果;否则转步骤4。
步骤4 :按照选择、交叉、变异等遗传算子产生子代C(t)。
步骤5:P(t)=C(t)转步骤2t=t+1。
图1所示为基本遗传算法的流程框图。
图1
二、柔性作业车间调度问题(FJSP)
柔性作业车间问题即FJSP问题,描述如下:n个工件(J1,J2,……,Jn)要在m台机器(M1,M2,……,Mm)上加工,每个工件包含一道或多道工序,工序顺序是预先确定的,每道工序可以在不同的机器上加工,工序的加工时间随加工机器的不同而不同;调度目标是为每道工序选择最合适的机器,确定每台机器上各道工序的最佳加工顺序及开工时间,使整个系统的某些性能指标达到最优。因此,柔性作业车间调度问题包含两个子问题:确定各工件的加工机器(机器选择子问题)和确定各个机器上的加工先后顺序(工序排序子问题)。
此外,在加工过程中还需要满足下面的约束条件:
(1)同一台机器在某一时刻只能加工一个工件。
(2)同一工件的同一道工序在同一时刻只能被一台机器加工。
(3)每个工件的每道工序一旦开始,加工便不能中断。
(4)不同工件之间具有相同的优先级。
(5)不同工件的工序之间没有先后约束,同一工件的工序之间有先后约束。
(6)所有工件在零时刻都可以被加工。
FJSP可以分为完全柔性车间作业问题(T-FJSP)和部分柔性车间作业问题(P-FJSP),在T-FJSP中,所有工件的每一道工序都可以在每一台机器上进行加工,而P-FJSP中,至少存在一道工序的加工机器只是所有机器中的某几台。为了更好的理解,放图。图2为T-FJSP,图3为P-FJSP。
图2
图3
三、FJSP的染色体编码
染色体编码就是将问题的解用染色体的形式表达出来,这也是遗传算法的关键。所编的码必须利于之后选择交叉变异过程的进行。在前面已经讲到,柔性作业车间调度问题包含两个子问题:确定各工件的加工机器(机器选择子问题)和确定各个机器上的加工先后顺序(工序排序子问题)。我们将其简称为机器选择(machine selection,MS)和工序排序(operations sequencing,OS)。以此为基础,进行染色体编码。见图4
图4
3.1机器选择部分(MS)和工序选择部分(OS)
本来想分开写,因为图是一起的,所以也一起写了,见图5。
机器选择部分的编码方式:
每个工件的每道工序按顺序排列,比如第一个工件的第一道工序为O11,第n个工件的m道工序为Onm。按顺序排列:O11,O12,O21,O22,O23……每个框代表的是每个工序所选择的机器,如第一个框代表的是O11选择的机器。框框里的数字则代表:此道工序能够选择的机器里的第几个机器。比如O11能够加工的机器为M1、M2、M3、M4、M5,4代表为这五台机器里的第四台机器,为M4,O12工序能够加工的机器为M2和M4,1则代表这两台机器里的第二台机器,为M2。以此生成MS这边的染色体。
工序排序部分的编码方式:
见图5右半部分,数字几则代表为第几个工件,数字出现的次数为工序的顺序。即数字1第一次出现为第一个工件的第一道工序,数字1第二次出现为第一个工件的第二道工序,数字2第3次出现为第二个工件的第三道工序。
图5
四、FJSP初始化
为了能够后续得到更优质的解,初始解不随机生成,而是经过一些处理,这样能够更好更快地得到后续优质解。现对初始解进行三种方法的处理(按照一定概率设置)。分别为全局选择、局部选择和随机选择。时间有限今天就先写全局选择。
4.1全局选择
设置一个数组,长度和机器数相等,数组的顺序依次对应加工机器的顺序,每一位上的值对应相应机器上的加工时间。随机在工件集中选择一个工件,从当前工件的第1道工序开始,将当前工序的可选加工机器的加工时间加上数组中对应的时间。从中选择最短的时间作为当前工序的加工机器,并且将数组更新,即把被选择的加工机器的加工时间加到数组中相应的位置上,以此类推,直到当前工件的所有工序的加工机器选择完毕后,然后再随机选择一个工件开始,直到所有工件的工序选择完毕为止。这样保证了最短加工机器先被选到而且保证了加工机器上的工作负荷平衡。
具体执行步骤如下:
步骤1:设置一个整型数组,长度等于机器总数m,依次为机器号顺序,数组对应机器[M1,M2,…Mm]上的总负荷。同时初始化数组中每一个元素值为零。
步骤2:随机从工件集中选择一个工件,同时选择当前工件的第1道工序。
步骤3:将当前工序的可选加工机器集中的加工机器的加工时间和数组中相应机器位置的时间数值相加,但不更新数组。
步骤4:从相加后的时间值中,选择最小的那台机器作为当前工序的加工机器将被选的机器在可选机器集中的顺序号设置为MS部分相应基因位的值。
步骤5:将当前被选择的加工机器的加工时间加到数组中相应位置机器的加工负荷中,同时更新数组作为下一次选择的依据。
步骤6:选择当前工件的下一道工序,重复执行步骤3到步骤5,直到当前工件的所有工序的加工机器选择完毕为止。
步骤7:从工件集中除去已被选择的工件,从剩下的工件集中随机选择一个工件,同时选择当前工件的第1道工序,重复执行步骤3到步骤6,直到工件集中的所有工件被选择完毕为止。
以图3所示P-FJSP为例,假设第一次随机选择到的加工工件是工件J1,第二次选择工件J2,则前四道工序的执行过程如图6所示。
图6
4.2 局部选择
局部选择同全局选择原理上基本一致,但是每次对一个工件选择完毕时,数组需要重新设置为0,且不存在随机选择工件,直接看一下具体步骤:
步骤1:设置一个整型数组,长度等于机器总数m,依次为机器号顺序,数组对应机器[M1,M2…Mm]上的总负荷。同时初始化数组中每一个元素值为零。
步骤2:选择工件集中的第1个工件,同时选择当前工件的第1道工序。
步骤3:将当前工序的可选加工机器集中的加工机器的加工时间和数组中相应机器位置的时间数值相加,但不更新数组。
步骤4:从相加后的时间值中,选择最小的那台机器作为当前工序的加工机器,将被选的机器在可选机器集中的顺序号设置为MS部分相应基因位的值。
步骤5:将当前被选择的加工机器的加工时间加到数组中相应位置机器的加工负荷中,同时更新数组作为下一次选择的依据。
步骤6:选择当前工件的下一道工序,重复执行步骤3到步骤5,直到当前工件的所有工序的加工机器选择完毕为止。
步骤7:将数组中的每一位元素的值重新设置为零。
步骤8:从工件集中除去已被选择的工件,选择工件集中下一个工件,同时选择当前工件的第一道工序,重复执行步骤3到步骤7,直到工件集中的所有工件被选择完毕为止。
以图3所示P-FJSP为例,依次选择工件J1和J2,执行过程如图7所示。
图7
4.3 随机选择
为了保证初始种群的多样性,初始种群应分散于解空间。一部分种群的机器选择部分采用随机初始化方法。RS与GS、LS的区别主要在于每一个基因位上的数字(即工序可选的机器号)是随机产生的。具体步骤如下:
步骤1:选择工件集中的第1个工件,同时选择当前工件的第1道工序。
步骤2:在[1,m]区间内随机产生一个数(m为对应工序可选择的机器数),即从当前工序的可选加工机器集中随机选择一个机器;同时将产生的随机数设置为MS染色体部分相应基因位的值。
步骤3:选择当前工件的下一道工序,执行步骤2,直到当前工件的所有工序的加工机器选择完毕为止。
步骤4:选择工件集中的下一个工件,重复执行步骤2到步骤3,直到工件集中的所有工件被选择完毕为止。
每个染色体的OS部分采用随机的方法生成。
4.4初始化代码
代码思路基本已经写在每一步的代码解释里了
import numpy as np
import random
class Encode:
def __init__(self,Matrix,Pop_size,J,J_num,M_num):
self.Matrix = Matrix #工件各工序对应的机器加工时间矩阵
self.GS_num = int(0.6*Pop_size) #全局选择初始化生成的种群数量
self.LS_num = int(0.2*Pop_size) #局部选择初始化生成的种群数量
self.RS_num = int(0.2*Pop_size) #随机选择初始化生成的种群数量
self.J = J #各工件对应的工序数量
self.J_num = J_num
self.M_num = M_num
self.Len_Chromo = 0
for i in J.values():
self.Len_Chromo += i #计算工序长度(如第一个工件有五道工序,第二个工件有十道工序,则长度为15)
def OS_List(self): #生成工序排列部分
OS_list = []
for key,value in self.J.items():
OS_add = [key for j in range(value)]
OS_list.extend(OS_add)
return OS_list
def CHS_Matrix(self,C_num): #生成初始化矩阵
return np.zeros((C_num,self.Len_Chromo),dtype = int)
def Site(self,Job, Operation):#确定第n个工件的第m道工序位于Chromo的哪个位置
O_num = 0
for i in range(len(self.J)):
if i == Job:
return O_num + Operation
else:
O_num = O_num + self.J[i+1]
return O_num
def Global_initial(self):
MS = self.CHS_Matrix(self.GS_num) #根据GS_num生成种群
OS_list = self.OS_List()
OS = self.CHS_Matrix(self.GS_num)
for i in range(self.GS_num):
Machine_time = np.zeros(self.M_num,dtype = int) #步骤1 生成一个整型数组,长度为机器数,且初始化每个元素为0
random.shuffle(OS_list) #生成工序排序部分
OS[i] = np.array(OS_list) #随机打乱后将其赋值给OS的某一行(因为有一个种群,第i则是赋值在OS的第i行,以此生成完整的OS)
GJ_list = [i_1 for i_1 in range(self.J_num)] #生成工件集
random.shuffle(GJ_list) #随机打乱工件集,为的是下一步可以随机抽出第一个工件
for g in GJ_list:#选择第一个工件(由于上一步已经打乱工件集,抽出第一个也是“随机”)
h = self.Matrix[g] #h为第一个工件包含的工序对应的时间矩阵
for j in range(len(h)):#从此工件的第一个工序开始
D = h[j] #D为第一个工件的第一个工序对应的时间矩阵
List_Machine_weizhi = []
for k in range(len(D)):#确定工序可用的机器位于第几个位置
Useing_Machine = D[k]
if Useing_Machine != 9999:
List_Machine_weizhi.append(k)
Machine_Select = []
for Machine_add in List_Machine_weizhi:#将机器时间数组对应位置和工序可选机器的时间相加
Machine_Select.append(Machine_time[Machine_add]+D[Machine_add])
Min_time = min(Machine_Select)#选出时间最小的机器
K = Machine_Select.index(Min_time)#第一次出现最小时间的位置,确定最小负荷为哪个机器,即为该工序可选择的机器里的第K个机器,并非Mk
I = List_Machine_weizhi[K] #所有机器里的第I个机器,即Mi
Machine_time[I] += Min_time #相应的机器位置加上最小时间
site = self.Site(g,j) #定位每个工件的每道工序的位置
MS[i][site] = K #即将每个工序选择的第K个机器赋值到每个工件的每道工序的位置上去 即生成MS的染色体
CHS1 = np.hstack((MS,OS))#将MS和OS整合为一个矩阵
return CHS1
def Local_initial(self):
MS = self.CHS_Matrix(self.LS_num) #根据LS_num生成局部选择的种群大小
OS_list = self.OS_List()
OS = self.CHS_Matrix(self.LS_num)
for i in range(self.LS_num):
random.shuffle(OS_list) #生成工序排序部分
OS[i] = np.array(OS_list) #随机打乱后将其赋值给OS的某一行(因为有一个种群,第i则是赋值在OS的第i行,以此生成完整的OS)
GJ_List = [i_1 for i_1 in range(self.J_num)] #生成工件集
for g in GJ_List: #选择第一个工件(注意:不用随机打乱了)
Machine_time = np.zeros(self.M_num,dtype = int) #设置一个整型数组 并初始化每一个元素为0,由于局部初始化,每个工件的所有工序结束后都要重新初始化,所以和全局初始化不同,此步骤应放在此处
h = self.Matrix[g] #h为第一个工件包含的工序对应的时间矩阵
for j in range(len(h)):#从选择的工件的第一个工序开始
D = h[j] #此工件第一个工序对应的机器加工时间矩阵
List_Machine_weizhi = []
for k in range(len(D)): #确定工序可用的机器位于第几个位置
Useing_Machine = D[k]
if Useing_Machine != 9999:
List_Machine_weizhi.append(k)
Machine_Select = []
for Machine_add in List_Machine_weizhi:#将机器时间数组对应位置和工序可选机器的时间相加
Machine_Select.append(Machine_time[Machine_add]+D[Machine_add])
Min_time = min(Machine_Select) #选出这些时间里最小的
K = Machine_Select.index(Min_time) #第一次出现最小时间的位置,确定最小负荷为哪个机器,即为该工序可选择的机器里的第K个机器,并非Mk
I = List_Machine_weizhi[K] #所有机器里的第I个机器,即Mi
Machine_time[I] += Min_time
site = self.Site(g,j) #定位每个工件的每道工序的位置
MS[i][site] = K #即将每个工序选择的第K个机器赋值到每个工件的每道工序的位置上去
CHS1 = np.hstack((MS, OS)) # 将MS和OS整合为一个矩阵
return CHS1
def Random_initial(self):
MS = self.CHS_Matrix(self.RS_num) #根据RS_num生成随机选择的种群大小
OS_list = self.OS_List()
OS = self.CHS_Matrix(self.RS_num)
for i in range(self.RS_num):
random.shuffle(OS_list)
OS[i] = np.array(OS_list)
GJ_List = [i_1 for i_1 in range(self.J_num)] #生成工件集
for g in GJ_List:#选择第一个工件
h = self.Matrix[g]
for j in range(len(h)):#选择第一个工件的第一个工序
D = h[j] #此工件第一个工序可加工的机器对应的时间矩阵
List_Machine_weizhi = []
for k in range(len(D)):
Useing_Machine = D[k]
if Useing_Machine != 9999:
List_Machine_weizhi.append(k)
number = random.choice(List_Machine_weizhi) #从可选择的机器编号中随机选择一个(此编号就是机器编号)
K = List_Machine_weizhi.index(number) #即为该工序可选择的机器里的第K个机器,并非Mk
site = self.Site(g,j) #定位每个工件的每道工序的位置
MS[i][site] = K #即将每个工序选择的第K个机器赋值到每个工件的每道工序的位置上去
CHS1 = np.hstack((MS,OS))
return CHS1