车间调度系列文章:
1、车间调度的编码、解码,调度方案可视化的探讨
2、多目标优化:浅谈pareto寻优和非支配排序遗传算法-NSGAII的非支配排序及拥挤度
3、柔性车间调度问题:以算例MK01初探数据处理和多个遗传算子
4、车间调度丨粒子群算法初探:以算例MK01为例
5、车间调度丨布谷鸟算法改进:以算例MK01为例
6、车间调度丨自适应灰狼算法改进:以算例MK01为例
7、车间调度丨模拟退火算法改进:以算例MK01为例
8、车间调度入门系列资料
9、多目标柔性车间调度丨改进灰狼算法:以算例MK01为例
10、多目标柔性车间调度丨NSGA-II:以算例MK01为例
11、书本算法重现丨遗传算法:以MK01为例
12、书本算法重现丨元胞粒子群算法:以MK01为例
13、车间调度丨遗传算法求解动态调度问题:重调度
14、柔性车间调度问题丨一种贪婪策略的应用:以算例MK02例
15、柔性车间调度问题丨教学优化算法和均匀交叉算子:以算例MK01和MK02对比
调度问题定义:在一定的约束条件下,把有限的资源在时间上分配给若干个任务,以满足或优化一个或多个性能指标。柔性车间问题的特点就是:每个工序有多机床可选。因此,要想在短时间内更好的分配资源,是很困难的,需要借助计算机来帮我们运算得出答案。
柔性车间调度问题可描述为:多个工件在多台机器上加工,工件安排加工时严格按照工序的先后顺序,至少有一道工序有多个可加工机器,在某些优化目标下安排生。柔性车间调度问题的约束条件如下:
(1)同一台机器同一时刻只能加工一个工件;
(2)同一工件的同一道工序在同一时刻被加工的机器数是一;
(3)任意工序开始加工不能中断;
(4)各个工件之间不存在的优先级的差别;
(5)同一工件的工序之间存在先后约束,不同工件的工序之间不存在先后约束;
(6)所有工件在零时刻都可以被加工。
1、 符号定义
n | 工件总数 | makespan i | 工件i的完工时间 |
---|---|---|---|
m | 机器总数 | LPz | 机器z的负载功率 |
i,h | 工件号 | ULPz | 机器z的空载功率 |
j,k | 工序号 | makespan | 最大完工时间 |
z | 机器号 | Twork | 机器总负荷 |
qi | 工件i的工序数 | Eall | 总能耗 |
oij | 工件i的第j道工序 | Xijz | 工序oij是否在机器z上加工,为0-1变量,在z上加工为1 |
Mij | 工序oij的可选机器 | Gijhk | 工序oij和工序ohk的先后顺序,为0-1变量,ij在前为1 |
Sij | 工序oij的开工时间 | M | 一个大正实数 |
Cij | 工序oij的完工时间 | Tijz | 工序oij在机器z的加工时间 |
CTz | 机器z的结束或者完工时间 |
2、模型建立
目标函数:
目标是完工时间
约束条件:
式(1)指工序开工后不能中断,式(2)指任意工序在机器上只加工1次,式(3)指任意工序的开工时间不大于完工时间,式(4)指任意工序完工时间不大于最大完工时间,式(5)指了每台机器在同一时刻只能加工一道工序,式(6)指任意工件的上一道工序的完工时间不大于下一道工序的开工时间,式(7)指任意工序的开工、加工、完工时间都非负。
MK02算例:
10 6 3.5
6 6 3 3 4 5 1 3 6 6 2 2 5 3 2 6 5 3 4 6 1 1 5 6 3 3 4 3 2 6 6 5 1 2 6 2 6 3 5 6 3 3 2 2 1 5 4
6 5 6 1 5 6 1 3 2 4 4 2 2 6 3 5 6 1 5 2 2 2 4 3 3 3 3 2 2 1 5 4 6 3 3 4 5 1 3 6 6 2 2 5 3
6 6 1 1 5 6 3 3 4 3 2 6 6 5 6 5 3 4 6 2 4 6 6 3 6 1 2 3 3 2 2 1 5 4 5 3 5 1 4 2 3 6 3 5 2 6 4 1 1 5 2 4 5 5 3 3 6 3 5 6 3 1 4 4 6 3 6 5 3
6 5 3 5 1 4 2 3 6 3 5 2 5 6 1 5 6 1 3 2 4 4 2 1 2 6 6 1 1 5 6 3 3 4 3 2 6 6 5 5 1 4 4 5 2 3 6 3 5 4 6 4 1 1 5 2 4 5 5 3 3 6 3
6 6 5 3 4 6 2 4 6 6 3 6 1 2 5 1 4 4 5 2 3 6 3 5 4 1 4 3 5 6 3 1 4 4 6 3 6 5 3 5 6 1 5 6 1 3 2 4 4 2 2 2 4 3 3
6 5 6 3 1 4 4 6 3 6 5 3 2 6 5 3 4 5 3 5 1 4 2 3 6 3 5 2 6 5 3 4 6 2 4 6 6 3 6 1 2 1 2 6 5 6 1 5 6 1 3 2 4 4 2
5 6 4 1 1 5 2 4 5 5 3 3 6 3 1 5 2 6 5 3 4 6 2 4 6 6 3 6 1 2 6 3 3 4 5 1 3 6 6 2 2 5 3 5 6 3 1 4 4 6 3 6 5 3
6 2 2 4 3 3 5 3 5 1 4 2 3 6 3 5 2 6 5 3 4 6 2 4 6 6 3 6 1 2 5 6 3 1 4 4 6 3 6 5 3 5 1 4 4 5 2 3 6 3 5 4 5 6 1 5 6 1 3 2 4 4 2
5 1 2 6 2 6 5 3 4 5 6 1 5 6 1 3 2 4 4 2 5 1 4 4 5 2 3 6 3 5 4 2 2 4 3 3
6 1 4 3 6 5 3 4 6 2 4 6 6 3 6 1 2 5 6 3 1 4 4 6 3 6 5 3 6 4 1 1 5 2 4 5 5 3 3 6 3 2 6 3 5 6 5 6 1 5 6 1 3 2 4 4 2
第一行的10,6是工件数和机器数。
第二行第一个数字6表示,工件1有6道工序。加粗的6 3 3 4 5 1 3 6 6 2 2 5 3,表示工件1的第一道工序有6个可选机器,在机器3的加工时间是3、机器4加工时间是5、机器1加工时间是3,机器6加工时间是6,机器2加工时间是2,机器5加工时间是3,后面的2 6 5 3 4表示工件1的第二道工序有2个可选机器,分别是6,3,加工时间是5,4,一行就是1个工件的所有工序的可选机器可加工时间,后面的工件以此类推。
本系列算例数据文件可在gitee仓库下载:
https://gitee.com/XZDNF-1618/data.git
贪心算法是一种局部搜索算法,其提出较早,在很多领域都有广泛应用,对于车间调度问题,其大多是与其他算法混合使用。贪心算法的核心是贪婪策略,对于问题的求解,每次贪婪策略下解的选择都是当前最好的选择,其出发点是局部最优,对于一些优化问题,其能取得很好的效果。贪心算法的两个主要性质是贪心选择和最优子结构,贪心选择性质指问题的的局部最优选择能使问题能达到整体最优,最优子结构选择指局部问题的最优解是在整个问题的最优解的组成部分。
贪心算法适合的最优化问题:问题在约束条件下有多个可行解且有最优解的存在,问题含有评价解的优劣目标函数。贪心算法的一般求解步骤如下:
(1)初始化:问题有多个输入选择,初始解集合为空;
(2)目标函数选择:根据题意选择合适的目标函数,并对多个输入选择排序;
(3)输入选择:保证解可行(局部最优)的情况下每次选择一个输入,所有输入选择完成后得到解。
柔性车间问题贪婪思想:
画一个可行调度方案的甘特图:
最晚加工机器是M5,M5首先加工的是工件3的第一道工序,工件3第一道工序加工机器和加工时间数据是6 1 1 5 6 3 3 4 3 2 6 6 5
6个可加工机器,编号1,5,3,4,2,6,时间是1,6,3,3,6,5。当前选择了机器M5,加工时间是6,
贪婪思想:调整工件3的第一道工序的加工机器,使得完工时间变低。
贪婪策略:把最晚完工机器上的工序往其他机器上安排,目的是使完工时间降低。
工序编码:
work =[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 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, 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9]
程序里为方便运算,0表示工件1,依次类推。
机器和加工时间编码:
根据工序编码读出对应工序的可加工机器和时间,随机选择可加工机器。
编码生成代码:
def creat_job(self):
job=self.work.copy()
np.random.shuffle(job) #打乱生成工序编码
job=np.array(job).reshape(1,len(self.work))
ccount=np.zeros((1,self.job_num),dtype=np.int)
machine=np.ones((1,job.shape[1]))
machine_time=np.ones((1,job.shape[1])) #初始化矩阵
for i in range(job.shape[1]):
oper=int(job[0,i])
highs=self.tom[oper][ccount[0,oper]]
lows=self.tom[oper][ccount[0,oper]]-self.tdx[oper][ccount[0,oper]]
n_machine=self.Tmachine[oper,lows:highs]
n_time=self.Tmachinetime[oper,lows:highs]
ccount[0,oper]+=1
#随机挑选机器
index=np.random.randint(0,len(n_time),1)
machine[0,i]=n_machine[index[0]] #机器编码生成
machine_time[0,i]=n_time[index[0]] #加工时间编码生成
return job,machine,machine_time
贪婪算法一般存在陷入局部最优和因为无限循环导致无法跳出迭代等问题,为避免这些问题,设计算法流程如下:
step1:找到原方案最晚完工时间的加工机器,读取机器的第一个加工工序,改变工序的加工机器,计算完工时间,
step2:如果完工时间小于原方案时间,更新加工机器和加工时间编码,转入step1,如果完工时间不小于原方案时间,不更新编码,转入step3,
step3:读取机器第二个加工工序(每次读取第i个加工工序,i每次加1),改变工序的加工机器,计算完工时间,转入step2
停止条件:当连续读取最晚完工时间机器的加工工序,改变加工机器都无法优化完工时间,贪婪结束。
代码对算法进行了比较完整的注释:
def greedy(machine,machine_time,C_finish,tmax):
ccount=np.zeros((1,10),dtype=np.int)
s_index=[]
m_index=[]
for i in range(job.shape[1]): #记录当前每道工序的位置
oper=int(job[0,i])
s_index.append([oper,ccount[0,oper]])
m_index.append(i)
ccount[0,oper]+=1
fit,Ma=C_finish,tmax #初始完工时间和完工机器
machine_last=machine[0].tolist() #加工机器情况转为列表
signal=0 #控制最晚完工时间机器上遍历工序的位置
control=0 #控制读取到机器编码的最后一道工序
memeroy=-1 #控制两次循环是否遍历到相同位置
while (control<1)and(Ma in machine_last[signal:]): #当不是机器编码的最后一道工序,且最晚完工机器还存在未遍历到的工序
sig=machine_last[signal:].index(Ma)
svg=m_index[signal:][sig] #每次遍历到最晚完工机器的加工工序位置
swg1,swg2=s_index[svg][0],s_index[svg][1] #根据位置找到加工工件和第几道工序
highs=tom[swg1][swg2]
lows=tom[swg1][swg2]-tdx[swg1][swg2]
n_machine=Tmachine[swg1,lows:highs].tolist()
n_time=Tmachinetime[swg1,lows:highs].tolist() #找到工序对应的可加工机器和时间
num=-1
if len(n_machine)>1 : #如果可加工机器数大于1
if memeroy==svg : #如果遍历到和上一次相同位置,下一次遍历从当前位置的下一个开始
signal=svg+1
if memeroy!=svg :
for ma in n_machine :
num+=1
if ma!=Ma: #选择对应工序和原方案不同的加工机器
x,y=machine[0,svg],machine_time[0,svg]
machine[0,svg]=n_machine[num]
machine_time[0,svg]=n_time[num]
C_finish,_,_,_,tmax=fj.caculate(job,machine,machine_time)
if C_finish>=fit : #如果完工时间没有变小,不更新编码,遍历位置也加1
machine[0,svg]=x
machine_time[0,svg]=y
signal=svg+1
if C_finish<fit : #如果完工时间变小,更新编码,下一次从0开始遍历
fit=C_finish
Ma=tmax
memeroy=svg #记录位置
signal=0
if len(n_machine)==1: #如果可加工机器数是1,下一次遍历从当前位置的下一个开始
signal=svg+1
if signal>len(machine_last) : #如果遍历到遍历最后一个位置,控制量变为1
control=1
return machine,machine_time
代码运行环境
windows系统,python3.6.0,第三方库及版本号如下:
numpy==1.18.5
matplotlib==3.2.1
第三方库需要在安装完python之后,额外安装,以前文章有讲述过安装第三方库的解决办法。
主函数
设计主函数如下:
da=data_deal(10,6) #数据模块
Tmachine,Tmachinetime,tdx,work,tom=da.cacu() #数据
parm_data=[Tmachine,Tmachinetime,tdx,work,tom]
fj=FJSP(10,6,parm_data) #柔性解码模块
for i in range(1): #生成多个编码并进行贪婪优化,可任意调整
job,machine,machine_time=fj.creat_job() #随机生成初始调度方案
C_finish,_,_,_,tmax=fj.caculate(job,machine,machine_time) #计算完工时间和最晚完工机器
t1=C_finish
print(C_finish)
fj.draw(job,machine,machine_time) #优化前画图,不需要可以屏蔽
machine,machine_time=greedy(machine,machine_time,C_finish,tmax) #贪婪优化
C_finish,_,_,_,tmax=fj.caculate(job,machine,machine_time) #优化后计算完工时间和最晚完工机器
print(C_finish)
fj.draw(job,machine,machine_time) #优化后画图,不需要可以屏蔽
运行结果
展示一次优化结果:
原调度方案:
贪婪优化后调度方案:
随机生成100个编码不画图的优化结果:
就贪婪策略来说,调整最晚完工机器的加工任务,难以确定能否得到最优结果,但无疑能很快降低完工时间。就本文这种贪婪策略来说,很可能对相同工序的加工机器进行重复调整,也可能更新到某个编码后,无法再进行优化,进入死循环,必须设置迭代结束条件。所以迭代结束了,并不是说明方案已经优化到最好,只能当前贪婪策略无法优化了。
就车间调度这一类组合优化问题来说,贪婪算法的这种由局部最优到全局最优的算法并一定行得通,但其算法优化效率,运行时间这些都是很优秀的,与其他算法搭配无疑会有很好的效果。
本文的贪心算法总的来说只是对工序的加工机器进行优化,如果后续想做这方面研究,可以对工序安排进行优化,或者对贪婪逻辑进行优化。
有3个代码和mk01,mk02的2个text文档,可以任意切换文档进行算法测试:
柔性车间调度问题丨一种贪婪策略的应用:以算例MK02例
知识博主创作不易,完整代码,可见微信公众号:学长带你飞,搜索XZDNF-1618,关注后回复:车间调度,查看相关超链接即可。
# 微信公众号:学长带你飞
# 主要更新方向:1、车辆路径问题和车间调度问题求解算法
# 2、学术写作技巧
# 3、读书感悟
# @Author : Jack Hao