From 250e5f5cad301c7f60a841b8bff3c180cd2375a2 Mon Sep 17 00:00:00 2001 From: Hgq <2757430053@qq.com> Date: Tue, 21 Oct 2025 21:22:49 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E7=BC=96=E7=A0=81?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E8=BF=87=E6=BB=A4=E4=B8=8D=E5=8F=AF?= =?UTF-8?q?=E8=A7=A3=E9=97=AE=E9=A2=98=EF=BC=8C=E4=BB=A5=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E5=B8=95=E7=B4=AF=E6=89=98=E5=89=8D=E6=B2=BF=E8=A7=A3=E8=BF=87?= =?UTF-8?q?=E5=B0=91=EF=BC=88=E6=95=88=E6=9E=9C=E5=BE=85=E5=AE=9A=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Decode.py | 12 ++- Encode.py | 146 ++++++++++++------------- GA.py | 213 ++++++++++++++++++++----------------- NSGA2.py | 16 ++- main.py | 32 +++++- 优化后排程方案的甘特图.png | Bin 19001 -> 19330 bytes 6 files changed, 228 insertions(+), 191 deletions(-) diff --git a/Decode.py b/Decode.py index 75ed9f2..61eff47 100644 --- a/Decode.py +++ b/Decode.py @@ -2,6 +2,7 @@ import numpy as np from Job import Job from Machine import Machine_Time_window + class Decode: def __init__(self, J, Processing_time, M_num): """ @@ -31,7 +32,7 @@ class Decode: Site = 0 # 按照基因的MS部分按工件序号划分 for S_i in self.J.values(): - Ms_decompose.append(MS[Site:Site + S_i]) #对MS按照每道工序数进行切分 + Ms_decompose.append(MS[Site:Site + S_i]) # 对MS按照每道工序数进行切分 Site += S_i for i in range(len(Ms_decompose)): # len(Ms_decompose)表示工件数 JM_i = [] @@ -79,11 +80,11 @@ class Decode: End_work_time = M_Ealiest + P_t # 当前工件当前工序的结束时间 return M_Ealiest, Selected_Machine, P_t, O_num, last_O_end, End_work_time - # 解码操作 + # 解码操作(适配新编码:前半部分为OS,后半部分为MS) def decode(self, CHS, Len_Chromo): """ :param CHS: 种群基因 - :param Len_Chromo: MS与OS的分解线 + :param Len_Chromo: 工序总数(OS和MS各占一半长度) :return: 双目标值 [最大加工时间, 负载标准差] """ # 重置状态 @@ -102,10 +103,11 @@ class Decode: job.Last_Processing_Machine = None job.Last_Processing_end_time = 0 - MS = list(CHS[0:Len_Chromo]) - OS = list(CHS[Len_Chromo:2 * Len_Chromo]) + OS = list(CHS[0:Len_Chromo]) # 前半部分为工件排列 + MS = list(CHS[Len_Chromo:2 * Len_Chromo]) # 后半部分为机器索引 Needed_Matrix = self.Order_Matrix(MS) JM, TM = Needed_Matrix[0], Needed_Matrix[1] + for i in OS: Job = i O_num = self.Jobs[Job].Current_Processed() # 现在加工的工序 diff --git a/Encode.py b/Encode.py index eeebfc4..f0f71b8 100644 --- a/Encode.py +++ b/Encode.py @@ -1,6 +1,7 @@ import random import numpy as np + class Encode: def __init__(self, Matrix, Pop_size, J, J_num, M_num): """ @@ -15,24 +16,25 @@ class Encode: self.J_num = J_num self.M_num = M_num self.CHS = [] - self.GS_num = int(0.6 * Pop_size) # 全局选择初始化 + # 调整初始化比例:增加随机选择比例到50% + self.GS_num = int(0.3 * Pop_size) # 全局选择初始化 self.LS_num = int(0.2 * Pop_size) # 局部选择初始化 - self.RS_num = int(0.2 * Pop_size) # 随机选择初始化 + self.RS_num = int(0.5 * Pop_size) # 随机选择初始化(提高比例) self.Len_Chromo = 0 for i in J.values(): - self.Len_Chromo += i # 遍历字典J的所有值并求和,即工序数之和 + self.Len_Chromo += i # 遍历字典J的所有值并求和,即工序数之和 - # 生成工序准备的部分 + # 生成工序准备的部分(工件排列,天然满足工序顺序约束) def OS_List(self): OS_list = [] - for k, v in self.J.items(): # 遍历字典J的所有键值对,初始化工序矩阵 - OS_add = [k - 1 for j in range(v)] + for k, v in self.J.items(): # 遍历字典J的所有键值对,初始化工序矩阵 + OS_add = [k - 1 for j in range(v)] # 工件索引从0开始 OS_list.extend(OS_add) return OS_list # 生成初始化矩阵 def CHS_Matrix(self, C_num): - return np.zeros([C_num, self.Len_Chromo], dtype=int) + return np.zeros([C_num, self.Len_Chromo * 2], dtype=int) # 工件排列+机器索引 # 定位每个工件的每道工序的位置,Job第几个工件,Operation第几道工序 def Site(self, Job, Operation): @@ -46,88 +48,74 @@ class Encode: # 全局初始化 def Global_initial(self): - MS = self.CHS_Matrix(self.GS_num) # 根据GS_num生成种群 + CHS = self.CHS_Matrix(self.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的某一行 - 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) # 第一次出现最小时间的位置 - I = List_Machine_weizhi[K] # 所有机器里的第I个机器 - Machine_time[I] += Min_time # 相应的机器位置加上最小时间 - site = self.Site(g, j) # 定位每个工件的每道工序的位置 - MS[i][site] = K # 即将每个工序选择的第K个机器赋值到对应位置 - CHS1 = np.hstack((MS, OS)) # 将MS和OS整合为一个矩阵 - return CHS1 + random.shuffle(OS_list) + OS = OS_list.copy() + MS = [0] * self.Len_Chromo + + Machine_time = np.zeros(self.M_num, dtype=int) + 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] + for j in range(len(h)): + D = h[j] + List_Machine_weizhi = [k for k, val in enumerate(D) if val != 9999] + Machine_Select = [Machine_time[m] + D[m] for m in List_Machine_weizhi] + Min_time = min(Machine_Select) + K = Machine_Select.index(Min_time) + MS[self.Site(g, j)] = K + Machine_time[List_Machine_weizhi[K]] += D[List_Machine_weizhi[K]] + + CHS[i] = np.hstack((OS, MS)) # 工件排列 + 机器索引(紧凑编码) + return CHS # 局部初始化 def Local_initial(self): - MS = self.CHS_Matrix(self.LS_num) # 根据LS_num生成局部选择的种群大小 + CHS = self.CHS_Matrix(self.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) - 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) # 局部初始化,每个工件重新初始化 - 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) # 第一次出现最小时间的位置 - I = List_Machine_weizhi[K] # 所有机器里的第I个机器 - Machine_time[I] += Min_time - site = self.Site(g, j) # 定位每个工件的每道工序的位置 - MS[i][site] = K # 即将每个工序选择的第K个机器赋值到对应位置 - CHS1 = np.hstack((MS, OS)) # 将MS和OS整合为一个矩阵 - return CHS1 + random.shuffle(OS_list) + OS = OS_list.copy() + MS = [0] * self.Len_Chromo - # 随机初始化 + 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) + h = self.Matrix[g] + for j in range(len(h)): + D = h[j] + List_Machine_weizhi = [k for k, val in enumerate(D) if val != 9999] + Machine_Select = [Machine_time[m] + D[m] for m in List_Machine_weizhi] + Min_time = min(Machine_Select) + K = Machine_Select.index(Min_time) + MS[self.Site(g, j)] = K + Machine_time[List_Machine_weizhi[K]] += D[List_Machine_weizhi[K]] + + CHS[i] = np.hstack((OS, MS)) + return CHS + + # 随机初始化(提高比例到50%) def Random_initial(self): - MS = self.CHS_Matrix(self.RS_num) # 根据RS_num生成随机选择的种群大小 + CHS = self.CHS_Matrix(self.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: # 选择第一个工件 + OS = OS_list.copy() + MS = [0] * self.Len_Chromo + + for g in range(self.J_num): 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个机器 - site = self.Site(g, j) # 定位每个工件的每道工序的位置 - MS[i][site] = K # 即将每个工序选择的第K个机器赋值到对应位置 - CHS1 = np.hstack((MS, OS)) - return CHS1 \ No newline at end of file + for j in range(len(h)): + D = h[j] + List_Machine_weizhi = [k for k, val in enumerate(D) if val != 9999] + # 随机选择可用机器 + selected = random.choice(List_Machine_weizhi) + K = List_Machine_weizhi.index(selected) + MS[self.Site(g, j)] = K + + CHS[i] = np.hstack((OS, MS)) + return CHS \ No newline at end of file diff --git a/GA.py b/GA.py index edf71d2..8932c54 100644 --- a/GA.py +++ b/GA.py @@ -11,141 +11,154 @@ class GA(): def __init__(self): self.Pop_size = 500 # 种群数量 self.Pc = 0.8 # 交叉概率 - self.Pm = 0.3 # 变异概率 + self.Pm = 0.15 # 降低变异概率到0.15 self.Pv = 0.5 # 选择何种方式进行交叉的概率阈值 self.Pw = 0.95 # 选择何种方式进行变异的概率阈值 - self.Max_Itertions = 100 # 最大迭代次数 + 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) #实例化一个解码器,传入问题参数 + d = Decode(J, Processing_time, M_num) # 实例化一个解码器,传入问题参数 Fit.append(d.decode(CHS[i], Len)) return Fit + def pmx_crossover(self, parent1, parent2, length): + if length <= 1: + return parent1.copy(), parent2.copy() + + # 确保交叉区间[a, b]有效(a < b) + a = random.randint(0, length - 2) + b = random.randint(a + 1, length - 1) + + child1 = parent1.copy() + child2 = parent2.copy() + + # 建立映射表(parent1[a:b+1]与parent2[a:b+1]的对应关系) + map1 = {parent1[i]: parent2[i] for i in range(a, b + 1)} # p1→p2的映射 + map2 = {parent2[i]: parent1[i] for i in range(a, b + 1)} # p2→p1的映射 + + # 处理child1:交叉区间外的元素替换 + for i in range(length): + if i < a or i > b: + val = child1[i] + # 限制循环次数,避免无限循环(最多循环len(map1)次) + loop_count = 0 + max_loop = len(map1) + while val in map1 and map1[val] in parent1[a:b + 1] and loop_count < max_loop: + val = map1[val] + loop_count += 1 + child1[i] = map1.get(val, val) # 若超出循环次数,直接使用当前val + + # 处理child2:交叉区间外的元素替换 + for i in range(length): + if i < a or i > b: + val = child2[i] + loop_count = 0 + max_loop = len(map2) + while val in map2 and map2[val] in parent2[a:b + 1] and loop_count < max_loop: + val = map2[val] + loop_count += 1 + child2[i] = map2.get(val, val) + + return child1, child2 + # 机器部分交叉 def machine_cross(self, CHS1, CHS2, T0): """ - :param CHS1: 机器选 择部分的基因1 - :param CHS2: 机器选择部分的基因2 + :param CHS1: 基因1 + :param CHS2: 基因2 :param T0: 工序总数 - :return: 交叉后的机器选择部分的基因 + :return: 交叉后的基因 """ - T_r = [j for j in range(T0)] - r = random.randint(1, 10) # 在区间[1,T0]内产生一个整数r - random.shuffle(T_r) #直接打乱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] + OS1, MS1 = CHS1[:T0], CHS1[T0:] + OS2, MS2 = CHS2[:T0], CHS2[T0:] + + # 随机选择交叉位置 + T_r = list(range(T0)) + random.shuffle(T_r) + r = random.randint(1, T0 // 2) # 交叉部分长度 + R = T_r[:r] + + # 交换机器选择部分 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 + MS1[i], MS2[i] = MS2[i], MS1[i] - # 工序部分交叉 + return np.hstack((OS1, MS1)), np.hstack((OS2, MS2)) + + # 工序部分交叉(使用PMX交叉) 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)) + OS1 = list(CHS1[T0:2 * T0]) # 确保转换为列表 + OS2 = list(CHS2[T0:2 * T0]) + MS1 = CHS1[0:T0].copy() + MS2 = CHS2[0:T0].copy() + + # 调用修复后的PMX交叉 + new_os1, new_os2 = self.pmx_crossover(OS1, OS2, T0) + + CHS1 = np.hstack((MS1, new_os1)) + CHS2 = np.hstack((MS2, new_os2)) return CHS1, CHS2 - # 机器部分变异 + # 机器部分变异(仅在可用机器集中选择) def machine_variation(self, CHS, O, T0, J): """ - :param CHS: 机器选择部分的基因 + :param CHS: 基因 :param O: 加工时间矩阵 :param T0: 工序总数 :param J: 各工件加工信息 - :return: 变异后的机器选择部分的基因 + :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 + OS = CHS[:T0] + MS = CHS[T0:] + + # 确定变异位置数量 + r = random.randint(1, max(1, T0 // 10)) # 最多变异10%的位置 + positions = random.sample(range(T0), r) + + for pos in positions: + # 找到该位置对应的工件和工序 + site = 0 + job = -1 + op = -1 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) + if site + v > pos: + job = k - 1 # 转换为0索引 + op = pos - site 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 + site += v + + # 获取该工序的可用机器 + D = O[job][op] + available_machines = [k for k, val in enumerate(D) if val != 9999] + if len(available_machines) <= 1: + continue # 只有一个可用机器时不变异 + + # 随机选择一个不同的可用机器 + current_idx = MS[pos] + current_machine = available_machines[current_idx] + new_machine = random.choice([m for m in available_machines if m != current_machine]) + MS[pos] = available_machines.index(new_machine) + + return np.hstack((OS, MS)) # 工序部分变异 def operation_variation(self, CHS, T0, J_num, J, O, M_num): """ - :param CHS: 工序选择部分的基因 + :param CHS: 基因 :param T0: 工序总数 :param J_num: 工件总数 :param J: 各工件加工信息 :param O: 加工时间矩阵 :param M_num: 机器总数 - :return: 变异后的工序选择部分的基因 + :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))] + OS = list(CHS[:T0]) + MS = CHS[T0:] + + # 随机选择两个位置交换 + i, j = random.sample(range(T0), 2) + OS[i], OS[j] = OS[j], OS[i] + + return np.hstack((OS, MS)) \ No newline at end of file diff --git a/NSGA2.py b/NSGA2.py index 4ef06da..a0bbcbc 100644 --- a/NSGA2.py +++ b/NSGA2.py @@ -1,4 +1,5 @@ import random +import numpy as np class NSGA2: def __init__(self, pop_size, obj_num): @@ -8,7 +9,7 @@ class NSGA2: def fast_non_dominated_sort(self, pop_obj): """快速非支配排序""" pop_size = len(pop_obj) - dominated = [[] for _ in range(pop_size)] # 被支配个体列表,_通常用作占位符变量,表示不关心这个变量的具体值 + dominated = [[] for _ in range(pop_size)] # 被支配个体列表 rank = [0] * pop_size # 个体的非支配等级 n = [0] * pop_size # 支配该个体的个体数量 @@ -50,7 +51,7 @@ class NSGA2: """计算拥挤度距离""" pop_size = len(pop_obj) distance = [0.0] * pop_size - max_rank = max(rank) + max_rank = max(rank) if rank else 0 # 对每个等级的个体计算拥挤度 for r in range(max_rank + 1): @@ -79,7 +80,7 @@ class NSGA2: return distance def selection(self, pop, pop_obj): - """选择操作:基于非支配排序和拥挤度的锦标赛选择""" + """选择操作:基于非支配排序和拥挤度的锦标赛选择,惩罚拥挤度为0的个体""" pop_size = len(pop) rank = self.fast_non_dominated_sort(pop_obj) distance = self.crowding_distance(pop_obj, rank) @@ -95,9 +96,14 @@ class NSGA2: selected.append(pop[i]) elif rank[i] > rank[j]: selected.append(pop[j]) - # 等级相同则选择拥挤度大的个体 + # 等级相同则选择拥挤度大的个体(惩罚拥挤度为0的重复解) else: - if distance[i] > distance[j]: + # 对拥挤度为0的个体进行惩罚 + if distance[i] == 0 and distance[j] > 0: + selected.append(pop[j]) + elif distance[j] == 0 and distance[i] > 0: + selected.append(pop[i]) + elif distance[i] >= distance[j]: selected.append(pop[i]) else: selected.append(pop[j]) diff --git a/main.py b/main.py index 3afb010..09fdf60 100644 --- a/main.py +++ b/main.py @@ -46,6 +46,11 @@ if __name__ == '__main__': all_solutions = [] # 存储所有个体(染色体) all_fitnesses = [] # 存储所有个体的目标值 + # 早停策略参数 + early_stop_counter = 0 + max_stagnation = 50 # 连续50代无更新则停止 + best_fitness_history = [] + for i in range(g.Max_Itertions): print(f"iter_{i} start!") Fit = g.fitness(C, J, Processing_time, M_num, O_num) @@ -60,8 +65,17 @@ if __name__ == '__main__': current_non_dominated_fit = [Fit[j] for j in range(len(C)) if rank[j] == 0] # 更新全局非支配解 - Optimal_solutions.extend(current_non_dominated) - Optimal_fit_values.extend(current_non_dominated_fit) + new_non_dominated = False + for sol, fit in zip(current_non_dominated, current_non_dominated_fit): + is_dominated = False + for existing_fit in Optimal_fit_values: + if all(existing_fit[d] <= fit[d] for d in range(2)) and any(existing_fit[d] < fit[d] for d in range(2)): + is_dominated = True + break + if not is_dominated: + Optimal_solutions.append(sol) + Optimal_fit_values.append(fit) + new_non_dominated = True # 对全局解重新筛选非支配解 if Optimal_solutions: @@ -77,6 +91,20 @@ if __name__ == '__main__': Optimal_solutions = [Optimal_solutions[j] for j in sorted_indices[:g.Pop_size]] Optimal_fit_values = [Optimal_fit_values[j] for j in sorted_indices[:g.Pop_size]] + # 早停策略检查 + if new_non_dominated: + early_stop_counter = 0 + else: + early_stop_counter += 1 + if early_stop_counter >= max_stagnation: + print(f"早停于第{i}代,连续{max_stagnation}代无新的非支配解") + break + + # 定期引入随机解(每20代) + if i % 20 == 0 and i > 0: + random_solutions = e.Random_initial()[:int(g.Pop_size * 0.1)] # 引入10%的随机解 + C = np.vstack((C[:-len(random_solutions)], random_solutions)) + # 选择操作(基于NSGA-II) selected = nsga2.selection(C, Fit) diff --git a/优化后排程方案的甘特图.png b/优化后排程方案的甘特图.png index c95e2ec866f19bef34379f09d3fb504f97ef360b..836671be692d84bcd2eb3295131b4d4c34d9f40a 100644 GIT binary patch literal 19330 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 HzxsawWdvsu