多目标柔性车间调度丨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 所支配的解个体的集合。

  1. 首先,找到种群中所有 n(i)=0 的个体,将它们存入当前集合F(1);
  2. 然后对于当前集合 F(1) 中的每个个体 j,考察它所支配的个体集 S(j),将集合 S(j) 中的每个个体 k 的 n(k) 减去1,即支配个体 k 的解个体数减1(因为支配个体 k 的个体 j 已经存入当前集 F(1) );
  3. 如 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个目标对应最大、最小、平均值随迭代次数的变化:

(C:\Users\22442\Desktop\nsga3\3)

5、结论

本文的主要是nsga2的延申,涉及的内容较多,有编码、解码、非支配排序,关联操作等,主要介绍的是nsga3对k个个体的选择,其余和nsga2类似,算法运行速度较快,但不算太稳定,可能会出现矩阵非逆等报错,不过try能避免。有兴趣的可以研究4目标,5目标等问题,不过归一化,距离计算,关联获等还要更深入的研究。

excel数据可更改,工件数、机器数、工件的工序数、工序的可加工机器数等数据对得上就能运行。

代码

演示视频:
视频

多目标柔性车间调度丨NSGA3算法:以MK01算例为例

完整算法+数据:

# 微信公众号:学长带你飞
# 主要更新方向:1、车间调度和车辆路径问题求解算法
#              2、学术写作技巧
#              3、读书感悟
# @Author  : Jack Hao
可加微信:diligent-1618,请备注:学校-专业-昵称。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 柔性作业车间调度问题是指在具有某些约束条件的车间中,如何合理地安排设备的使用,使得所有作业能够在规定的时间内完成,同时尽量减少生产成本。 MK算例是一种常用于求解此类调度问题的解法。其基本思路是将车间中的作业划分为若干个阶段,并按照优先级进行排序。然后,按照一定的规则分配设备,并利用各种约束条件进行求解,可以得到最优的调度方案。 对于柔性作业车间调度问题,需要考虑的约束条件包括设备工作时间,设备切换时间,作业加工时间和作业之间的交错关系等。在进行算法求解时,需要根据这些约束条件进行有效地编码和解码,以实现快速而有效的求解。 总之,柔性作业车间调度问题是一项复杂而重要的生产调度任务,MK算例是一种常用的求解算法。在实践中,需要综合考虑各种约束条件,灵活运用调度策略和技巧,以提高生产效率和降低生产成本。 ### 回答2: 柔性作业车间调度是现代工业生产中非常重要的一环,它可以有效地提高生产效率、降低成本,并且能够适应生产需求的变化,实现生产的最优化。MK算例柔性作业车间调度中一个常用的算法。 MK算例是一种基于遗传算法的作业车间调度算法。它主要分为两个步骤:首先通过遗传算法的方式对作业序列进行优化,然后再根据优化后的作业序列进行车间调度。在优化作业序列时,MK算例主要考虑每个作业任务的加工时间、加工设备、工件存储区域等因素,通过遗传算法的方式对作业任务进行排列组合,得到最佳的作业序列。在车间调度时,MK算例通过生成作业开始时间的方法,确定每个作业的开工时间、加工时间和完成时间等。同时,MK算例还可以进行异常情况的处理,如设备故障、工艺异常等情况的处理。 MK算例通过遗传算法的方式对车间调度进行优化,可以有效地降低生产成本、提高生产效率,同时还可以适应不同生产环境的需求。在实际应用中,MK算例已经被广泛地应用于工业生产的各个方面,并且在大量的案例中得到了验证和应用。 ### 回答3: 柔性作业车间调度是指在生产过程中,根据客户需求和生产情况,灵活调整车间生产计划,以达到最佳的生产效益和客户满意度。而mk算例则是一个常用的调度算法,也被广泛应用于生产制造中。 在柔性作业车间调度mk算例中,需要考虑以下几个方面: 首先是任务的分配和调度车间内有多种不同类型的设备,每个设备可以完成不同的任务。在任务分配中需要考虑到设备的类型特性,同时将任务按照其完成时间、优先级等因素进行评估,然后进行优先级调度和任务分配。 其次是设备调度车间内的设备都是有限的资源,需要对其进行合理的调度。在设备调度中需要考虑它们的特性,如加工能力、维修需要等。通过对设备负载的监控和分析,实现对设备的动态调度。 再次是车间作业调度。在车间作业调度中需要考虑的是车间内多个任务之间的相互影响。通过调整车间生产计划来防止任务之间的冲突,实现车间内各项任务的协调与统一。 最后是数据的收集和分析。在柔性作业车间调度mk算例中,需要对生产过程中的各类数据进行收集和分析,以帮助生产管理人员做出更加准确的决策。通过对数据的分析,可以及时发现生产问题,并进行及时的处理。同时也为车间内生产计划的优化提供了有力的支持。 综上所述,柔性作业车间调度mk算例是一种基于计算机算法的生产管理方式,它能够有效地帮助企业提高生产效益和降低生产成本。同时也能够提高车间生产计划的灵活性和符合度,为企业的高效发展提供有力支持。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值