This commit is contained in:
Hgq 2025-08-21 13:31:44 +08:00
commit b91c034162
26 changed files with 851 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.idea/
.git/
__pycache__/

103
Decode.py Normal file
View File

@ -0,0 +1,103 @@
import numpy as np
from Job import Job
from Machine import Machine_Time_window
class Decode:
def __init__(self, J, Processing_time, M_num):
"""
:param J: 各工件对应的工序数字典
:param Processing_time: 各工件的加工时间矩阵
:param M_num: 加工机器数
"""
self.Processing_time = Processing_time
self.M_num = M_num
self.J = J
self.Machines = [] # 存储机器类
self.Scheduled = [] # 已经排产过的工序
self.fitness = 0 # 适应度
self.Machine_State = np.zeros(M_num, dtype=int) # 在机器上加工的工件是哪个
self.Jobs = [] # 存储工件类
for j in range(M_num):
self.Machines.append(Machine_Time_window(j))
for k, v in J.items():
self.Jobs.append(Job(k, v))
# 时间顺序矩阵和机器顺序矩阵根据基因的MS部分转换
def Order_Matrix(self, MS):
JM = []
T = []
Ms_decompose = []
Site = 0
# 按照基因的MS部分按工件序号划分
for S_i in self.J.values():
Ms_decompose.append(MS[Site:Site + S_i])
Site += S_i
for i in range(len(Ms_decompose)): # len(Ms_decompose)表示工件数
JM_i = []
T_i = []
for j in range(len(Ms_decompose[i])): # len(Ms_decompose[i])表示每一个工件对应的工序数
O_j = self.Processing_time[i][j] # 工件i的工序j可选择的加工时间列表
M_ij = []
T_ij = []
for Mac_num in range(len(O_j)): # 寻找MS对应部分的机器时间和机器顺序
if O_j[Mac_num] != 9999:
M_ij.append(Mac_num)
T_ij.append(O_j[Mac_num])
else:
continue
JM_i.append(M_ij[Ms_decompose[i][j]])
T_i.append(T_ij[Ms_decompose[i][j]])
JM.append(JM_i)
T.append(T_i)
return JM, T
# 确定工序的最早加工时间
def Earliest_Start(self, Job, O_num, Machine):
P_t = self.Processing_time[Job][O_num][Machine]
last_O_end = self.Jobs[Job].Last_Processing_end_time # 上道工序结束时间
Selected_Machine = Machine
M_window = self.Machines[Selected_Machine].Empty_time_window() # 当前机器的空格时间
M_Tstart = M_window[0]
M_Tend = M_window[1]
M_Tlen = M_window[2]
Machine_end_time = self.Machines[Selected_Machine].End_time
ealiest_start = max(last_O_end, Machine_end_time)
if M_Tlen is not None: # 此处为全插入时窗
for le_i in range(len(M_Tlen)):
# 当前空格时间比加工时间大可插入
if M_Tlen[le_i] >= P_t:
# 当前空格开始时间比该工件上一工序结束时间大可插入该空格,以空格开始时间为这一工序开始
if M_Tstart[le_i] >= last_O_end:
ealiest_start = M_Tstart[le_i]
break
# 当前空格开始时间比该工件上一工序结束时间小但空格可满足插入该工序,以该工序的上一工序的结束为开始
if M_Tstart[le_i] < last_O_end and M_Tend[le_i] - last_O_end >= P_t:
ealiest_start = last_O_end
break
M_Ealiest = ealiest_start # 当前工件当前工序的最早开始时间
End_work_time = M_Ealiest + P_t # 当前工件当前工序的结束时间
return M_Ealiest, Selected_Machine, P_t, O_num, last_O_end, End_work_time
# 解码操作
def decode(self, CHS, Len_Chromo):
"""
:param CHS: 种群基因
:param Len_Chromo: MS与OS的分解线
:return: 适应度即最大加工时间
"""
MS = list(CHS[0:Len_Chromo])
OS = list(CHS[Len_Chromo:2 * Len_Chromo])
Needed_Matrix = self.Order_Matrix(MS)
JM = Needed_Matrix[0]
for i in OS:
Job = i
O_num = self.Jobs[Job].Current_Processed() # 现在加工的工序
Machine = JM[Job][O_num] # 用基因的OS部分的工件序号以及工序序号索引机器顺序矩阵的机器序号
Para = self.Earliest_Start(Job, O_num, Machine)
self.Jobs[Job]._Input(Para[0], Para[5], Para[1]) # 工件完成该工序
if Para[5] > self.fitness:
self.fitness = Para[5]
self.Machines[Machine]._Input(Job, Para[0], Para[2], Para[3]) # 机器完成该工件该工序
return self.fitness

136
Encode.py Normal file
View File

@ -0,0 +1,136 @@
import random
import numpy as np
class Encode:
def __init__(self, Matrix, Pop_size, J, J_num, M_num):
"""
:param Matrix: 机器加工时间矩阵
:param Pop_size: 种群数量
:param J: 各工件对应的工序数
:param J_num: 工件数
:param M_num: 机器数
"""
self.Matrix = Matrix
self.J = J
self.J_num = J_num
self.M_num = M_num
self.CHS = []
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.Len_Chromo = 0
for i in J.values():
self.Len_Chromo += i
# 生成工序准备的部分
def OS_List(self):
OS_list = []
for k, v in self.J.items():
OS_add = [k - 1 for j in range(v)]
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):
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

151
GA.py Normal file
View File

@ -0,0 +1,151 @@
import itertools
import random
import numpy as np
from Decode import Decode
from Instance import *
class GA():
def __init__(self):
self.Pop_size = 400 # 种群数量
self.Pc = 0.8 # 交叉概率
self.Pm = 0.3 # 变异概率
self.Pv = 0.5 # 选择何种方式进行交叉的概率阈值
self.Pw = 0.95 # 选择何种方式进行变异的概率阈值
self.Max_Itertions = 100 # 最大迭代次数
# 适应度
def fitness(self, CHS, J, Processing_time, M_num, Len):
Fit = []
for i in range(len(CHS)):
d = Decode(J, Processing_time, M_num)
Fit.append(d.decode(CHS[i], Len))
return Fit
# 机器部分交叉
def machine_cross(self, CHS1, CHS2, T0):
"""
:param CHS1: 机器选择部分的基因1
:param CHS2: 机器选择部分的基因2
:param T0: 工序总数
:return: 交叉后的机器选择部分的基因
"""
T_r = [j for j in range(T0)]
r = random.randint(1, 10) # 在区间[1,T0]内产生一个整数r
random.shuffle(T_r)
R = T_r[0:r] # 按照随机数r产生r个互不相等的整数
OS_1 = CHS1[O_num:2 * T0]
OS_2 = CHS2[O_num:2 * T0]
MS_1 = CHS2[0:T0]
MS_2 = CHS1[0:T0]
for i in R:
K, K_2 = MS_1[i], MS_2[i]
MS_1[i], MS_2[i] = K_2, K
CHS1 = np.hstack((MS_1, OS_1))
CHS2 = np.hstack((MS_2, OS_2))
return CHS1, CHS2
# 工序部分交叉
def operation_cross(self, CHS1, CHS2, T0, J_num):
"""
:param CHS1: 工序选择部分的基因1
:param CHS2: 工序选择部分的基因2
:param T0: 工序总数
:param J_num: 工件总数
:return: 交叉后的工序选择部分的基因
"""
OS_1 = CHS1[T0:2 * T0]
OS_2 = CHS2[T0:2 * T0]
MS_1 = CHS1[0:T0]
MS_2 = CHS2[0:T0]
Job_list = [i for i in range(J_num)]
random.shuffle(Job_list)
r = random.randint(1, J_num - 1)
Set1 = Job_list[0:r]
new_os = list(np.zeros(T0, dtype=int))
for k, v in enumerate(OS_1):
if v in Set1:
new_os[k] = v + 1
for i in OS_2:
if i not in Set1:
Site = new_os.index(0)
new_os[Site] = i + 1
new_os = np.array([j - 1 for j in new_os])
CHS1 = np.hstack((MS_1, new_os))
CHS2 = np.hstack((MS_2, new_os))
return CHS1, CHS2
# 机器部分变异
def machine_variation(self, CHS, O, T0, J):
"""
:param CHS: 机器选择部分的基因
:param O: 加工时间矩阵
:param T0: 工序总数
:param J: 各工件加工信息
:return: 变异后的机器选择部分的基因
"""
Tr = [i_num for i_num in range(T0)]
MS = CHS[0:T0]
OS = CHS[T0:2 * T0]
# 机器选择部分
r = random.randint(1, T0 - 1) # 在变异染色体中选择r个位置
random.shuffle(Tr)
T_r = Tr[0:r]
for num in T_r:
T_0 = [j for j in range(T0)]
K = []
Site = 0
for k, v in J.items():
K.append(T_0[Site:Site + v])
Site += v
for i in range(len(K)):
if num in K[i]:
O_i = i
O_j = K[i].index(num)
break
Machine_using = O[O_i][O_j]
Machine_time = []
for j in Machine_using:
if j != 9999:
Machine_time.append(j)
Min_index = Machine_time.index(min(Machine_time))
MS[num] = Min_index
CHS = np.hstack((MS, OS))
return CHS
# 工序部分变异
def operation_variation(self, CHS, T0, J_num, J, O, M_num):
"""
:param CHS: 工序选择部分的基因
:param T0: 工序总数
:param J_num: 工件总数
:param J: 各工件加工信息
:param O: 加工时间矩阵
:param M_num: 机器总数
:return: 变异后的工序选择部分的基因
"""
MS = CHS[0:T0]
OS = list(CHS[T0:2 * T0])
r = random.randint(1, J_num - 1)
Tr = [i for i in range(J_num)]
random.shuffle(Tr)
Tr = Tr[0:r]
J_os = dict(enumerate(OS)) # 随机选择r个不同的基因
J_os = sorted(J_os.items(), key=lambda d: d[1])
Site = []
for i in range(r):
Site.append(OS.index(Tr[i]))
A = list(itertools.permutations(Tr, r))
A_CHS = []
for i in range(len(A)):
for j in range(len(A[i])):
OS[Site[j]] = A[i][j]
C_I = np.hstack((MS, OS))
A_CHS.append(C_I)
Fit = []
for i in range(len(A_CHS)):
d = Decode(J, O, M_num)
Fit.append(d.decode(CHS, T0))
return A_CHS[Fit.index(min(Fit))]

26
Instance.py Normal file
View File

@ -0,0 +1,26 @@
"""
Processing_time工件各工序对应各机器加工时间矩阵
J各工件对应的工序数字典
M_num加工机器数
O_num加工工序数
J_num工件个数
"""
CKS201 = [[10, 9, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999],
[9999, 9999, 14, 16, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999],
[9999, 9999, 9999, 9999, 15, 25, 21, 9999, 9999, 9999, 9999, 9999],
[9999, 9999, 9999, 9999, 9999, 9999, 9999, 9, 13, 15, 24, 9999],
[9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 10]]
CKS301 = [[12, 9, 10, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999],
[9999, 9999, 9999, 16, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999],
[9999, 9999, 9999, 9999, 15, 9999, 9999, 9999, 9999, 9999, 9999, 9999],
[9999, 9999, 9999, 9999, 9999, 27, 22, 9999, 9999, 9999, 9999, 9999],
[9999, 9999, 9999, 9999, 9999, 9999, 9999, 21, 17, 9999, 9999, 9999],
[9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 19, 9999, 9999],
[9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 17, 9999],
[9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 18]]
Processing_time = [CKS201] * 3 + [CKS301] * 4
J = {i: (5 if i < 4 else 8) for i in range(1, 8)}
M_num = 12
O_num = 3 * 5 + 4 * 8
J_num = 7

33
Job.py Normal file
View File

@ -0,0 +1,33 @@
class Job:
def __init__(self, Job_index, Operation_num):
"""
:param Job_index: 工件序号
:param Operation_num: 工序数
"""
self.Job_index = Job_index
self.Operation_num = Operation_num
self.Processed = [] # 记录工件工序的加工进度
self.J_start = [] # 记录工件工序的开始时间
self.J_end = [] # 记录工件工序的结束时间
self.J_machine = [] # 记录工件工序选择的机器
self.Last_Processing_Machine = None # 工件当前工序的加工机器
self.Last_Processing_end_time = 0 # 工件当前工序的结束时间
# 工件已经加工的工序数
def Current_Processed(self):
return len(self.Processed)
# 工件某工序开始加工
def _Input(self, W_Eailiest, End_time, Machine):
"""
:param W_Eailiest: 工件当前工序的开始时间
:param End_time: 工件当前工序的结束时间
:param Machine: 工件当前工序选择的加工机器
:return:
"""
self.Last_Processing_Machine = Machine
self.Last_Processing_end_time = End_time
self.Processed.append(1)
self.J_start.append(W_Eailiest)
self.J_end.append(End_time)
self.J_machine.append(Machine)

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Incalos
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

53
Machine.py Normal file
View File

@ -0,0 +1,53 @@
class Machine_Time_window:
def __init__(self, Machine_index):
"""
:param Machine_index: 加工机器序号
"""
self.Machine_index = Machine_index
self.assigned_task = [] # 机器分配的任务记录,包括工件序号以及工序序号
self.O_start = [] # 各任务工序的开始时间记录
self.O_end = [] # 各任务工序的结束时间记录
self.End_time = 0
# 机器的哪些时间窗是空的,此处只考虑内部封闭的时间窗,类似甘特图每一行往后叠加
def Empty_time_window(self):
"""
:return: 空格时间的开始结束时长
"""
time_window_start = []
time_window_end = []
len_time_window = []
if self.O_end is None:
pass
elif len(self.O_end) == 1:
if self.O_start[0] != 0:
time_window_start = [0]
time_window_end = [self.O_start[0]]
elif len(self.O_end) > 1:
if self.O_start[0] != 0:
time_window_start.append(0)
time_window_end.append(self.O_start[0])
time_window_start.extend(self.O_end[:-1]) # 因为使用时间窗的结束点就是空时间窗的开始点
time_window_end.extend(self.O_start[1:])
if time_window_end is not None:
len_time_window = [time_window_end[i] - time_window_start[i] for i in range(len(time_window_end))]
return time_window_start, time_window_end, len_time_window
# 机器投入新一轮加工
def _Input(self, Job, M_Ealiest, P_t, O_num):
if self.O_end != []:
# 如果当前机器加工的最早开始时间比记录的大,则依次往后排任务,否则将任务插入中间的分配任务记录
if self.O_start[-1] > M_Ealiest:
for i in range(len(self.O_end)):
if self.O_start[i] >= M_Ealiest:
self.assigned_task.insert(i, [Job + 1, O_num + 1])
break
else:
self.assigned_task.append([Job + 1, O_num + 1])
else:
self.assigned_task.append([Job + 1, O_num + 1])
self.O_start.append(M_Ealiest)
self.O_start.sort()
self.O_end.append(M_Ealiest + P_t)
self.O_end.sort()
self.End_time = self.O_end[-1]

76
README.md Normal file
View File

@ -0,0 +1,76 @@
# Flexible Job-shop Scheduling Problem With Genetic Algorithm
This project involves using Genetic Algorithm to solve the Flexible Job-shop Scheduling Problem.
## 1. Create Instance.py based on actual problems
For example, the following is the processing schedule for each certain workpiece.
| | Machine1 | Machine2 | Machine3 | Machine4 | Machine5 | Machine6 | Machine7 | Machine8 | Machine9 | Machine10 | Machine11 |
| :--------: | :------: | :------: | :------: | :------: | :------: | :------: | :------: | :------: | :------: | :-------: | :-------: |
| Operation1 | 10 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Operation2 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Operation3 | 0 | 0 | 14 | 16 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Operation4 | 0 | 0 | 0 | 0 | 15 | 25 | 21 | 0 | 0 | 0 | 0 |
| Operation5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 13 | 25 | 14 |
If the number of workpieces is **5**, the **Instance.py** will be written in the following format.
```python
Processing_time = [[10, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999],
[9999, 9, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999],
[9999, 9999, 14, 16, 9999, 9999, 9999, 9999, 9999, 9999, 9999],
[9999, 9999, 9999, 9999, 15, 25, 21, 9999, 9999, 9999, 9999],
[9999, 9999, 9999, 9999, 9999, 9999, 9999, 9, 13, 25, 24]]
J = {1: 5, 2: 5, 3: 5, 4: 5, 5: 5}
J_num = 5
M_num = 11
O_num = 25
```
The following is an introduction to variable names.
* **Processing_time** : `the processing schedule of every workpiece written in the list format`
* In the table, the row index represents the sequence number of the operation, the column index represents the sequence number of the machine, and the numerical value represents the corresponding processing time.
* If a machine is not selected in the operation, the corresponding value is represented by **9999**.
* **J** : `the index of each workpiece and the total number of corresponding operations written in the dictionary format`
* **J_num** : `the number of workpieces`
* **M_num** : `the number of machines`
* **O_num** : `the number of operations for all workpieces`
## 2. Set the parameters of Genetic Algorithm
Set the parameters of the genetic algorithm in **GA.py**.
```python
class GA():
def __init__(self):
self.Pop_size = 400
self.Pc = 0.8
self.Pm = 0.3
self.Pv = 0.5
self.Pw = 0.95
self.Max_Itertions = 100
```
The following is an introduction to variable names.
* **Pop_size** : `the size of population`
* **Pc** : `the probability of performing the crossover operation`
* **Pm** : `the probability of performing the variational operation`
* **Pv** : `the probability of choosing which way to perform the crossover operation`
* **Pw** : `the probability of choosing which way to perform the variational operation`
* **Max_Itertions** : `the maximum number of evolutionary generations`
## 3. Run main.py
After the code runs, the following two results will appear.
* **Result1** : processing schedule of all the workpieces showed in gantt chart
![](assets/img1.png)
* **Result2** : the maximum completion time of each iteration
![](assets/img2.png)

164
README_CN.md Normal file
View File

@ -0,0 +1,164 @@
# 基于遗传算法的柔性车间生产调度问题
本项目主要涉及使用遗传算法解决柔性车间生产调度问题。
## 1. 根据实际问题创建 Instance.py
例如,以下是每一工件的生产加工时间表。
| | 机器1 | 机器2 | 机器3 | 机器4 | 机器5 | 机器6 | 机器7 | 机器8 | 机器9 | 机器10 | 机器11 |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :----: | :----: |
| 工序1 | 10 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 工序2 | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 工序3 | 0 | 0 | 14 | 16 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 工序4 | 0 | 0 | 0 | 0 | 15 | 25 | 21 | 0 | 0 | 0 | 0 |
| 工序5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 13 | 25 | 14 |
假设某一车间共生产 **5** 个上述的工件,对应的 **Instance.py** 应该写成下面的格式。
```python
Processing_time = [[10, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999],
[9999, 9, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999, 9999],
[9999, 9999, 14, 16, 9999, 9999, 9999, 9999, 9999, 9999, 9999],
[9999, 9999, 9999, 9999, 15, 25, 21, 9999, 9999, 9999, 9999],
[9999, 9999, 9999, 9999, 9999, 9999, 9999, 9, 13, 25, 24]]
J = {1: 5, 2: 5, 3: 5, 4: 5, 5: 5}
J_num = 5
M_num = 11
O_num = 25
```
各参数的具体含义如下:
* **Processing_time** : `每一个工件的加工时间列表`
* 在加工时间表中,行索引代表的是各工序的序号,列索引代表的是各加工机器的序号,表中的数值代表的是工件的每一工序对应选择加工机器的加工时间。
* 如果在某一工序中不能使用该机器加工,对应的加工时间应该在列表中用 **9999** 替代。
* **J** : `以字典的格式表示的加工信息,其中字典的键表示工件的序号,对应键的值表示加工该工件共需多少工序`
* **J_num** : `工件的数量`
* **M_num** : `机器的数量`
* **O_num** : `所有工件的工序总数量`
## 2. 设置遗传算法的参数
**GA.py** 中设置遗传算法的参数。
```python
class GA():
def __init__(self):
self.Pop_size = 400
self.Pc = 0.8
self.Pm = 0.3
self.Pv = 0.5
self.Pw = 0.95
self.Max_Itertions = 100
```
各参数的具体含义如下:
* **Pop_size** : `种群数量`
* **Pc** : `进行交叉运算的概率`
* **Pm** : `进行变异运算的概率`
* **Pv** : `以何种方式进行交叉运算的概率`
* **Pw** : `以何种方式进行变异运算的概率`
* **Max_Itertions** : `种群最大的进化代数`
## 3. 运行 main.py
在运行 **main.py** 后,会产生以下两个结果。
* **结果1** : 用甘特图绘制的所用工件各工序的加工时间表
![](assets/img1.png)
* **结果2** : 每次种群进化后生产所有工件的最大完成时间
![](assets/img2.png)
## 4. 编程思路
### 1问题描述
* 柔性车间调度问题描述如下:
* $n$ 个工件 $(J_1,J_2,J_3,...,J_n)$ 要在 $m$ 台机器 $(M_1,M_2,...,M_m)$ 上加工;每个工件包含一道或多道工序;工序顺序是预先确定的;每道工序可以在多台不同加工机器上进行加工;工序的加工时间随加工机器的不同而不同;调度目标是为每到工序选择最合适的机器,确定每台机器上各道工序最佳加工顺序及开工时间,使整个系统的某些性能指标达到最优。
* 假设按照表2-2的加工时间表设计。
![](assets/img4.png)
* 遗传算法的计算流程假设P表示种群规模t表示当前代P(t)和C(t)表示第t代的父代和子代那么基本的遗传算法执行步骤如下。
* 按照一定的初始化方法产生初始种群P(t)t=0。
* 评价种群P(t),计算每个个体的适应度值。
* 判断是否满足终止条件,如果满足则输出结果;否则转下一步骤。
* 按照选择、交叉、变异等遗传算子产生子代C(t)。
* P(t) = C(t)转步骤2t=t+1。
![](assets/img3.png)
### 2编码
* 染色体编码是将所研究的问题的解用染色体的形式来表达的,这是遗传算法的关键。编码的目的是为了实现交叉、变异等类似于生物界的遗传操作。必须考虑染色体的合法性、可行性、有效性,以及对问题解空间表达的完全性。良好的编码方式可以在后续遗传操作中易产生可行解,提高执行效率;否则,经过遗传操作会产生不可行解,需要一定的修补措施,这样就降低了执行效率。染色体编码对求解速度、计算精度等有着直接的关系,对算法具有重要影响。
* FJSP包括两个子问题机器选择和工序排序。机器选择解决每道工序在可选机器集中的哪台机器上进行加工的问题工序排序解决所有工序确定加工机器后的排序和开工时间问题。针对编码过程中对两个子问题编码处理的不同目前主要有以下两种不同的编码方法
* 针对编码过程中两个问题编码处理的不同,目前主要有以下两种不同的编码方法。
* **集成编码**:集成编码染色体中的每一个基因位 $(h,j,i)$ 代表一个工序任务,表示工件 $j$ 的第 $h$ 道工序在机器 $i$ 上加工,染色体总长度等于所有工件的工序总和 $T_o$ 。
* **分段编码**分段编码染色体有A/B两部分组成将工序信息分开处理分别表示FJSP的两个子问题两部分染色体长度都等于 $T_o$ 。
* 此处设计了一种整数编码MSOS由两部分组成机器选择部分machines selection,MS和工序排序部分operations sequencing,OS
* **机器选择部分**:机器选择部分染色体长度为 $T_o$ 。每个基因位用整数表示,依次按照工件和工件工序的顺序进行排列,每个整数表示当前工序选择的加工机器在可选机器集中的顺序编号,并不是对应的机器号,这保证了后续交叉、变异等操作后产生的解仍然是可行解。如下图所示,依次是工件 $J_1$ 和工件 $J_2$ 的所有工序。工序 $O_{11}$ 有5台机器可以选择对应的4表示可选机器集中第4台机器即在机器 $M_4$ 上进行加工。同理,工序 $O_{12}$ 有2台机器可以选择分别为机器 $M_2$ 和机器 $M_4$ 图中对应的“1”表示可选机器集中的第1台机器即在机器 $M_2$ 上进行加工。
* **工序选择部分**:此部分采用基于工序的编码方式进行编码,染色体长度等于所有工件的工序之和 $T_o$ 。每一个基因用工件号直接编码工件号出现的顺序表示该工件工序间的先后加工顺序即对染色体从左到右进行编译对于第h次出现的工件号表示该工件 $j$ 的第 $h$ 道工序,并且工件号的出现次数等于该工件的工序总数 $h_j$ 。如下图所示,假设工序染色体为 [2 2 1 1 2] 则其中第一个“2”表示工件 $J_2$ 的工序 $O_{21}$ 第二个“2”表示工件 $J_2$ 的工序 $O_{22}$ ,以此类推,转换成各工件工序的加工顺序为 $O_{21}$ → $O_{22}$ → $O_{11}$ → $O_{12}$ → $O_{23}$ 。
![](assets/img5.png)
### 3解码
* FJSP染色体MSOS由两部分组成分别对它们进行解码关键是需要将工序排序部分解码成对应于机器选择部分的活动调度具体的解码步骤如下。
![](assets/img6.png)
![](assets/img7.png)
### 4初始化
* 种群初始化在进化算法中是一个关键问题初始解的质量对遗传算法求解的速度和质量有非常大的影响。FJSP不但要解决机器选择问题还要解决所有工序排序问题。目前大部分文献一般采用的是随机初始化方法使得初始解的质量偏低机器之间负荷不均衡导致要增加迭代次数或种群大小来达到最优解或近似最优解这势必增加优化时间。
* 针对FJSP特点此处提出一种GLR机器选择方法包括全局选择Global Selection、局部选择Local Selection和随机选择Random Selection。GS和LS主要是为了考虑机器选择的负荷问题使各台被选择的机器的工作负荷尽量平衡充分提高机器的利用率。RS主要考虑尽量使初始种群分散地分布于整个解空间。通过三者的有机结合提高初始解在机器选择部分中解的质量。下面分别介绍三种选择方法的具体执行步骤
* **全局选择**设置一个数组长度和机器数相等数组的顺序依次对应加工机器的顺序每一位上的值对应相应机器上的加工时间。随机在工件集中选择一个工件从当前工件的第1道工序开始将当前工序的可选加工机器的加工时间加上数组中对应的时间。从中选择最短的时间作为当前工序的加工机器并且将数组更新即把被选择的加工机器的加工时间加到数组中相应的位置上以此类推直到当前工件的所有工序的加工机器选择完毕后然后再随机选择一个工件开始直到所有工件的工序选择完毕为止。这样保证了最短加工机器先被选到而且保证了加工机器上的工作负荷平衡。具体执行步骤如下。
![](assets/img8.png)
* **局部选择**局部选择同全局选择原理上基本一致但是每次对一个工件选择完毕时数组需要重新设置为零不存在随机选择工件。设置一个数组长度和机器数相等选择工件集中第1个工件选择当前工件的第1道工序开始将当前工序的可选加工机器的加工时间加上数组中对应的时间。从中选择最短的时间作为当前工序的加工机器并且将数组更新即把被选择的加工机器的加工时间加到数组中相应的位置上以此类推直到当前工件的所有工序的加工机器选择完毕后然后数组每一位重新设置为零选择下一个工件直到所有工件选择完毕为止。这样保证了一个工件的工序中优先加工时间最短或说选择机器负荷最小的加工机器进行加工。具体执行步骤如下。
![](assets/img9.png)
* **随机选择**为保证初始种群的多样性初始种群应分散于解空间。一部分种群的机器选择部分采用随机初始化方法。RS与GS、LS的主要区别在于每一个基因位上的数字即表示工序可选机器集中的顺序号是随机产生的。具体执行步骤如下。
![](assets/img10.png)
### 5交叉操作
* 交叉的目的是利用父代个体经过一定操作组合后产生新个体在尽量降低有效模式被破坏的概率基础上对解空间进行高效搜索。交叉操作是主要的遗传操作GA的性能在很大程度上依赖于所使用的交叉操作它决定了GA的全局搜索能力。在设计交叉操作时必须满足可行性、特征的有效继承性、完全性和信息非冗余性等指标。特征的有效继承性保证父代中的优良信息能够保留到子代信息非冗余性保证子代中不会产生过多无用信息这两个特征在交叉操作设计中是两个重要的指标。
* **机器选择部分**:机器选择部分必须保证每位基因的先后顺序保持不变,采用均匀交叉操作。
![](assets/img11.png)
* **工序排序部分**:每个染色体中对多个工件进行操作,能够较好地继承父代个体的优良特征。
![](assets/img12.png)
![](assets/img13.png)
### 6变异操作
* 变异操作通过随机改变染色体的某些基因对染色体进行较小扰动来生成新的个体增加种群多样性并在一定程度上影响着GA的局部搜索能力。此处结合FJSP特点设计变异方法如下。
![](assets/img14.png)
### 7选择操作
* 选择操作的作用是使高性能的个体能以更高的概率生存,避免有效基因的损失,同时保持种群大小恒定,从而加快全局收敛性和提高计算效率。

BIN
assets/img1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
assets/img10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
assets/img11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
assets/img12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
assets/img13.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
assets/img14.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
assets/img2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
assets/img3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
assets/img4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
assets/img5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
assets/img6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
assets/img7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

BIN
assets/img8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

BIN
assets/img9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

85
main.py Normal file
View File

@ -0,0 +1,85 @@
import random
import matplotlib.pyplot as plt
import numpy as np
from Decode import Decode
from Encode import Encode
from GA import GA
from Instance import *
# 绘制甘特图
def Gantt(Machines):
M = ['red', 'blue', 'yellow', 'orange', 'green', 'palegoldenrod', 'purple', 'pink', 'Thistle', 'Magenta',
'SlateBlue', 'RoyalBlue', 'Cyan', 'Aqua', 'floralwhite', 'ghostwhite', 'goldenrod', 'mediumslateblue',
'navajowhite', 'navy', 'sandybrown', 'moccasin']
for i in range(len(Machines)):
Machine = Machines[i]
Start_time = Machine.O_start
End_time = Machine.O_end
for i_1 in range(len(End_time)):
plt.barh(i, width=End_time[i_1] - Start_time[i_1], height=0.8, left=Start_time[i_1],
color=M[Machine.assigned_task[i_1][0] - 1], edgecolor='black')
plt.text(x=Start_time[i_1] + (End_time[i_1] - Start_time[i_1]) / 2 - 0.5, y=i,
s=Machine.assigned_task[i_1][0])
plt.yticks(np.arange(len(Machines) + 1), np.arange(1, len(Machines) + 2))
plt.title('Scheduling Gantt chart')
plt.ylabel('Machines')
plt.xlabel('Time(min)')
plt.savefig('优化后排程方案的甘特图.png')
plt.show()
if __name__ == '__main__':
Optimal_fit = 9999 # 最佳适应度(初始化)
Optimal_CHS = 0 # 最佳适应度对应的基因个体(初始化)
g = GA()
e = Encode(Processing_time, g.Pop_size, J, J_num, M_num)
CHS1 = e.Global_initial()
CHS2 = e.Random_initial()
CHS3 = e.Local_initial()
C = np.vstack((CHS1, CHS2, CHS3))
Best_fit = [] # 记录适应度在迭代过程中的变化,便于绘图
for i in range(g.Max_Itertions):
print("iter_{} start!".format(i))
Fit = g.fitness(C, J, Processing_time, M_num, O_num)
Best = C[Fit.index(min(Fit))]
best_fitness = min(Fit)
if best_fitness < Optimal_fit:
Optimal_fit = best_fitness
Optimal_CHS = Best
Best_fit.append(Optimal_fit)
print('best_fitness', best_fitness)
d = Decode(J, Processing_time, M_num)
Fit.append(d.decode(Optimal_CHS, O_num))
Gantt(d.Machines)
else:
Best_fit.append(Optimal_fit)
for j in range(len(C)):
Cafter = []
if random.random() < g.Pc:
N_i = random.choice(np.arange(len(C)))
if random.random() < g.Pv:
Cross = g.machine_cross(C[j], C[N_i], O_num)
else:
Cross = g.operation_cross(C[j], C[N_i], O_num, J_num)
Cafter.append(Cross[0])
Cafter.append(Cross[1])
Cafter.append(C[j])
if random.random() < g.Pm:
if random.random() < g.Pw:
Variance = g.machine_variation(C[j], Processing_time, O_num, J)
else:
Variance = g.operation_variation(C[j], O_num, J_num, J, Processing_time, M_num)
Cafter.append(Variance)
if Cafter != []:
Fit = g.fitness(Cafter, J, Processing_time, M_num, O_num)
C[j] = Cafter[Fit.index(min(Fit))]
x = np.linspace(0, 50, g.Max_Itertions)
plt.plot(x, Best_fit, '-k')
plt.title('the maximum completion time of each iteration')
plt.ylabel('Cmax')
plt.xlabel('Test Num')
plt.savefig('最大完成时间的优化过程.png')
plt.show()

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB