车间调度系列文章:
一、柔性车间调度问题
柔性车间调度问题可描述为:多个工件在多台机器上加工,工件安排加工时严格按照工序的先后顺序,至少有一道工序有多个可加工机器,在某些优化目标下安排生产。柔性车间调度问题的约束条件如下:
- (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的空载功率 |
模型:
目标函数:
c_max = min makespan
约束条件:
三、蚁群算法过程详解
1、初始工序编码
参考文献的30只蚂蚁,均匀放在10个工件的第一道工序上,后续随机选择。
- 步骤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]
如第一道工序是7,job就是一个可行工序编码。
核心编码:
def job_init(self): # 初始30只蚂蚁均匀分布,10个工件,每个工件3只
count = int(self.ant_num/self.job_num)
count1 = 0
JOB = []
for i in range(self.ant_num):
job1 = self.work.copy()
job1.remove(count1) # 取出首工序
np.random.shuffle(job1) # 打乱其余工序
job2 = [count1] + job1 # 连接
JOB.append(job2)
if (i+1)%count == 0: # 每个工件做3次,切换下一个
count1 += 1
return JOB
2、后续工序编码
按照论文,后续编码以0.7的概率轮盘赌选择,0.3的概率随机选择:
轮盘赌选择:
轮盘赌法是模拟博彩游戏的轮盘赌,扇形的面积对应它所表示的染色体的适应值的大小,适应度值被选择的可能性也就越大。轮盘赌法的关键部分是概率和累计概率的计算,具体的步骤如下:
步骤一:计算出群体中每个个体的适应度f(i=1,2,…,M),M为群体大小;计算出每个个体被遗传到下一代群体中的概率
步骤二:累加计算累积概率,如步骤1有概率为[0.2,0.3,0.5],累积概率是[0.2,0.5,1]
步骤三:随机在0,1之间产生一个数r,如果若r<q[1],则选择个体1,否则,选择个体k,使得:q[k-1]<r≤q[k]成立。
代码:
def rand_choose(self, p): #轮盘赌选择
x = np.random.rand()
q = 0
for i, px in enumerate(p):
q += px # 累计概率
if x <= q:
break
return i
3、转移概率计算
按照参考文献,转移概率由信息素浓度和启发因子计算,启发因子比较容易,工序时间分之一,信息素浓度和是否是精英解和具体的完工时间相关:
信息素更新核心代码:
def update_Tau(self, ant_group, answer): # 更新信息素
delta_tau = np.zeros([self.job_num, self.width])
best_ant = min(answer) # 精英蚂蚁
best_ant_index = [i for i in range(len(answer)) if answer[i] == best_ant] #索引精英蚂蚁索引
best_ant_num = len(best_ant_index) # 精英蚂蚁数量
tau_best = 0
for i in range(self.width):
for j in range(ant_group.shape[0]):
idx = int(ant_group[j][i])
if j in best_ant_index:
tau_best = (best_ant_num*self.Q)/answer[j] # 精英信息素计算
tau = self.Q/answer[j] # 一般信息素计算
delta_tau[idx][i] = (1 -self.ρ)*delta_tau[idx][i] + tau +tau_best # 信息素更新
if delta_tau[idx][i] > self.τmax: # 信息素约束
delta_tau[idx][i] = self.τmax
if delta_tau[idx][i] < self.τmin:
delta_tau[idx][i] = self.τmin
return delta_tau
概率矩阵计算核心代码:
def trans_p_caculate(self, delta_tau, ant_group_job, ant_group_time):
delta_p = np.zeros([self.job_num, self.width])
for i in range(self.width):
τij = delta_tau[:,i] # 信息素浓度
ant_time = [ant_group_time[j,i] for j in range(ant_group_time.shape[0])]
ant_job = [ant_group_job[j,i] for j in range(ant_group_time.shape[0])]
ηij = [0 for w in range(self.job_num)]
count = 0
for jo in ant_job:
ηij[int(jo)] += 1/ant_time[count]
count += 1
ηij = np.array(ηij) # 启发函数
pall = (τij**self.α) * (ηij**self.β) # 转移概率计算
p = pall/sum(pall)
delta_p[:,i] = p
return delta_p
4、机器和加工时间编码:
参考文献的机器编码生成方法:0.7选择最短加工时间,0.2选择开工时间最早,0.1随机选择。
核心代码
def machine_select_with_decode(self, job, decode_inf):
count = np.zeros((1, self.job_num), dtype=np.int32)
jobtime=np.zeros((1,self.job_num))
tmm=np.zeros((1,self.machine_num))
startime=0
list_M,list_S,list_W=[],[],[]
r = np.random.rand()
machine, machine_time = [], [] # 初始化矩阵
for i in range(len(job)):
svg = int(job[i])
if len(decode_inf) == 0: # 空矩阵做初始机器编码的生成,否则只做解码
index1 = count[0, svg]
highs = self.start[svg][index1]
lows = self.start[svg][index1] - self.end[svg][index1]
n_machine = self.Tmachine[svg, lows:highs].tolist()
n_time = self.Tmachinetime[svg, lows:highs].tolist()
if r < self.p1 : # 0.7选择加工时间最短的机器
idx = n_time.index(min(n_time))
machine.append(n_machine[idx])
machine_time.append(n_time[idx])
elif r<= self.p1 + self.p2: # .2选择最早开始加工工件的机器
time = []
for ma in n_machine:
time.append(tmm[0,int(ma)-1])
idx = time.index(min(time))
machine.append(n_machine[idx])
machine_time.append(n_time[idx])
else: # 否则随机挑选机器
index = np.random.randint(0, len(n_time), 1)
machine.append(n_machine[index[0]])
machine_time.append(n_time[index[0]])
if len(decode_inf) > 0:
machine = decode_inf[0]
machine_time = decode_inf[1]
sig = int(machine[i]) - 1
startime=max(jobtime[0,svg],tmm[0,sig])
tmm[0,sig]=startime+machine_time[i]
jobtime[0,svg]=startime+machine_time[i]
list_M.append(machine[i])
list_S.append(startime)
list_W.append(machine_time[i])
count[0, svg] += 1
m_last = np.argmax(tmm[0]) + 1 # 结束最晚的机器
c_max = max(tmm[0]) # 最晚完工时间
return c_max,machine, machine_time, list_M, list_S, list_W, m_last
5、解码:
安排每一道工序生产时,开工时间是上一道工序,和选择的加工机器完工时间的最大值,上面的机器编码生成时也完成解码,单独解码时由decode_inf控制。
代码主要放在fjsp.py和ACO.py里。
四、改进蚁群算法
具体的算法参考文献有介绍,简单介绍一下步骤:
算法步骤:
-
步骤1:随机和几种机器选择方式初始多个工序、机器、加工时间编码,并解码
-
步骤2:按照文献的方式更新信息素浓度,计算转移概率
-
步骤3:轮盘赌和随机方式选择工序,机器选择由最小加工时间,开工时间和随机选择相结合
-
步骤4:判断是否达到最大迭代次数,是的话输出结果,否则转到步骤2
五、结果
代码运行环境
windows系统,python3.6.0,第三方库及版本号如下:
numpy==1.18.5
matplotlib==3.2.1
第三方库需要在安装完python之后,额外安装,以前文章有讲述过安装第三方库的解决办法。
主函数
import numpy as np
from data import data_deal
from fjsp import fj
from ACO import aco
da = data_deal(10,6)
Tmachine,Tmachinetime,end,work,start,machines = da.cacu()
#print(Tmachine,Tmachinetime,tdx,work,tom,machines)
parm_fj = [10, 6, .7, .2] # 工件数,机器数,0.7的最小机器选择,0.2的最开始加工机器选择,1-0.7-0.2的随机选择
parm_mk = [Tmachine, Tmachinetime, work,start, end]
ant_num = 30 #蚂蚁数量
f = fj(parm_fj, parm_mk, ant_num)
m, Q, α, β, ρ, τmax, τmin, Nmax = 30, 100, 1, 2, .3, 50, 1, 50
# 依次是蚂蚁数量,信息素常量,信息素因子,启发函数因子,信息素挥发因子,信息素最大值, 信息素最小值, 最大迭代次数
parm_j = [10, work, .7, f] # 两个数字分别是工件数, 0.7的概率轮盘赌选择
ant = aco(m, Q, α, β, ρ, τmax, τmin, Nmax, parm_j)
job, machine, machine_time, result = ant.total()
c_max,machine, machine_time, list_M, list_S, list_W, m_last = f.machine_select_with_decode(job,[machine, machine_time])
print('验证:', c_max)
f.draw(job, machine, machine_time)
运行结果
一次迭代结果如下:
一个解的甘特图如下:
结论
-
1、本文的涉及的内容较多,有编码、解码、改进算法等等。论文主要是对文献的算法进行复习,虽然对于mk01来说,结果不是太好,可能是数据问题,或者算法迭代之类的问题,可以考虑和其他算法结合,或者调整参数之类的尝试,但这是是对蚁群算法求解车间调度等离散问题的一次尝试,参考意义较大。
-
2、excel数据可更改,工件数、机器数、工件的工序数、工序的可加工机器数等数据对得上就能运行。
-
3、参考文献:
[1]赵小惠,卫艳芳,王凯峰等.改进蚁群算法的柔性作业车间调度问题研究[J].组合机床与自动化加工技术,2022(02):165-168.DOI:10.13462/j.cnki.mmtamt.2022.02.038.
六、代码
有4个py文件和一个mk01的excel文件:
篇幅问题,代码附在后面。
演示视频:
论文复现丨蚁群算法求解柔性车间调度问题
文末
完整算法源码+数据:见下方微信公众号:关注后回复:车间调度
# 微信公众号:学长带你飞
# 主要更新方向:1、柔性车间调度问题求解算法
# 2、学术写作技巧
# 3、读书感悟
# @Author : Jack Hao