用python实现基于蚁群算法求解带准备时间的双资源约束柔性作业车间调度问题

本文的程序实现流程主要参考以下几篇论文:

《双资源约束作业车间调度算法研究》——机械工程学报/2010/西北工业大学 李兢尧

《An improved genetic algorithm for the flexible job shop scheduling problem with multiple time constraints》  ——Swarm and Evolutionary Computation/2020

《基于蚁群算法的Job-Shop多资源约束车间作业调度》 ——系统仿真学报/2007

《基于时窗的双资源约束车间调度研究》——机械工程学报/2011/西北工业大学 李兢尧

《A New Rank Based Version of the Ant System-A Computational Study》——Central European Journal of Operations Research/1999/

问题描述:

     DRCFJSP问题的研究对象是一个w个工人操作m台设备加工n个工件的制造系统调度优化,每个加工工件有多道工序,每道工序可以有多个可选机器进行加工,工人具备操作一台以上设备的能力,且工人的柔性水平和操作效率不一样。设备的加工性能、工人效率、工人的技能水平等因素共同决定着工件的实际加工时间与加工成本。DRCFJSP问题的调度任务,就是在满足工艺约束的及双资源能力约束等条件下,通过确定最优的工序加工顺序及工序加工资源组合,获得一个或一组使某些调度性能指标最优的调度方案。

时窗策略:

DRCFJSP优化算法:

   DRCFJSP可分解为两个子问题:

(1)加工任务排序问题;

(2)双资源加工能力分配问题

蚁群算法流程:

基于蚁群系统的算法求解DRCFJSP问题的算法表诉:

     根据算法规定的数量放出蚂蚁,为保证操作符合工作的调度顺序,在每只蚂蚁寻找路径的过程中,首先判断目的节点的前驱是否已经完成,在前驱已经完成并且本身尚未完成的所有结点中,蚂蚁要选择一个结点作为移动的目标时,使用节点之间的信息素的浓度作为概率选取下一个目标,对多目标的选取过程中,借鉴遗传算法中常用的轮盘赌方法决定。蚂蚁已经经过的节点被放入tabu表中,tabu表中的节点不能作为下一步选择的节点。

     蚂蚁转移时转移可能性与轨迹信息素强度成正比。也就是说,路径越近,轨迹信息素强度越大,越容易被选择;走过的蚂蚁越多,在路径上留下的轨迹信息素越多,轨迹强度越强,同样也容易被选择,经过多次循环后,所有的蚂蚁所走过的路就必然相同,这也就是最佳路径。

状态转移规则:

蚂蚁从节点i根据以下原则选择所要下一步转移的节点s为:

路径选择的概率为:

信息素更新:

工件类:

class Job:
    def __init__(self,Job_index,Operation_num):
        self.Job_index=Job_index
        self.Operation_num = Operation_num
        self.Processed=[]
        self.Last_Processing_end_time=0
        self.Last_Processing_Machine=None

    def Process_One(self,Machine,End_time):
        self.Processed.append(1)
        self.Last_Processing_Machine=Machine
        self.Last_Processing_end_time=End_time

    def Current_Processed(self):
        return len(self.Processed)

    def _Input(self,End_time,Machine):
        self.Processed.append(1)
        self.Last_Processing_Machine=Machine
        self.Last_Processing_end_time=End_time

工人时间窗:

class worker_Time_window:
    def __init__(self,worker_index):
        self.worker_index=worker_index
        self.assigned_task=[]
        self.W_start=[]
        self.W_end=[]
        self.End_time=0

    #机器的哪些时间窗是空的,此处只考虑内部封闭的时间窗
    def Empty_time_window(self):
        time_window_start = []
        time_window_end = []
        len_time_window=[]
        if self.W_end ==[]:
            pass
        elif len(self.W_end)==1:
            if self.W_start[0]!=0:
                time_window_start=[0]
                time_window_end=[self.W_start[0]]
        elif len(self.W_end)>1:
            if self.W_start[0] != 0:
                time_window_start.append(0)
                time_window_end.append(self.W_start[0])
            time_window_start.extend(self.W_end[:-1])        #因为使用时间窗的结束点就是空时间窗的开始点
            time_window_end.extend(self.W_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 worker_burden(self):
        if len(self.W_start)==0:
            burden=None
        else:
            work_time=[self.W_end[i]-self.W_start[i] for i in range(len(self.W_start))]
            burden=sum(work_time)
        return burden

    #给工人一个任务
    def _Input(self,Job,W_eailest,worker_work_time):
        self.assigned_task.append(Job)
        self.W_start.append(W_eailest)
        self.W_start.sort()
        self.W_end.append(W_eailest+worker_work_time)
        self.W_end.sort()
        self.End_time=self.W_end[-1]

机器时间窗:

class Machine_Time_window:
    def __init__(self,Machine_index,Machine_Type):
        self.Machine_index=Machine_index
        self.Machine_type=Machine_Type
        self.assigned_task = []
        self.O_start = []
        self.O_end = []
        self.End_time=0

    #机器的哪些时间窗是空的,此处只考虑内部封闭的时间窗
    def Empty_time_window(self):
        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 Machine_Burden(self):
        if len(self.O_start)==0:
            burden=None
        else:
            processing_time=[self.O_end[i]-self.O_start[i] for i in range(len(self.O_start))]
            burden=sum(processing_time)
        return burden

    def _Input(self,Job,M_Ealiest,P_t):
        self.assigned_task.append(Job)
        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]

if __name__=="__main__":
    m=Machine_Time_window(1,0)
    m.O_end=[2,5]
    m.O_start=[1,3]
    H=m.Empty_time_window()
    print(H)

时窗策略:

import decimal
from Job import Job
from Worker_Time_window import worker_Time_window
from Machine_Time_window import Machine_Time_window
import numpy as np

class Scheduling:
    def __init__(self, M_num, W_num, Processing_time, Transportation_time, Setup_time, Worker_LE, J, M_type):
        self.Worker_LE = Worker_LE
        self.Processing_time = Processing_time
        self.Trans_time = Transportation_time
        self.Setup = Setup_time
        self.Scheduled = []                 #已经排产过的工序
        self.M_num = M_num
        self.W_num = W_num
        self.workers = []  # 存储工人类
        self.Machines = []  # 存储机器类
        self.fitness=None
        for i in range(W_num):
            self.workers.append(worker_Time_window(i))
        for j in range(len(M_type)):
            self.Machines.append(Machine_Time_window(j, M_type[j]))
        self.Machine_State = np.zeros(M_num, dtype=int)  # 在机器上加工的工件是哪个
        self.worker_site = np.zeros(W_num, dtype=int)  # 工人所在位置
        self.Jobs = []
        for k, v in J.items():
            self.Jobs.append(Job(k, v))

    def Burden_based_Machine_select(self,Job):
        O_num = self.Jobs[Job].Current_Processed()
        # print(Job,O_num)
        m=self.Processing_time[Job][O_num][1]
        Machine_option = [[i, self.Processing_time[Job][O_num][i]] for i in range(len(self.Processing_time[Job][O_num]))
                          # [i,j]表示:机器号,加工时间
                          if self.Processing_time[Job][O_num][i] != 9999]
        # 根据负荷程度来选择机器,尽量选择负荷最小的机器
        M_burden = []
        M_not_burdened=[]
        for j in range(len(Machine_option)):
            if self.Machines[j].Machine_Burden() is not None:
                M_burden.append(Machine_option[j])
            else:
                M_not_burdened.append(Machine_option[j])
        if M_not_burdened !=[]:            #如果存在未负荷机器
            M_list = [M_not_burdened[m_i][1] for m_i in range(len(M_not_burdened))]
            Selected_Machine=M_not_burdened[M_list.index(min(M_list))][0]
        elif M_burden is not None and M_not_burdened is not None:
            M_list = [M_burden[m_i][1] for m_i in range(len(M_burden))]
            Selected_Machine = M_burden[M_list.index(min(M_list))][0]
        return Selected_Machine

    def Setup_and_trans_time(self,O_num,Job,Machine):
        Last_O_Machine=self.Jobs[Job].Last_Processing_Machine
        S=self.Setup[Job][O_num][Machine]
        if Last_O_Machine is None:
            Trans=0
        else:
            Trans=self.Trans_time[Last_O_Machine][Machine]
        if Machine==Last_O_Machine:
            S=0
            Trans=0
        return S,Trans

    def earliest_start_time(self, Job):
        O_num=self.Jobs[Job]. Current_Processed()
        last_O_end=self.Jobs[Job].Last_Processing_end_time      #上道工序结束时间
        Selected_Machine=self.Burden_based_Machine_select(Job)  #选择使用的机器
        Setup_Trans=self.Setup_and_trans_time(O_num,Job,Selected_Machine)
        S=Setup_Trans[0]        #准备时间
        # Trans=Setup_Trans[1]    #运输时间                 #暂时先不考虑运输时间,之后考虑的话再加进去
        Machine_type=self.Machines[Selected_Machine].Machine_type
        P_t=self.Processing_time[Job][O_num][Selected_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]
        #可以操作这个机器的工人
        W_LE=list(self.Worker_LE[:,Selected_Machine])
        Worker_option=[i for i in range(len(W_LE)) if W_LE[i]!=0]
        Machine_end_time=self.Machines[Selected_Machine].End_time
        if Machine_type == 1:  # 如果机器为数控机床
            worker_work_t = S
        else:  # 如果机器为数控机床
            worker_work_t = S + P_t
        ealiest_start=[]
        for j in Worker_option:
            worker_end_time = self.workers[j].End_time
            W_window = self.workers[j].Empty_time_window()
            W_Tstart = W_window[0]
            W_Tend = W_window[1]
            W_Tlen = W_window[2]
            Worker_ealiest_start =max(last_O_end,worker_end_time,Machine_end_time)
            if W_Tlen is not None:
                for le_i in range(len(W_Tlen)):
                    if W_Tlen[le_i]> worker_work_t :
                        if W_Tstart[le_i]>=last_O_end :
                            if Machine_end_time<=W_Tstart[le_i]:
                                Worker_ealiest_start = W_Tstart[le_i]
                            if Machine_end_time>W_Tstart[le_i] and W_Tend[le_i] -Machine_end_time>worker_work_t:
                                Worker_ealiest_start = Machine_end_time
                        if W_Tstart[le_i]<last_O_end and W_Tend[le_i]-last_O_end>=worker_work_t:
                            for M_len in range(len(M_Tlen)):
                                if M_Tend[M_len]-last_O_end-S>P_t and M_Tstart[M_len]<=W_Tstart[le_i] :
                                    Worker_ealiest_start = last_O_end
            ealiest_start.append(Worker_ealiest_start)
        W_Ealiest=min(ealiest_start)
        M_Ealiest=W_Ealiest+S
        Selected_worker=Worker_option[ealiest_start.index(W_Ealiest)]
        return W_Ealiest,M_Ealiest,Selected_Machine,Selected_worker,worker_work_t,P_t,O_num

    def add_job(self,Job,W_Ealiest,M_Ealiest,Selected_Machine,Selected_worker,worker_work_t,P_t,O_num):
        self.Scheduled.append([Job,O_num])
        self.Machines[Selected_Machine]._Input(Job,M_Ealiest,P_t)
        self.workers[Selected_worker]._Input(Job,W_Ealiest,worker_work_t)
        End_time=M_Ealiest+P_t
        self.Jobs[Job]._Input(End_time,Selected_Machine)
        self.fitness=End_time

蚁群算法:

import numpy as np
import random
from Scheduling import  Scheduling
from Worker_Time_window import worker_Time_window
from Machine_Time_window import Machine_Time_window
from Job import Job
from Instance1 import Processing_time,Setup_time,Transpotation_time,worker_LE,M_type,J,M_num,O_Max_len,J_num,O_num,Worker_num
import matplotlib.pyplot as plt

class TACOSA:   #tuba-ant-colony-optimal-simulated annealing
    def __init__(self,J_num,O_num,alpha=1,beita=5,p=0.1,N_max=100,S=10):
        self.J_num=J_num        #工件总类
        self.O_total=O_num     #总工序数
        self.alpha=alpha        #信息素启发因子
        self.beita=beita        #期望启发因子
        self.p=p                #信息素蒸发率
        self.N_max=N_max        #最大迭代次数
        self.S=S                #蚂蚁总数
        self.Ant_Map=np.ones((J_num,O_num),dtype=float)    #蚂蚁地图
        self.P0=0.7             #目前暂定为这样
    #候选集
    def Candidate_set(self,Jobs):  # 候选解集
        S_site=[]
        S_t0=[]
        priv_Job_O_num=0
        for i in range(len(Jobs)):
            if Jobs[i].Current_Processed()<Jobs[i].Operation_num:
                S_site.append(i)
                S_t0.append(self.Ant_Map[i,priv_Job_O_num+Jobs[i].Current_Processed()])
            priv_Job_O_num+=Jobs[i].Operation_num
        return S_t0,S_site

    def heur_info(self,Ealiest_time_Set,Ealiest):   #即为Nxy
        N=0
        for i in Ealiest_time_Set:
            N+=1/(i+1)
        return ((1/(Ealiest+1))/N)

    #状态转移规则
    def State_trans_rule(self,Ealiest_Set,S_0):
        N_xy=[]     #工序启发式信息
        for i in Ealiest_Set:
            N_xy.append(self.heur_info(Ealiest_Set,i))
        Sum_t=0
        Argmax=[]
        for j in range(len(Ealiest_Set)):
            Sum_t+=S_0[j]**self.alpha*N_xy[j]**self.beita
            Argmax.append(S_0[j]**self.alpha*N_xy[j]**self.beita)
        Probis=[k/Sum_t for k in Argmax ]       #路径选择概率
        Sum_argmax=Probis.index(max(Probis))
        #选择下一步转移的节点
        roulette=[]     #装盘赌选择下一步转移节点
        lun=0
        roulette.append(lun)
        for A_i in Probis:
            lun+=A_i
            roulette.append(lun)
        q0=random.random()
        for r_i in range(len(roulette)):
            if roulette[r_i]>q0:
                S=r_i-1
                break
        if random.random()<self.P0:
            Site=Sum_argmax
        else:
            Site=S
        return Site

    def Operation_Site(self,J,Job,Operation):
        O_num=0
        for i in range(len(J)):
            if i==Job:
                return O_num+Operation
            else:
                O_num=O_num+J[i+1]



    #双向收敛策略:取目前调度时刻为止最好和部分最差路径,对其进行惩罚。
    def Bi_directional_convergence_strategy(self,Best,Wrost,Ant,J):
        for i in range(len(Best)):
            Ant_i=Ant[Best[i][0]]
            Process_Sequence=Ant_i.Scheduled
            for S_i in range(len(Process_Sequence)):
                O_Site = self.Operation_Site(J,Process_Sequence[S_i][0],Process_Sequence[S_i][1])
                self.Ant_Map[Process_Sequence[S_i][0]][O_Site]=\
                    (1-self.p)*self.Ant_Map[Process_Sequence[S_i][0]][O_Site]+(1/Best[i][1])
        for j in range(len(Wrost)):
            Ant_j=Ant[Wrost[j][0]]
            Process_Sequence=Ant_j.Scheduled
            for S_j in range(len(Process_Sequence)):
                O_Site = self.Operation_Site(J, Process_Sequence[S_j][0], Process_Sequence[S_j][1])
                self.Ant_Map[Process_Sequence[S_j][0]][O_Site] = \
                    (1 - self.p) * self.Ant_Map[Process_Sequence[S_j][0]][O_Site] - (1 / Wrost[j][1])

    #寻找部分最优解和部分最劣解。
    def Select_Best_and_Worst_taril(self,Ant_fitness):
        Fit=[]
        for i in range(len(Ant_fitness)):
            Fit.append([i,Ant_fitness[i]])
        Fit=dict(Fit)
        Fit=sorted(Fit.items(),key=lambda x:x[1])
        eliest_num=int(0.1*self.S)
        Worst=Fit[-eliest_num:-1]
        Worst.append(Fit[-1])
        Best=Fit[0:eliest_num]
        return Best,Worst

    def Gantt(self,Machines,Workers):
        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.5,left=Start_time[i_1],\
                         color=M[Machine.assigned_task[i_1]],edgecolor='black')
                plt.text(x=Start_time[i_1]+0.1,y=i,s=Machine.assigned_task[i_1])
        # for j in range(len(Workers)):
        #     worker=Workers[j]
        #     Start_time=worker.W_start
        #     End_time=worker.W_end
        #     for j_1 in range(len(End_time)):
        #         plt.barh(i+j_1+1,width=End_time[j_1]-Start_time[j_1],height=0.5,left=Start_time[j_1],\
        #                  color=M[j],edgecolor='black')
        #         plt.text(x=Start_time[j_1] + 0.1, y=i+j_1+1, s=j)
        plt.yticks(np.arange(i+1), np.arange(1, i+2))
        plt.show()

    #用作调试
    def Jobs_Situation(self,Jobs):
        for i in range(len(Jobs)):
            print(Jobs[i].Processed)

    #用作调试
    def Machines_end_time(self,Machines):
        Max_Endtime=0
        for i in range(len(Machines)):
            if Machines[i].O_end[-1]>Max_Endtime:
                Max_Endtime=Machines[i].O_end[-1]
        return Max_Endtime

    #蚁群算法主程序
    def ACO(self, M_num, W_num, Processing_time, Transpotation_time, Setup_time, Worker_LE, J, M_type):
        Set=[]
        Ant_best_fit=[]
        best_fit = 9999
        Best_Roat=None
        x=np.linspace(0,100,100)
        for i in range(self.N_max):
            Ant_roat=[]
            Ant_fitness=[]
            for j in range(self.S):
                Ant_Scheduling=\
                    Scheduling( M_num, W_num, Processing_time,Transpotation_time, Setup_time, Worker_LE, J, M_type)
                J_s=Ant_Scheduling.Jobs         #工件集
                S_0=self.Candidate_set(J_s)
                while len(S_0[0]) >=1:
                    Ealiest_Set=[]          #最早开始时间集
                    for S_i in range(len(S_0[0])):
                        Job=S_0[1][S_i]
                        Ealiest_i=Ant_Scheduling.earliest_start_time(Job)
                        Ealiest_Set.append(Ealiest_i)
                    Job_Ealiest_Start_Set=[]
                    for E_i in range(len(Ealiest_Set)):
                        Job_Ealiest_Start_Set.append(Ealiest_Set[E_i][0])
                    Job_Site=self.State_trans_rule(Job_Ealiest_Start_Set,S_0[0])
                    Seleted_Job=S_0[1][Job_Site]
                    Para=Ealiest_Set[Job_Site]
                    Ant_Scheduling.add_job(Seleted_Job,Para[0],Para[1],Para[2],Para[3],Para[4],Para[5],Para[6])
                    S_0 = self.Candidate_set(J_s)
                Ant_roat.append(Ant_Scheduling)
                fitness= self.Machines_end_time(Ant_Scheduling.Machines)
                Ant_fitness.append(fitness)
            Trail_Situation=self.Select_Best_and_Worst_taril(Ant_fitness)
            Best=Trail_Situation[0]
            Wrost=Trail_Situation[1]
            self.Bi_directional_convergence_strategy(Best, Wrost, Ant_roat,J)
            Best_Ant=Trail_Situation[0][0]
            Set.append(Ant_roat[Best[0][0]])
            if Best[0][1]<best_fit:
                best_fit=Best[0][1]
                Best_Roat=Ant_roat[Best[0][0]]
            Ant_best_fit.append(best_fit)
        plt.plot(x,Ant_best_fit)
        plt.show()
        self.Gantt(Best_Roat.Machines,Best_Roat.workers)
ac=TACOSA(J_num,O_num)
ac.ACO(M_num, Worker_num, Processing_time, Transpotation_time, Setup_time, worker_LE, J, M_type)

例子:

import numpy as np

Processing_time=[
    [[2,5,4,1,2],
    [5,4,5,7,5],
    [4,5,5,4,5],
    [3,3,2,4,5]],

    [[2,5,4,7,8],
    [5,6,9,8,5],
    [4,5,4,54,5],
    [4,7,6,4,5]],

    [[9,8,6,7,9],
     [6,1,2,5,4],
     [2,5,4,2,4],
     [4,5,2,1,5]],

    [[1,5,2,4,12],
     [5,1,2,1,2],
     [4,3,2,4,5]]
]


Setup_time=[
    [[1,3,1,1,2],
     [3,1,2,6,1],
     [1,4,4,2,2],
     [4,3,4,3,4]],

    [[2,4,1,5,7],
     [1,6,7,4,3],
     [2,3,3,43,4],
     [3,4,5,2,3]],

    [[4,7,4,3,9],
     [6,1,2,3,1],
     [1,5,1,1,2],
     [4,1,2,1,3]],

    [[1,1,1,8,8],
     [4,1,1,1,1],
     [3,4,5,3,4]]
]


Transpotation_time=[
    [0,4,2,1,5],
    [1,0,5,1,5],
    [1,3,0,2,3],
    [3,1,3,0,1],
    [4,2,5,3,0]
]
Transpotation_time=np.array(Transpotation_time)
Worker_num=3
worker_LE=[
    [0,0.9,0.8,0,0.85],
    [0,0.9,0,0.85,0.8],
    [0.9,0,0.85,0.8,0],
]
worker_LE=np.array(worker_LE)

M_type=[0,1,0,0,1]
J={1:4,2:4,3:4,4:3}
M_num=5
O_Max_len=4
J_num=4
O_num=15

结果:

下面的甘特图:1-5表示机器,6-8表示工人 

注:由于算例规模太小,测试结果难以说明问题,未完待续!

猜你喜欢

转载自blog.csdn.net/crazy_girl_me/article/details/113574851