本文的程序实现流程主要参考以下几篇论文:
《双资源约束作业车间调度算法研究》——机械工程学报/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表示工人
注:由于算例规模太小,测试结果难以说明问题,未完待续!