书本算法重现丨遗传算法:以算例MK01为例


车间调度系列文章:

引言

算法重现系列文章,都是对书本《柔性作业车间调度智能算法及其应用》一书的算法实现,该书作者:高亮、张国辉、王晓娟。具体电子书:可关注公众号后,回复:调度,获得。

问题描述

柔性车间调度问题可描述为:多个工件在多台机器上加工,工件安排加工时严格按照工序的先后顺序,至少有一道工序有多个可加工机器,在某些优化目标下安排生产。柔性车间调度问题的约束条件如下:

  • (1)同一台机器同一时刻只能加工一个工件;
  • (2)同一工件的同一道工序在同一时刻被加工的机器数是一;
  • (3)任意工序开始加工不能中断;
  • (4)各个工件之间不存在的优先级的差别;
  • (5)同一工件的工序之间存在先后约束,不同工件的工序之间不存在先后约束;
  • (6)所有工件在零时刻都可以被加工。

MK01算例:

10 6 2
6 2 1 5 3 4 3 5 3 3 5 2 1 2 3 4 6 2 3 6 5 2 6 1 1 1 3 1 3 6 6 3 6 4 3
5 1 2 6 1 3 1 1 1 2 2 2 6 4 6 3 6 5 2 6 1 1
5 1 2 6 2 3 4 6 2 3 6 5 2 6 1 1 3 3 4 2 6 6 6 2 1 1 5 5
5 3 6 5 2 6 1 1 1 2 6 1 3 1 3 5 3 3 5 2 1 2 3 4 6 2
6 3 5 3 3 5 2 1 3 6 5 2 6 1 1 1 2 6 2 1 5 3 4 2 2 6 4 6 3 3 4 2 6 6 6
6 2 3 4 6 2 1 1 2 3 3 4 2 6 6 6 1 2 6 3 6 5 2 6 1 1 2 1 3 4 2
5 1 6 1 2 1 3 4 2 3 3 4 2 6 6 6 3 2 6 5 1 1 6 1 3 1
5 2 3 4 6 2 3 3 4 2 6 6 6 3 6 5 2 6 1 1 1 2 6 2 2 6 4 6
6 1 6 1 2 1 1 5 5 3 6 6 3 6 4 3 1 1 2 3 3 4 2 6 6 6 2 2 6 4 6
6 2 3 4 6 2 3 3 4 2 6 6 6 3 5 3 3 5 2 1 1 6 1 2 2 6 4 6 2 1 3 4 2

第一行的10,6是工件数和机器数。

第二行第一个加粗的数字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个工件的所有工序的可选机器可加工时间,后面的工序以此类推。

下面的每一行以此类推。本系列算例MK01.txt数据文件可在gitee仓库下载:

https://gitee.com/XZDNF-1618/data.git

数学模型

字符说明:

目标函数:

约束条件:

柔性作业车间工具

本文写了甘特图的画图函数;工序,机器,加工时间编码的生成函数;编码的解码函数。甘特图和解码前面推文有介绍,为了能在遗传算法使用,下面介绍一下编码的生成:

  • 步骤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就是一个可行工序编码。

代码在fjsp.py里。

机器和加工时间编码:

参考文献的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[i].append(m_select)
    machine_time[i].append(n_time[t_index])
else:                               #否则随机挑选机器                                
    index=np.random.randint(0,len(n_time),1)
    machine[i].append(n_machine[index[0]])
    machine_time[i].append(n_time[index[0]])

遗传算法

遗传算法(Genetic Algorithm,GA)是由John Holland教授在19世纪70年代率先提出,它是模拟自然界“优胜劣汰”淘汰机制的一种智能优化算法。由于该算法的算法稳健性、求解问题的广泛性及坚实的生物基础,该算法法一提出就受到很多学者亲睐。40多年国内外学者的共同研究已经让该算法变得成熟,其应用也较广。

相较于其它算法来说,遗传算法已经是一种较为成熟的算法,其在是算法本身的理论研究,与其他算法、方法的融合以及应用都取得很多成果。遗传算法的求解思路是模拟染色体基因的交叉、重组和突变,设计相关操作算子对问题解进行迭代,在达到最大迭代次数或者满足收敛条件得到问题的解。基于物种遗传规律设计的操作算子,一般包括选择、交叉和变异等算子。

遗传算法的基本要素主要包括:特定问题的染色体编码方式(解的表达)、种群的初始化方法、解码方式、选择、交叉和变异操作。基本遗传算法的流程描述如下:

Stepl:确定染色体编码方式,初始化一定规模的种群;

Step2:特定的解码方法对染色体解码,计算各个染色体的适应度值;

Step3:在交叉概率下对种群的个体进行交叉操作;

Step4:在变异概率下种群的个体进行变异操作;

Step5:按照选择策略选择下一代种群;判断是否达到终止条件,如果满足,则输出优化结果,否则转至Step2

遗传算法设计

参考文献的工序编码的变异方法是找出邻域最优的个体,计算量是所选择基因个数的阶乘,比较复杂,选择两个点对换基因的方式进行变异,即逆转变异。

简单来说:每次迭代以上一次迭代得到种群为基础,工序编码和机器编码分别进行pox交叉和均匀交叉,然后进行逆转变异和选择最小加工机器的变异,第一次迭代以初始种群为基础。
算法步骤:

  • 步骤1:初始化多个工序,机器,加工时间编码,解码得到加工时间
  • 步骤2:交叉概率下工序编码进行pox交叉,机器编码进行均匀交叉
  • 步骤3:变异概率下工序编码进行逆转变异,机器编码进行挑选最小加工机器
  • 步骤4:判断是否达到最大迭代次数,是的话输出结果,否则锦标赛选择种群,转到步骤2

pox交叉
以mk01为例:初始工件编号为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

均匀交叉

均匀交叉算子的概念比较简单,简单说一下逻辑:假设两个解的工序编码的第一道工序分别选择了机器1和3,随机生成0,1两个数中的一个,如果随机数是1,交换两个解第一道工序的机器选择,否则保持原选择。

核心代码:

def ma_cross(self,m1,t1,m2,t2):  #机器均匀交叉
    MC1,MC2,TC1,TC2=[],[],[],[]
    for i in range(len(self.machines)):     
        MC1.append([]),MC2.append([]),TC1.append([]),TC2.append([]);
        for j in range(self.machines[i]):
            index=np.random.randint(0,2,1)[0]
            if(index==0):  #为0时继承继承父代的机器选择
                MC1[i].append(m1[i][j]),MC2[i].append(m2[i][j]),TC1[i].append(t1[i][j]),TC2[i].append(t2[i][j]);
            else:                #为1时另一个父代的加工机器选择
                MC2[i].append(m1[i][j]),MC1[i].append(m2[i][j]),TC2[i].append(t1[i][j]),TC1[i].append(t2[i][j]);
    return MC1,TC1,MC2,TC2

逆转变异

比较简单,交换两个位置的基因。代码:

def job_mul(self,job):
    location=random.sample(range(job.shape[1]),2)
    job[0,location[0]],job[0,location[1]]=job[0,location[1]],job[0,location[0]]
    return job

选择最小加工机器变异

首先挑选位置,然后对应位置的机器编码变为最小加工时间的机器,代码:

def ma_mul(self,machine,machine_time):
    for i in range(len(self.machines)):
        r=np.random.randint(0,self.machines[i],1)[0]   #挑选位置
        mul_idx=random.sample(range(self.machines[i]),r)
        for j in mul_idx:
            highs=self.tom[i][j]
            lows=self.tom[i][j]-self.tdx[i][j]         
            n_machine=self.Tmachine[i,lows:highs]      #取出加工机器
            n_time=self.Tmachinetime[i,lows:highs]      #取出加工时间
            machine_time[i][j]=min(n_time)              #挑选最小加工时间
            index=np.argwhere(n_time==machine_time[i][j])
            machine[i][j]=n_machine[index[0,0]]
    return machine,machine_time

锦标赛选择

对种群的个体进行放回抽样,每次随机抽取多个,选择最优的个体,代码:

for i in range(self.popsize):
    cab=random.sample(range(self.popsize*2),self.C_size)      #按照C_size生成一组不重复的索引用于锦标赛选择 
    index,time=[],[]
    for j in range(self.C_size):
        index.append(cab[j]),time.append(answer2[cab[j]]);  
    min_time=time.index(min(time))
    min_idx=index[min_time]
    work_job[i],work_M[i],work_T[i]=work_job2[min_idx],work_M2[min_idx],work_T2[min_idx] #选择出的个体,用于下次遗传
    answer[i]=answer2[min_idx]

具体的整个遗传算法代码细节在ga.py里。

结果

代码运行环境
windows系统,python3.6.0,第三方库及版本号如下:

numpy==1.18.5
matplotlib==3.2.1

第三方库需要在安装完python之后,额外安装,以前文章有讲述过安装第三方库的解决办法。

命令

oj=data_deal(10,6)               #工件数,机器数
Tmachine,Tmachinetime,tdx,work,tom,machines=oj.cacu()
parm_data=[Tmachine,Tmachinetime,tdx,work,tom,machines]
to=FJSP(10,6,0.3,0.4,parm_data)      #工件数,机器数,3种选择的概率和mk01的数据

ho=GA(50,100,to,0.8,0.1,parm_data,4)     #4个数依次是迭代次数,种群规模,交叉概率,变异概率和锦标赛选择框的大小
job,machine,machine_time,result=ho.ga_total()
result=np.array(result).reshape(len(result),2)
plt.plot(result[:,0],result[:,1])                   #画完工时间随迭代次数的变化
font1={'weight':'bold','size':22}
plt.xlabel("迭代次数",font1)
plt.title("完工时间变化图",font1)
plt.ylabel("完工时间",font1)
plt.show()
to.draw(job,machine,machine_time)#画甘特图

运行结果

结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qNrjsD33-1646702502674)(https://gitee.com/XZDNF-1618/picture/raw/master/2022-2-8/1644293564216-11%20(1)].png)

帕累托解中完工时间迭代次数的变化图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Ofxdlu8-1646702502675)(https://gitee.com/XZDNF-1618/picture/raw/master/2022-2-8/1644293602867-11%20(2)].png)

解的甘特图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BvvpovRd-1646702502675)(https://gitee.com/XZDNF-1618/picture/raw/master/2022-2-8/1644293602865-11%20(3)].png)

结论

完工时间大约能优化到42左右,可以调整参数,或者改进遗传算子。

甘特图有时会出现bug,和算法结果不一样,大多数时候是正确的,太过繁琐没有解决这个bug。
参考文献:柔性作业车间调度智能算法及其应用-高亮

车间调度问题完整代码,详见微信公众号:学长带你飞
扫描下方二维码,关注并回复关键词:车间调度,可得。

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值