作业车间调度问题
问题描述
一个加工系统共有m台机器,需要加工n个加工顺序不同的工件。
已知:
(1)
工
件
集
P
=
{
p
1
,
p
2
,
…
,
p
n
}
,
其
中
p
i
为
第
i
个
工
件
,
i
=
1
,
2
,
…
,
n
,
;
工件集P= \{ p_1,p_2,…,p_n \} ,其中p_i 为第i个工件,i=1,2,…,n,;
工件集P={p1,p2,…,pn},其中pi为第i个工件,i=1,2,…,n,;
(2)
机
器
集
M
=
{
m
1
,
m
2
,
…
,
m
m
}
,
m
j
为
第
j
号
机
器
,
j
=
1
,
2
,
…
,
m
;
机器集M=\{m_1,m_2,…,m_m\},m_j为第j号机器,j=1,2,…,m;
机器集M={m1,m2,…,mm},mj为第j号机器,j=1,2,…,m;
(3)
工
序
集
O
P
=
{
o
p
1
,
o
p
2
,
…
,
o
p
n
}
,
o
p
i
=
{
o
p
i
1
,
o
p
i
2
,
…
,
o
p
i
m
}
为
工
件
p
i
的
工
序
序
列
。
o
p
i
k
为
第
i
个
工
件
的
第
k
道
工
序
使
用
的
机
器
号
,
o
p
i
k
=
0
表
示
工
件
p
i
在
第
k
道
工
序
不
加
工
,
k
=
1
,
2
,
…
,
m
;
工序集OP=\{op_1,op_2,…,op_n\},op_i=\{op_{i1},op_{i2},…,op_{im}\}为工件p_i的工序序列。op_{ik}为第i个工件的第k道工序使用的机器号,op_{ik}=0表示工件p_i在第k道工序不加工,k=1,2,…,m;
工序集OP={op1,op2,…,opn},opi={opi1,opi2,…,opim}为工件pi的工序序列。opik为第i个工件的第k道工序使用的机器号,opik=0表示工件pi在第k道工序不加工,k=1,2,…,m;
(4)
每
个
工
件
使
用
每
台
机
器
的
时
间
矩
阵
T
=
{
t
i
j
}
,
t
i
j
为
第
i
个
工
件
p
i
使
用
第
j
台
机
器
的
时
间
。
t
i
j
=
0
表
示
工
件
p
i
不
使
用
机
器
j
。
每个工件使用每台机器的时间矩阵T=\{t_{ij}\},t_{ij}为第i个工件p_i使用第j台机器的时间。t_{ij}=0表示工件p_i不使用机器j。
每个工件使用每台机器的时间矩阵T={tij},tij为第i个工件pi使用第j台机器的时间。tij=0表示工件pi不使用机器j。
JSP需要满足下列约束条件:
(1) 每个工件使用每台机器不多于1次;
(2) 每个工件利用每台机器的顺序可以不同,即可以有
o
p
i
≠
o
p
d
(
i
≠
d
)
op_i≠op_d(i≠d)
opi=opd(i=d);
(3) 每个工件的工序必须依次加工,后工序不能先于前工序;
(4) 任何工件没有抢先加工的优先权,应服从任何生产顺序;
(5) 工件加工过程中没有新工件加入,也不临时取消工件的加工。
调度目标通常是最小化最大完工时间,即
m
i
n
{
m
a
x
{
c
i
}
}
,
i
=
1
,
2
,
…
,
n
min\{max\{c_i\}\},i=1,2,…,n
min{max{ci}},i=1,2,…,n。式中
c
i
c_i
ci表示工件
i
i
i的完工时间。
对于以上描述的调度问题,调度算法的任务就是在许可的计算时间内得到最优或是较优的加工顺序。
问题模型
令
(
i
,
j
)
(i,j)
(i,j)表示作业
i
i
i的第
j
j
j个工序。
S
i
j
S_{ij}
Sij和
T
i
j
T_{ij}
Tij分别表示
(
i
,
j
)
(i,j)
(i,j)的加工起始时刻和加工时间。
Z
i
j
k
Z_{ijk}
Zijk表示
(
i
,
j
)
(i,j)
(i,j)是否在第
k
k
k台机器上加工:如果
(
i
,
j
)
(i,j)
(i,j)在第
k
k
k台机器上加工,
Z
i
j
k
=
1
Z_{ijk}=1
Zijk=1;否则,
Z
i
j
k
=
0
Z_{ijk}=0
Zijk=0,
C
k
C_k
Ck为第
k
k
k台机器的完工时间,则问题的数学模型如下:
公式(1)为目标函数,即优化目标,系统中使用总加工时间最短为优化目标。公式(2)表示1个作业只能在加工完成前一道工序后才可以加工后一道工序。公式(3)表示1个作业的第1道工序的起始加工时刻大于或等于0。公式(4)表示在1台机床上不会同时加工1个以上的作业。
举个栗子
假如此时有3个工件需要再3台机器上加工,不同工件所需的加工工序及加工时间可以用以下公式表示:
J
o
b
0
=
[
(
0
,
3
)
,
(
1
,
2
)
,
(
2
,
2
)
]
Job_0=[(0,3),(1,2),(2,2)]
Job0=[(0,3),(1,2),(2,2)]
J
o
b
1
=
[
(
0
,
2
)
,
(
2
,
1
)
,
(
1
,
4
)
]
Job_1=[(0,2),(2,1),(1,4)]
Job1=[(0,2),(2,1),(1,4)]
J
o
b
2
=
[
(
1
,
4
)
,
(
2
,
3
)
]
Job_2=[(1,4),(2,3)]
Job2=[(1,4),(2,3)]
在这个例子中,作业
j
o
b
0
job_0
job0有3道工序:它的第1道工序上标注有(0,3),表示第1道工序必须在第0台机器上进行加工,且需要3个单位的加工时间;它的第2道工序上标注有(1,2),其表示第2道工序必须在第1台机器上进行加工,且需要2个单位的加工时间;余下的同理。总的来说,这个实例中共有8道工序。
下图用甘特图表示了一种可行解:
该解所需的最长时间为11工时。
禁忌搜索与JSP
有关禁忌搜索算法的内容,公众号内有详细教程:
干货 |【算法】禁忌搜索算法(Tabu Search,TS)超详细通俗解析附C++代码实例
干货 | 到底是什么算法,能让人们如此绝望?
禁忌搜索算法求解带时间窗的车辆路径规划问题详解(附Java代码)
大家可以点击超链接回顾相关知识。
一般而言,用禁忌搜索算法解决问题时,需要注意的点无非就是以下几个:
初始解的生成;
禁忌对象的选择;
邻域动作算子的选择。
我们简单介绍代码中使用的算子:
Neighbor1类:对同一机器上的两道相邻工序
o
1
,
o
2
o_1,o_2
o1,o2,交换两道工序的前后顺序。例如交换图例中
m
a
c
h
i
n
e
0
machine_0
machine0中
j
o
b
0
job_0
job0和
j
o
b
1
job_1
job1的顺序。交换后,
m
a
c
h
i
n
e
0
machine_0
machine0上的加工顺序为
{
j
o
b
0
,
j
o
b
1
}
{job_0,job_1}
{job0,job1}。
NeighborA类:对同一机器上的三道不同的工序
o
1
,
o
2
,
o
3
o_1,o_2,o_3
o1,o2,o3,满足
o
1
o_1
o1和
o
2
o_2
o2相邻,或
o
2
o_2
o2与
o
3
o_3
o3相邻。找到最早开始加工的工件的位置
k
k
k,按
o
3
,
o
2
,
o
1
o_3,o_2,o_1
o3,o2,o1的顺序加入
k
k
k处。例如图例中
m
a
c
h
i
n
e
1
machine_1
machine1的三道工序,交换后的加工顺序为
{
j
o
b
0
,
j
o
b
1
,
j
o
b
2
}
{job_0,job_1,job_2}
{job0,job1,job2}。
禁忌表为工序总数*工序总数大小的二维表。对Neighbor1,禁忌边
(
o
1
,
o
2
)
(o_1,o_2)
(o1,o2);对NeighborA,禁忌边
(
o
1
,
o
3
)
,
(
o
2
,
o
3
)
(o_1,o_3),(o_2,o_3)
(o1,o3),(o2,o3)。
如果所有边都被禁忌,随机选择某一组工序进行变换。
若执行邻域动作后的新解优于历史最优解,则不会被禁忌表禁忌。
代码展示
代码是github上的开源代码,作者是Thiebout Dewitte。我们简单介绍一下代码,感兴趣的朋友可以进入公众号下载代码(附小编的详细注解哦!)。
输入部分
输入算例格式如下:
第一行为注释部分。
第二行数字分别为工件数、机器数。
输出部分
运行代码时,可以多种运行方式:
在Main.java文件内选择所需运行模式,算例设置也在同一文件中。
测试单一算例:使用opendeurdagKulak()方法。将测试算例路径放入Main.java中:
测试算例附带在代码内。
结果生成在编译器内部:
前三行按照机器顺序排列,cost表示总耗时,最后一行表示最长耗时的加工顺序。
测试多个算例,分别生成table1、2:
在上方输入算例所在文件夹,下方输入输出部分文件名。
table输出可放置在LaTeX环境中,在此就不展示了。
代码下载
具体代码比较长,就不在此展示了,有兴趣的朋友可以在公众号【程序猿声】内输入【JSPTS】不带【】,即可下载对应Java代码。
欢迎关注公众号,收看更多干货运筹学算法知识及有趣内容!