车间调度系列文章:
-
20、多目标柔性车间调度丨NSGA3:以算例MK01为例
问题说明
柔性车间调度问题可描述为:多个工件在多台机器上加工,工件安排加工时严格按照工序的先后顺序,至少有一道工序有多个可加工机器,在某些优化目标下安排生产。柔性车间调度问题的约束条件如下:
- (1)同一台机器同一时刻只能加工一个工件;
- (2)同一工件的同一道工序在同一时刻被加工的机器数是一;
- (3)任意工序开始加工不能中断;
- (4)各个工件之间不存在的优先级的差别;
- (5)同一工件的工序之间存在先后约束,不同工件的工序之间不存在先后约束;
- (6)所有工件在零时刻都可以被加工。
MK01算例:
算例的工件数和机器数分别是10和6。
excel的第一行和第一列是编号,不用考虑,修改与否也不影响。
第一行第一个数字6表示,工件1有6道工序。后面的2 1 5 3 4,表示工件1的第一道工序有两个可选机器,分别是1和3,加工时间是5和4,后面的3 5 3 3 5 2 1表示工件1的第二道工序有3个可选机器,分别是5,3,2,加工时间是3,5,1,一行就是1个工件的所有工序的可选机器可加工时间,后面的工序以此类推。
数学建模
符号定义:
n | 工件总数 | makespani | 工件i的完工时间 |
---|---|---|---|
m | 机器总数 | makespan | 最大完工时间 |
i,h | 工件号 | Load_all | 机器总负荷 |
j,k | 工序号 | E_all | 总能耗 |
z | 机器号 | Xijz | 工序oij是否在机器z上加工,为0-1变量,在z上加工为1 |
qi | 工件i的工序数 | Gijhk | 工序oij和工序ohk的先后顺序,为0-1变量,ij在前为1 |
oij | 工件i的第j道工序 | M | 一个大正实数 |
Mij | 工序oij的可选机器 | Tijz | 工序oij在机器z的加工时间 |
Sij | 工序oij的开工时间 | Fz | 机器的z的完工时间 |
Cij | 工序oij的完工时间 | Pz | 机器的z的负载功率 |
Load_z | 机器z负荷 | Rz | 机器的z的空载功率 |
模型:
目标函数:
makespan=min{max makespani} 完工时间
Load_all=∑Load_z 机器负荷
E_all=∑Load_zPz +(Fz-Load_z)Rz 能耗
i=(1,2,…,n),z=(1,2,…m)
约束条件:
求解思路
1、工序编码
- 步骤1:按照工件的工序数依次生成工序编码如下:
work = [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9]
程序里为方便运算,0表示工件1,依次类推。
- 步骤2:随机打乱work得到如下编码:
job= [7, 1, 7, 9, 4, 6, 4, 2, 4, 6, 5, 0, 5, 1, 1, 5, 1, 6, 3, 2, 4, 9, 2, 3, 8, 8, 4, 0, 0, 7, 7, 6, 1, 8, 9, 0, 2, 9, 3, 6, 8, 7, 5, 8, 9, 9, 3, 4, 3, 2, 5, 5, 0, 0, 8]
job就是一个可行工序编码。
2、机器和加工时间编码:
参考文献的3种机器编码生成方法:全局选择、局部选择和随机选择。
对于6个加工机器的mk01。
全局选择:依次安排每个工件的加工,每道工序选择最小负荷的机器。
局部选择:依次安排每个工件的加工,每次安排完一个工件加工后,各个机器的负荷清0,每道工序选择最小负荷的机器。
随机选择:依次安排每个工件的加工,每道工序随机选择可加工机器
核心代码
if r<self.p1 or r>1-self.p2:
for k in range(len(n_machine)):
m=int(n_machine[k])-1
index_select.append(m)
t=n_time[k]
a_global[0,m]+=t #全局负荷计算
a_part[0,m]+=t #局部负荷计算
if r<self.p1: #全局选择
select=a_global[:,index_select]
idx_select=np.argmin(select[0])
else: #局部选择
select=a_part[:,index_select]
idx_select=np.argmin(select[0])
m_select=n_machine[idx_select]
t_index=n_machine.index(m_select)
machine.append(m_select)
machine_time.append(n_time[t_index])
else: #否则随机挑选机器
index=np.random.randint(0,len(n_time),1)
machine.append(n_machine[index[0]])
machine_time.append(n_time[index[0]])
一次随机生成的机器和加工时间编码如下:
machine=[3.0, 2.0, 6.0, 1.0, 3.0, 4.0, 2.0, 3.0, 1.0, 4.0, 1.0, 2.0, 6.0, 1.0, 3.0, 1.0, 1.0, 2.0, 3.0, 5.0, 6.0, 2.0, 1.0, 2.0, 1.0, 4.0, 6.0, 6.0, 1.0, 2.0, 2.0, 1.0, 4.0, 6.0, 4.0, 3.0, 5.0, 3.0, 6.0, 2.0, 1.0, 2.0, 4.0, 6.0, 1.0, 4.0, 1.0, 2.0, 4.0, 6.0, 2.0, 5.0, 6.0, 4.0, 1.0]
machine_time=[4.0, 1.0, 2.0, 1.0, 1.0, 3.0, 6.0, 1.0, 2.0, 6.0, 1.0, 6.0, 2.0, 1.0, 4.0, 1.0, 1.0, 6.0, 1.0, 3.0, 2.0, 1.0, 1.0, 6.0, 5.0, 6.0, 6.0, 2.0, 2.0, 6.0, 6.0, 1.0, 2.0, 1.0, 2.0, 4.0, 1.0, 1.0, 2.0, 6.0, 1.0, 6.0, 6.0, 1.0, 1.0, 3.0, 2.0, 6.0, 6.0, 2.0, 6.0, 3.0, 1.0, 6.0, 3.0]
由算例知道工件1有6道工序,所以machine和machine_time的前6个数表示工件1的6道工序依次在机器3.0、2.0、6.0、 1.0、 3.0、4.0加工,加工时间是4.0、1.0、2.0、 1.0、1.0、 3.0。小数点是因为数据类型是浮点型,不影响,为了美观也可变为整型。
同理工件2有5道工序,所以machine和machine_time的第7到11个数表示工件2的5道工序依次在机器 2.0、3.0、1.0、4.0、 1.0加工,加工时间是 6.0, 1.0, 2.0, 6.0, 1.0,后续编码依次类推。
3、插入式解码:
简单来说:每次安排工序的加工机器,首先找对应加工机器的空闲时间,尽量把工序安排空闲时间里。
核心代码:
if jobtime[0,svg] >0 : #如果工序不是第一道工序
if len(rest[sig])>0 : #如果空闲时间非空
for m in range(len(rest[sig])-1,-1,-1): #空闲时间从后往前遍历
if rest[sig][m][1]<=jobtime[0,svg] : #如果某个空闲时间段小于等于上一道工序的完工时间
break #结束遍历
else: #否则
begin=max(jobtime[0,svg],rest[sig][m][0]) #可开工时间是上一道工序的完工时间和空闲片段开始时间最大值
if begin+machine_time[index] <= rest[sig][m][1] : #如果空闲时间段满足要求
startime=begin #更新开工时间
signal=1
del rest[sig][m] #删掉空闲时间段
break
if signal==0 : #如果不可插入
startime=max(jobtime[0,svg],tmm[0,sig]) #开工时间是加工机器结束时间和上一道工序完工时间的最大值
if startime>tmm[0,sig] and signal==0: #如果不可插入且开工时间大于加工机器的完工时间
rest[sig].append([tmm[0,sig],startime]) #添加时间段到空闲时间里
if signal==0 : #如果不可插入
tmm[0,sig]=startime+machine_time[index] #更新机器的结束时间
if signal>0 : #如果可插入
signal=0 #不更新机器结束时间,且可插入信号归零
jobtime[0,svg]=startime+machine_time[index] #更新工序完工时间
load_m[0,sig]+=machine_time[index] #更新对应机器的负荷
对本文来说,一次插入成功后,就把当前插入的空闲时间段删除,虽然插入后,时间段仍然可能剩余空闲时间,但认为剩余较短,不考虑。
代码在fjsp里。
nsga3算法
1、算法介绍:
nsga3和nsga2类似,都是按个体的支配关系对种群进行分层,通过交叉、变异等遗传算子对种群进行优化,不同的是,nsga2借助拥挤度计算对种群进行选择,而nsga3通过种群个体和参考点的关联情况进行选择:
nsga3使用的方法便是预先指定参考线,计算St 中的个体与参考线之间的垂直距离。步骤如下:
1.对St中的值进行自适应归一化
2.定义超平面上连接参考点的参考线
3.计算St 中的个体与参考线之间的垂直距离
4.每个个体都根据最小垂直距离与一个参考点相关联
5.计算每个参考点相关联的个体数量
6.根据第五步数量选择K个个体
简单来说,就是父代和子代个体合并成2N个个体,从中选择N个个体,非支配的排序方法形成I层,如果FL<I,刚好能有N个个体,选择结束,否则按照上述的参考点相关联方法对FL层数选择K个个体。
2、非支配型排序方法
n(i) 为在种群中支配个体 i 的解个体的数量。S(i) 为被个体 i 所支配的解个体的集合。
- 首先,找到种群中所有 n(i)=0 的个体,将它们存入当前集合F(1);
- 然后对于当前集合 F(1) 中的每个个体 j,考察它所支配的个体集 S(j),将集合 S(j) 中的每个个体 k 的 n(k) 减去1,即支配个体 k 的解个体数减1(因为支配个体 k 的个体 j 已经存入当前集 F(1) );
- 如 n(k)-1=0则将个体 k 存入另一个集H。最后,将 F(1) 作为第一级非支配个体集合,并赋予该集合内个体一个相同的非支配序 i(rank),然后继续对 H 作上述分级操作并赋予相应的非支配序,直到所有的个体都被分级。其计算复杂度为O(mN^{2}),m为目标函数个数,N为种群大小。
支配的概念:假设有两个解p,q,如果p在每个目标函数的维度都不劣于q,则p支配q,如最小值问题,[1,2,3]支配[1,2,4]
简单来说,假设有两个解p,q,如果p支配q,则q放到p支配的集合S§ 即里,且n(q)在原来的等级基础上上升一个等级。
分层的逻辑:层级为0的个体是最优的一层,把层级为0的个体取出来之后,被该层支配的所有个体等级减1,以此类推,不断取出等级为0的个体,最终所有剩余个体的等级都会为0,分层结束。
代码:
def divide(answer):
S = [[] for i in range(len(answer))]
front = [[]]
n = [0 for i in range(len(answer))]
rank = [0 for i in range(len(answer))]
for p in range(len(answer)):
for q in range(len(answer)):
# 如果p支配q
if (np.array(answer[p]) <= np.array(answer[q])).all() and (answer[p] != answer[q]):
if q not in S[p]:
S[p].append(q) # 同时如果q不属于sp将其添加到sp中
# 如果q支配p
elif (np.array(answer[p]) >= np.array(answer[q])).all() and (answer[p] != answer[q]):
n[p] = n[p] + 1 # 则将np+1
if n[p] == 0:
rank[p] = 0
if p not in front[0]:
front[0].append(p)
i = 0
while (front[i] != []):
Q = []
for p in front[i]:
for q in S[p]:
n[q] = n[q] - 1 # 则将fk中所有给对应的个体np-1
if (n[q] == 0): # 如果nq==0
rank[q] = i + 1
if q not in Q:
Q.append(q)
i = i + 1
front.append(Q)
del front[len(front) - 1]
return front
3、自适应归一化:
- 1.寻找理想点:即求解这一代种群所有目标的最小值
如answer = [[2, 3, 5], [7, 2, 8], [3, 6, 5], [2, 4, 7], [1, 6, 2], [8, 2, 4]]是3目标问题的6个解,每个目标的最小值:ideal_point=[1,2,2]
- 2.种群平移:种群个体相对于理想点的位置
change= answer-ideal_point 结果change=[[1, 1, 3], [6, 0, 6], [2, 4, 3], [1, 2, 5], [0, 4, 0], [7, 0, 2]]
- 3.极值点寻找:极值点是指在一个目标值上很大,另外几个目标值上很小的点
以寻找x轴的极值点为例:change/[1, 10-6, 10-6]取每个解对应目标的最大值point_1 =[3000000.0, 6000000.0, 4000000.0, 5000000.0, 4000000.0, 2000000.0],第一个3000000是[[1, 1, 3]/[1, 10-6, 10-6]的最大值,其余以此类推。然后取point_1的最小值的位置,即2000000的位置,所以x轴的极值点是 [7, 0, 2]
可以理解为沿x轴看去,这三个点y和z值中最大的那个谁更小,也就是谁更接近x平面。y轴和z轴的极值点寻找类似,不过变成除[10-6, 1, 10-6]和[10-6, 10-6, 1],最终得到3个轴的极值点[7, 0, 2],[0, 4, 0],[1, 1, 3]。
- 4.超平面建立和截距获取:极值点形成的平面和3个坐标轴的交点
由空间平面斜截式:x/a=y/b=z/c=1 得[a,b,c]=1/([x,y,z]-1*[1,1,1]) [x,y,z]-1是[x,y,z]的逆矩阵,上述3个点组成的平面的截距是intercept=[12.666666666666671, 4.0, 4.470588235294118]
- 5.归一化:个体在超平面的相对位置
norm = change / (intercept - ideal_point) 有的文章是norm = change /intercept 上述的norm的值是:
norm=[[0.08571428571428567, 0.5, 1.2142857142857142], [0.5142857142857141, 0.0, 2.4285714285714284], [0.17142857142857135, 2.0, 1.2142857142857142], [0.08571428571428567, 1.0, 2.0238095238095237], [0.0, 2.0, 0.0], [0.5999999999999998, 0.0, 0.8095238095238095]]
需要注意的:本文的调度问题对应上述的[x,y,z]的不一定都可逆,相当于3个极值点不一定组成一个平面,如其中两个点的值是倍数关系,3个点就是一条直线,本文不可逆时设置截距intercept=[0,0,0]
代码:
def point_deal(answer):
point = np.array(answer)
ideal_point = point.min(0) # 理想点
change = point - ideal_point # 原点
x = [1, 1e-6, 1e-6] # 单位向量
y = [1e-6, 1, 1e-6]
z = [1e-6, 1e-6, 1]
point_1 = (change / np.array(x)).max(1).tolist()
point_x = change[point_1.index(min(point_1))] # x轴极值点
point_2 = (change / np.array(y)).max(1).tolist()
point_y = change[point_2.index(min(point_2))] # y轴极值点
point_3 = (change / np.array(z)).max(1).tolist()
point_z = change[point_3.index(min(point_3))] # z轴极值点
w = np.array([point_x, point_y, point_z])
try:
w_ = np.linalg.inv(w) # 逆矩阵
intercept = 1 / np.dot(w_, np.array([1, 1, 1])) # 截距
except:
intercept = 0
norm = change / (intercept - ideal_point) # 目标归一化
return norm
4、参考点生成:
参考点概念和生成介绍见参考博文https://blog.csdn.net/ztzi321/article/details/111304393
h是5,是一个目标的分段个数;m=3,3目标问题,参考点个数是15,代码:
def reference_point(h, m): # 参考点函数
begin = [round((1 / h) * i, 1) for i in range(h + m - 1)]
x1, x2 = [], []
width = len(begin)
for i in range(width - 1):
x1 += [begin[i] for j in range(width - i - 1)]
x2 += begin[i + 1:]
a = np.array(x1)
b = np.array(x2) - 1/h
s1 = a # x坐标
s2 = b - a # y坐标
s3 = 1 - b # z坐标
w = np.array([s1, s2, s3]).T # 转置
return w
结果:
[[0.0, 0.0, 1.0], [0.0, 0.2, 0.8], [0.0, 0.39999999999999997, 0.6000000000000001], [0.0, 0.6000000000000001, 0.3999999999999999], [0.0, 0.8, 0.19999999999999996], [0.0, 1.0, 0.0], [0.2, 0.0, 0.8], [0.2, 0.19999999999999996, 0.6000000000000001], [0.2, 0.4000000000000001, 0.3999999999999999], [0.2, 0.6000000000000001, 0.19999999999999996], [0.2, 0.8, 0.0], [0.4, -5.551115123125783e-17, 0.6000000000000001], [0.4, 0.20000000000000007, 0.3999999999999999], [0.4, 0.4, 0.19999999999999996], [0.4, 0.6, 0.0], [0.6, 1.1102230246251565e-16, 0.3999999999999999], [0.6, 0.20000000000000007, 0.19999999999999996], [0.6, 0.4, 0.0], [0.8, 0.0, 0.19999999999999996], [0.8, 0.19999999999999996, 0.0], [1.0, 0.0, 0.0]]
5、关联操作
关联:对于归一化的解空间的种群,计算个体到参考点与原点连线的距离,挑选最短距离的参考点,则认为参考点和个体关联。
所以一个参考点有可能关联多个个体,也可能没有个体与之关联。本文的距离是欧式距离,即3维空间点到过原点直线的距离,根据点到直线的数学方法,在Python中,距离式d = np.linalg.norm(np.cross(point1, point2 - point1)) / np.linalg.norm(point1),point1是任意一点,point2是参考点,np.linalg.norm是模计算,np.cross是叉乘计算。
代码:
def association(a, w):
point_assoc=[]
d_min = []
for i in range(w.shape[0]):
point_assoc.append([])
d_min.append([])
for m in range(a.shape[0]):
point1 = a[m]
dis = []
for n in range(w.shape[0]):
point2 = w[n]
# 点到参考线的距离
d = np.linalg.norm(np.cross(point1, point2 - point1)) / np.linalg.norm(point1)
dis.append(d)
idx = dis.index(min(dis))
d_min[idx].append(min(dis))
point_assoc[idx].append(m)
return point_assoc, d_min
point_assoc记录参考点的关联情况,d_min记录对应参考点与个体的距离。例子answer的关联结果:
[[], [0], [3], [2], [], [4], [1], [], [], [], [], [5], [], [], [], [], [], [], [], [], []]
距离:[[], [0.13093330562105995], [0.09670164403543269], [0.06085648230927952], [], [0.0], [0.029924619386217616], [], [], [], [], [0.03591624664788051], [], [], [], [], [], [], [], [], []]
说明第二(位置)参考点关联answer的第一个(0+1)个体,第三个参考点关联第4个个体,以此类推,第二个中括号是对应距离。
6、最小生境保留
参考点与个体的关联情况得到后,对个体进行选择,即最小生境保留。假设第FL层需要选择k个个体,步骤如下:
1.选择关联FL层个体数量最小(大于0)的参考点,多个这样的参考点随机选择一个;
2.对于步骤1选择的参考点,获取其与FL层前个体的关联情况,如果关联转入3,如果不关联,转入4;
3.参考点关联的FL层个体随机选择一个;
4.参考点关联的FL层个体按最短距离选择一个。
不断重复,直到k个个体取满。
代码:
def select(n,point_assoc,point_former, d_min):
n_idx = []
while len(n_idx)<n: # 挑选n个个体
numer = [len(po) for po in point_assoc] # 参考点在FL层的关联点情况
numer_former = [len(poa) for poa in point_former] # 参考点在FL层之前的关联点情况
rank = np.array(numer).argsort().tolist() # 关联点数由小到大排列
for ra in rank:
if numer[ra] == 1: # 如果参考点关联一个点
n_idx.append(point_assoc[ra][0]) # 直接取
del point_assoc[ra][0] # 删除对应关联点
del d_min[ra][0]
break
if numer[ra] > 1: # 如果参考点关联多个点
if numer_former[ra] == 0: # 如果该点在FL层之前关联0个点
idx = d_min[ra].index(min(d_min[ra]))
n_idx.append(point_assoc[ra][idx]) # 取距离最小的点
else: # 如果该点在FL层之前关联多个点
idx = np.random.randint(0, numer[ra], 1)[0]
n_idx.append(point_assoc[ra][idx]) # 随机取
del point_assoc[ra][idx] # 删除对应关联点
del d_min[ra][idx]
break
return n_idx
代码中del 操作保证一个个体只能被选择一次。
目前为止,nsga3K个个体的选择方法已经介绍完成,参考的博文有:http://t.csdn.cn/F3xH8,http://t.csdn.cn/KzIrG
以下介绍一下交叉和变异及FL及FL层前个体的获取。
7、工序的pox交叉
以mk01为例:随机0到9的一个数为6,对应两个进行交叉的工序编码,0到6基因及其位置保持不变,每个编码6到9的基因位置按顺序填入另一个工序编码6到9的基因。
核心代码:
def job_cross(self,chrom_L1,chrom_L2): #工序的pox交叉
num=list(set(chrom_L1[0]))
np.random.shuffle(num)
index=np.random.randint(0,len(num),1)[0]
jpb_set1=num[:index+1] #固定不变的工件
jpb_set2=num[index+1:] #按顺序读取的工件
C1,C2=np.zeros((1,chrom_L1.shape[1]))-1,np.zeros((1,chrom_L1.shape[1]))-1
sig,svg=[],[]
for i in range(chrom_L1.shape[1]):#固定位置的工序不变
ii,iii=0,0
for j in range(len(jpb_set1)):
if(chrom_L1[0,i]==jpb_set1[j]):
C1[0,i]=chrom_L1[0,i]
else:
ii+=1
if(chrom_L2[0,i]==jpb_set1[j]):
C2[0,i]=chrom_L2[0,i]
else:
iii+=1
if(ii==len(jpb_set1)):
sig.append(chrom_L1[0,i])
if(iii==len(jpb_set1)):
svg.append(chrom_L2[0,i])
signal1,signal2=0,0 #为-1的地方按顺序添加工序编码
for i in range(chrom_L1.shape[1]):
if(C1[0,i]==-1):
C1[0,i]=svg[signal1]
signal1+=1
if(C2[0,i]==-1):
C2[0,i]=sig[signal2]
signal2+=1
return C1,C2
8、机器的均匀交叉
均匀交叉算子的概念比较简单,简单说一下逻辑:假设两个解的工序编码的第一道工序分别选择了机器1和3,随机生成0,1两个数中的一个,如果随机数是1,交换两个解第一道工序的机器选择,否则保持原选择。
代码:
def ma_cross(self,m1,t1,m2,t2): #机器均匀交叉
MC1,MC2,TC1,TC2=[],[],[],[]
for i in range(m1.shape[0]):
index=np.random.randint(0,2,1)[0]
if(index==0): #为0时继承继承父代的机器选择
MC1.append(m1[i]),MC2.append(m2[i]),TC1.append(t1[i]),TC2.append(t2[i]);
else: #为1时另一个父代的加工机器选择
MC2.append(m1[i]),MC1.append(m2[i]),TC2.append(t1[i]),TC1.append(t2[i]);
return MC1,TC1,MC2,TC2
9、工序变异
两点交叉变异,即交换任意两个位置的基因:
def var(job):
mul = random.sample(range(job.shape[0]), 2) # 变异的位置
job[mul[0]], job[mul[1]] = job[mul[1]], job[mul[0]]
return job
10、FL及FL层前个体的获取
初始的时候,随机生成父种群Pt(N),再经过交叉和变异产生子种群(N),两者结合起来,用非支配的排序方法将它们划分等级(F1、F2、…Fl),从 F1 层开始选择个体,然后是F2……,把它们放在St中,一直到St的大小为N,作为下一次迭代的父种群Pt+1。
在取到最后一层即FL层的时候,只选择其中的部分——K=N-|Pt+1|个。
def select(n, front):
count = n
count1 = 0
w1, w2 = [], []
while count > 0:
if len(front[count1]) <= count: # Fl层以前个体
w1 += front[count1]
count -= len(front[count1])
count1 += 1
else: # Fl层个体
w2 += front[count1]
count = 0
return w1, w2
当len(w2)=0时,不用进行nsga3选择操作。
11、nsga3实现步骤
-
步骤1:固定比例全局选择、局部选择和随机选择3种方式初始1个工序、机器、加工时间编码,并解码
-
步骤2:工序、机器编码的交叉和变异
-
步骤3:合并子代和父代,种群数量是2N,非支配排序对种群进行分层,假设截至FL-1层的个体小于等于N,截至FL个体数大于N
-
步骤4:从第一层往后依次选择个体,如果FL-1层选择到个体等于N,进入步骤5,否则按照参考点关联方法在FL层选择k个个体
-
步骤5:判断算法是否达到迭代次数,如果达到,结束算法,否则重复步骤2,3,4
结果
1、代码运行环境
windows系统,python3.11.1,第三方库及版本号如下:
numpy==1.24.3
matplotlib==3.7.1
第三方库需要在安装完python之后,额外安装,以前文章有讲述过安装第三方库的解决办法。
2、运行代码
在main.py里
import fjsp
import data
import nsga_3
Tmachine,Tmachinetime,machines,work,start,lenth=data.total(10)
parm_fj=[10,6,0.3,0.4] #编码解码参数,依次是工件数、机器数 ,全局、局部选择比例
parm_p=[[3,1.4,1.7,2.4,2.4,4.1],[0.6,0.6,0.5,0.4,0.4,0.6]] #负载和空载功率
parm_mk=[Tmachine,Tmachinetime,machines,work,start,lenth]
parm_ns=[20,100,.8,.2] # 迭代次数,种群给规模,交叉概率,变异概率
pareto, pareto_job, pareto_machine, pareto_time, fit_every=nsga_3.nsga(parm_ns, parm_mk, parm_fj, parm_p)
print(pareto)
sig=0
job,machine,machine_time=pareto_job[sig],pareto_machine[sig],pareto_time[sig]
C_max,Twork,Eall,_,_,_,_=fjsp.caculate(job,machine,machine_time,parm_fj,parm_mk,parm_p)
print(C_max,Twork,Eall)
fjsp.draw(job,machine,machine_time,parm_fj,parm_mk,parm_p) # 第一张图
fjsp.draw_change(fit_every)
3、一次运行结果
pareto结果:
[[57.0, 160.0, 381.09999999999997], [53.0, 160.0, 385.79999999999995], [54.0, 160.0, 383.0], [57.0, 160.0, 381.09999999999997], [53.0, 160.0, 385.79999999999995], [57.0, 160.0, 381.09999999999997], [53.0, 160.0, 385.79999999999995], [46.0, 160.0, 389.8], [50.0, 162.0, 386.3], [51.0, 160.0, 386.29999999999995]]
第一个解的甘特图:
pareto解中3个目标对应最大、最小、平均值随迭代次数的变化:
5、结论
本文的主要是nsga2的延申,涉及的内容较多,有编码、解码、非支配排序,关联操作等,主要介绍的是nsga3对k个个体的选择,其余和nsga2类似,算法运行速度较快,但不算太稳定,可能会出现矩阵非逆等报错,不过try能避免。有兴趣的可以研究4目标,5目标等问题,不过归一化,距离计算,关联获等还要更深入的研究。
excel数据可更改,工件数、机器数、工件的工序数、工序的可加工机器数等数据对得上就能运行。
代码
演示视频:
视频
多目标柔性车间调度丨NSGA3算法:以MK01算例为例
完整算法+数据:
# 微信公众号:学长带你飞
# 主要更新方向:1、车间调度和车辆路径问题求解算法
# 2、学术写作技巧
# 3、读书感悟
# @Author : Jack Hao
可加微信:diligent-1618,请备注:学校-专业-昵称。