一、柔性作业车间调度问题描述
1、柔性车间调度问题(Flexible Jop Shop Problem Scheduling,FJSP)描述如下: n个工件(J1,J2,J3…,Jn)要在m台机器(M1,M2…Mm)上加工;每个工件包含一道或多道工序;工序顺序是预先确定的;每道工序可以在多台不同加工机器上进行加工;工序的加工时间随加工机器的不同而不同;调度目标是为每到工序选择最合适的机器,确定每台机器上各道工序最佳加工顺序及开工时间,使整个系统的某些性能指标达到最优。
2、FJSP通常有如下约束条件:
①同一台机器在某一时刻只能加工一个工件;
②同一个工件的统同一道工序在同一时刻只能被一台机器加工;
③每一个工件的每道工序一旦开始,加工便不能中断;
④不同工件之间具有相同的优先级;
⑤不同工件的工序之间没有先后约束,同一工件的工序之间有先后约束;
⑥所有工件在零时刻都可以被加工。
3、FJSP包含的两个子问题: ①确定各工件的加工机器(机器选择子问题);②确定各个机器上的加工先后顺序(工序排序子问题)
参考资料:
[1]高亮, 张国辉, 王晓娟. 柔性作业车间调度智能算法及其应用[M]. 华中科技大学出版社, 2012.
[2]张国辉, 高亮, 李培根,等. 改进遗传算法求解柔性作业车间调度问题[J]. 机械工程学报, 2009, 45(7):7.
二、遗传算法
遗传算法借用了生物遗传学的思想,以及自然界中的“物竞天择,适者生存”原则,将问题的解表示成“染色体”,通过模拟自然选择、交叉、变异等操作,实现个体适应度的提高,不断迭代,逐步寻找最优解(或次优解)。遗传算法在求解问题时,从一组随机产生的初始种群开始搜索。种群中一个个体(individual)表示问题的一个解,称为染色体(chromosome)。种群通过连续的迭代进行进化,每一次迭代操作产生一代(generation)。在每一代中用适应度函数(fitness function)或目标函数(objective function)对每个个体进行评价(evaluation),适应度值高的个体被选中的概率高。迭代过程的下一代称为子代(offspring),通过选择(selection)、交叉(crosscover)、变异(mutation)等遗传算子产生子代。遗传算法有五个基本要素:编码和解码;种群初始化方法;适应度函数;遗传算子(主要包括选择、交叉、变异等);遗传参数设置(种群规模、遗传算子的概率等)等。如下图所示为基本遗传算法的流程框图。
三、FJSP的染色体编码
FJSP包括两个子问题:机器选择和工序排序。机器选择解决每道工序在可选机器集中的哪台机器上进行加工的问题;工序排序解决所有工序确定加工机器后的排序和开工时间问题。本书结合分段编码易表现、易操作等特点,对以往编码方法进行了改进,设计了一种整数编码MSOS,由两部分组成:机器选择部分(machines selection,MS)和工序排序部分(operations sequences,OS),如下图所示。
①机器选择部分:每个基因位用整数表示,依次按照工件和工件工序的顺序进行排列,每个整数表示当前工序选择的加工机器在可选机器集中的顺序编号,并不是对应的机器号,这保证了后续交叉、变异等操作后产生的解仍然是可行解。
②工序排序部分:当工序的加工机器确定后,对工序的排序类似一般JSP。此部分采用Gen提出的基于工序的编码方式进行编码,染色体长度等于所有工件的工序之和T0。每一个基因用工件号直接编码,工件号出现的顺序表示该工件工序间的先后加工顺序,即对染色体从左到右进行编译,对于第h次出现的工件号,表示该工件j的第h道工序,并且工件号的出现次数等于该工件的工序总数hj。
四、FJSP的初始化策略
1、三种初始化策略: 针对FJSP特点,结合以上方法的优点,本书提出一种GRL机器选择方法,包括:全局选择(global selection,GS)、局部选择(local selection,LS)和随机选择(random selection,RS)。GS和LS主要是为了考虑机器选择的负荷问题,使各台被选择的机器的工作负荷尽量平衡,充分提高机器的利用率。RS主要考虑尽量使初始种群分散地分布于整个解空间。通过三者的有机结合,提高初始解在机器选择部分中解的质量。
①全局选择(GS):设置一个数组,长度和机器数相等,数组的顺序依次对应加工机器的顺序,每一位上的值对应相应机器上的加工时间。随机在工件集中选择一个工件,从当前工件的第1道工序开始,将当前工序的可选加工机器的加工时间加上数组中对应的时间。从中选择最短的时间作为当前工序的加工机器,并且将数组更新,即把被选择的加工机器的加工时间加到数组中相应的位置上,以此类推,直到当前工件的所有工序的加工机器选择完毕后,然后再随机选择一个工件开始,直到所有工件的工序选择完毕为止。全局选择流程图如下:
②局部选择(LS):局部选择同全局选择原理上基本一致,但是每次对一个工件选择完毕时,数组需要重新设置为零,不存在随机选择工件。设置一个数组,长度和机器数相等,选择工件集中第1个工件,选择当前工件的第1道工序开始,将当前工序的可选加工机器的加工时间加上数组中对应的时间。从中选择最短的时间作为当前工序的加工机器,
并且将数组更新,即把被选择的加工机器的加工时间加到数组中相应的位置上,以此类推,直到当前工件的所有工序的加工机器选择完毕后,然后数组每一位重新设置为零,选择下一个工件,直到所有工件选择完毕为止。这样保证了一个工件的工序中优先加工时间最短或说选择机器负荷最小的加工机器进行加工。局部选择流程图如下:
全局选择与局部选择的两个不同点:
a.全局选择是随机选择工件,而局部选择是按照顺序选择工件;
b.全局选择的机器时间数组不用清零,而局部选择在遇到新的工件时需要将机器时间数组清零。
③随机选择(RS):为保证初始种群的多样性,初始种群应分散于解空间。一部分种群的机器选择部分采用随机初始化方法。RS与GS、LS的主要区别在于每一个基因位上的数字即表示工序可选机器集中的顺序号是随机产生的。
每个染色体的OS部分采用随机的方法生成。
五、初始化以及编码部分代码
import numpy as np
import random
class Encode:
def __init__(self, Matrix, popSize, J, jobNum, machineNum):
'''
:param Matrix: 工件各个工序对应的加工时间矩阵
:param popSize: 种群数量
:param J: 工序数 是一个dict{1:2,2:3} 工件1有2道工序 工件2有3道工序
:param jobNum: 工件数
:param machineNum: 机器数
'''
self.Matrix = Matrix
self.GSNum = int(0.6 * popSize) # 全局初始化生成的种群数量
self.LSNum = int(0.2 * popSize) # 局部初始化生成的种群数量
self.RSNum = int(0.2 * popSize) # 随机选择初始化生成的种群数量
self.J = J # 各工件对应的工序数
self.jobNum = jobNum # 工件数
self.machineNum = machineNum # 机器数
self.lenChrome = 0
for cnt in self.J.values(): # 计算MS的长度 工序之和
self.lenChrome += cnt
def OSList(self):
'''
生成工序排序部分
:param self:
:param J:工序数 是一个dict{1:2,2:3} 工件1有2道工序 工件2有3道工序
:return:
'''
osList = []
for k, v in self.J.items():
osAdd = []
for i in range(v):
osList.append(k)
osList.extend(osAdd)
random.shuffle(osList) # 工序排序部分随机生成即可
temp = []
for t in range(len(osList)):
temp.append(osList[t])
OS = np.array(temp) # 将OS列表转为Numpy
return OS
def Site(self, job, op):
'''
确定Oij在MS染色体上的位置
:param job:
:param op:
:return:
'''
O_num = 0
for i in range(1, len(self.J) + 1):
if i == job:
return O_num + op
else:
O_num = O_num + self.J[i]
return O_num
def initPopulation(self, num):
'''
初始化种群矩阵
:return:
'''
return np.zeros((num, self.lenChrome), dtype=int)
def global_selection(self):
'''
全局选择初始化策略
:return:
'''
OS = self.initPopulation(self.GSNum) # 生成工序排序部分矩阵
MS = self.initPopulation(self.GSNum) # 生成机器选择部分矩阵
for it in range(self.GSNum): # 生成种群 有GSNum个个体
OS[it] = self.OSList()
machineTimearr = np.zeros(self.machineNum, dtype=int) # 1.初始化一个整型数组 长度为机器数 初始化为0 机器时间数组
jobList = [x + 1 for x in range(self.jobNum)] # 初始化工件集 下标从1开始
random.shuffle(jobList) # 将工件集打乱
for jobi in jobList: # 因为工件集已经打乱,所以选择工件也是随机的
timei = self.Matrix[jobi - 1] # 工件jobi各工序对应的加工时间
for i in range(1, len(timei) + 1): # 从工件的第一道工序开始
operation = timei[i - 1] # 第i道工序对应的时间矩阵
machinePosition = []
for j in range(1, self.machineNum + 1): # machinePosition存储当前可用机器的机器号
if operation[j - 1] != 9999:
machinePosition.append(j)
sumTime = []
for machineAdd in machinePosition:
sumTime.append(machineTimearr[machineAdd - 1] + operation[machineAdd - 1]) # 将时间相加得到对应加工时间
minTime = min(sumTime) # 选出时间最小的机器
k = sumTime.index(minTime) + 1 # 第一次出现最小时间的位置
# 将sumTime和machinePosition进行映射 表示加工时间在数组中的位置
nvs = zip(sumTime, machinePosition)
nvdict = dict((sumTime, machinePosition) for sumTime, machinePosition in nvs)
machineTimearr[nvdict[minTime] - 1] = minTime # 更新机器时间数组
site = self.Site(jobi, i) # 定位工件jobi的第i道工序
MS[it][site - 1] = k
CHS_GS = np.hstack([MS, OS]) # 将MS和OS拼接
return CHS_GS
def local_selection(self):
'''
局部选择初始化策略
:return:
'''
OS = self.initPopulation(self.LSNum) # 生成工序排序部分矩阵
MS = self.initPopulation(self.LSNum) # 生成机器选择部分矩阵
for it in range(self.LSNum):
OS[it] = self.OSList()
jobList = [x + 1 for x in range(self.jobNum)] # 初始化工件集
for jobi in jobList: # 随机选择 选择工件集中的第一个工件
machineTimearr = np.zeros(self.machineNum, dtype=int)
timei = self.Matrix[jobi - 1] # 工件的加工时间矩阵
for i in range(1, len(timei) + 1): # 从第一道工序开始
operation = timei[i - 1]
machinePosition = []
for j in range(1, self.machineNum + 1):
if operation[j - 1] != 9999:
machinePosition.append(j)
sumTime = []
for machineAdd in machinePosition:
sumTime.append(machineTimearr[machineAdd - 1] + operation[machineAdd - 1]) # 将时间相加得到对应加工时间
minTime = min(sumTime) # 选出时间最小的机器
k = sumTime.index(minTime) + 1 # 第一次出现最小时间的位置
# 将sumTime和machinePosition进行映射 表示加工时间在数组中的位置
nvs = zip(sumTime, machinePosition)
nvdict = dict((sumTime, machinePosition) for sumTime, machinePosition in nvs)
# 更新机器时间数组
machineTimearr[nvdict[minTime] - 1] = minTime
site = self.Site(jobi, i) # 定位工件jobi的第i道工序
MS[it][site - 1] = k
CHS_LS = np.hstack([MS, OS]) # 将MS和OS拼接
return CHS_LS
def random_selection(self):
'''
随机选择初始化策略
:return:
'''
OS = self.initPopulation(self.RSNum) # 生成工序排序部分矩阵
MS = self.initPopulation(self.RSNum) # 生成机器选择部分矩阵
for it in range(self.RSNum):
OS[it] = self.OSList() # 生成工序排序部分
jobList = [x + 1 for x in range(self.jobNum)] # 初始化工件集
random.shuffle(jobList) # 将工件集打乱
for jobi in jobList: # 随机选择一个工件
timei = self.Matrix[jobi - 1] # 当前工件的加工时间矩阵
for i in range(1, len(timei) + 1): # 选择当前工件的第一道工序
operation = timei[i - 1]
machinePosition = []
for j in range(1, self.machineNum + 1):
if operation[j - 1] != 9999:
machinePosition.append(j)
machineNum = random.choice(machinePosition) # 随机选择一台机器
k = machinePosition.index(machineNum) + 1
site = self.Site(jobi, i)
MS[it][site - 1] = k
CHS_RS = np.hstack([MS, OS]) # 将MS和OS拼接
return CHS_RS
if __name__ == '__main__':
matrix = [[[2, 6, 5, 3, 4],
[9999, 8, 9999, 4, 9999],
[4, 9999, 3, 6, 2]],
[[3, 9999, 6, 9999, 5],
[4, 6, 5, 9999, 9999],
[9999, 7, 11, 5, 8]]]
J = {1: 3, 2: 3} # 工件对应的工序数 工件1有3道工序 工件3有3道工序
e = Encode(matrix, 10, J, 2, 5) # 加工时间矩阵 种群规模 工件对应的工序 工件数 机器数
CHS_GS = e.global_selection()
CHS_LS = e.local_selection()
CHS_RS = e.random_selection()
CHS = np.vstack([CHS_GS, CHS_LS, CHS_RS])
print(CHS)
代码思路:根据上面几个图提供的思路先写出一个个体(一条染色体)的编码方案,然后根据书上给的算例来进行测试,如果要生成种群的话在最外层加个循环就可以。
部分代码参考了:遗传算法解决柔性作业车间调度问题
感谢大佬hh~