FJSP中设备的可加工工序总时间的方差对Gap收敛效率的影响

文章目录

  • 问题描述
  • 基于 Brandimarte 的结果对比
    • Python调用OR-Tools建模求解
    • 求解结果对比


相关文章:
《【柔性作业车间调度FJSP】通过python调用OR-Tools的区间变量建模求解》
《【(较大规模)作业车间调度JSP】通过OR-Tools的区间变量建模求解的效率对比实验》

问题描述

柔性作业车间调度(Flexible Job Shop Scheduling Problem,FJSP)相比于 JSP问题,增加了工序的可选设备不唯一的条件,这增加了问题的柔性,也增大了问题的求解难度。求解FJSP问题时,不仅需要确定工件工序分配的机器,还需要确定机器上工序的加工顺序,以最优化既定的目标。

在关于JSP的大规模排程问题上的实验发现,设备的可加工工序总时间的方差对求解的Gap的收敛效率有影响,当方差很大时,设备的负载可能很不平衡,此时存在较为明显的瓶颈设备,则JSP问题在求解时Gap值收敛效率较高。因此,本文进一步探究这个规律在FJSP问题当中是否存在

基于 Brandimarte 的结果对比

基于 Brandimarte 数据集进行测试,其中包含了 Nk01.fjs ~ Mk10.fjs 算例文件,打印输出工序数(表征问题规模)、工序平均可上机台数(柔性表征问题求解复杂度)、设备可加工工序的总时间及其方差,以及输出各个算例的求解情况(设置求解终止条件为 10s/5%Gap)。

Python调用OR-Tools建模求解

同样的,以下代码基于FJSP标准测试数据解析的结果进行建模。

model = cp_model.CpModel()

# 命名元组:存储变量信息(相当于实例化一道工序)
task_type = collections.namedtuple("task_type", "start end interval")
# 存储变量信息
all_tasks = {}
x_ijk = {}
y_ijlmk = {}
real_tasks_s = {}
real_tasks_e = {}

# 将工序信息按可上机器(一台)进行存储
machine_to_intervals = collections.defaultdict(list)

for job_id in range(Job_num):
    for task_id in range(job_2_step_num[job_id]):

        real_tasks_s[job_id, task_id] = model.NewIntVar(0,1000,"start_by_task")
        real_tasks_e[job_id, task_id] = model.NewIntVar(0,1000,"end_by_task")

        for machine_id in job_step_2_avail_machines[job_id, task_id]:

            suffix = f"_{job_id}_{task_id}_{machine_id}"
            # 工序选择了哪一台设备
            x_ijk[job_id, task_id, machine_id] = model.NewBoolVar(name="x"+suffix)

            duration_init = job_step_machine_2_ptime[job_id, task_id, machine_id]
            duration = model.NewIntVar(0, 1000, "duration" + suffix)
            model.Add(duration <= duration_init + 1000*(1-x_ijk[job_id, task_id, machine_id]))
            model.Add(duration >= duration_init + 1000*(x_ijk[job_id, task_id, machine_id]-1))
            model.Add(duration <= 1000*x_ijk[job_id, task_id, machine_id])

            start_var = model.NewIntVar(0, 1000, "start" + suffix)
            end_var = model.NewIntVar(0, 1000, "end" + suffix)
            model.Add(start_var + end_var <= 1000*x_ijk[job_id, task_id, machine_id])
            
            interval_var = model.NewIntervalVar(
                start=start_var, size=duration, end=end_var, name="interval" + suffix)          
            all_tasks[job_id, task_id, machine_id] = task_type(
                start=start_var, end=end_var, interval=interval_var)
            machine_to_intervals[machine_id].append(interval_var)

            for job_id2 in range(Job_num):
                for task_id2 in range(job_2_step_num[job_id]):
                    if not( (job_id==job_id2) and (task_id==task_id2) ):
                        if machine_id in job_step_2_avail_machines[job_id2, task_id2]:
                            y_ijlmk[job_id, task_id, job_id2, task_id2, machine_id]=\
                                model.NewBoolVar(name="y")

Cmax = model.NewIntVar(0, 1000, "Cmax")

## 工序有且只能选择一个机台
for job_id in range(Job_num):
    for task_id in range(job_2_step_num[job_id]):
        model.Add(sum(x_ijk[job_id, task_id, k] for k in job_step_2_avail_machines[job_id, task_id]) == 1)

## 对齐设备坑位上的时间与工序的时间
for job_id in range(Job_num):
    for task_id in range(job_2_step_num[job_id]):
        model.AddMaxEquality(real_tasks_s[job_id, task_id], 
                                [all_tasks[job_id, task_id, machine_].start for machine_ in job_step_2_avail_machines[job_id, task_id]])
        model.AddMaxEquality(real_tasks_e[job_id, task_id], 
                                [all_tasks[job_id, task_id, machine_].end for machine_ in job_step_2_avail_machines[job_id, task_id]])

## 设备上区间变量的不重叠约束
for machine in machine_to_intervals:
	model.AddNoOverlap(machine_to_intervals[machine])

## 工件的前后工序约束
for job_id in range(Job_num):
    for task_id in range(job_2_step_num[job_id]-1):
        model.Add(real_tasks_s[job_id, task_id + 1] >= real_tasks_e[job_id, task_id])

model.AddMaxEquality(Cmax, [real_tasks_e[index] for index in real_tasks_e])
model.Minimize(Cmax)

solver = cp_model.CpSolver()
status = solver.Solve(model, VarArraySolutionPrinter(10, 0.05))

print(f"Optimal Schedule Length: {solver.ObjectiveValue()}")
print("\nStatistics")
print(f"  - conflicts: {solver.NumConflicts()}")
print(f"  - branches : {solver.NumBranches()}")
print(f"  - wall time: {solver.WallTime()}s")

求解结果对比

在FJSP问题提出相同的猜想1即问题规模相近情况下,若设备的可加工工序总时间的方差越大,则该问题求解的Gap值收敛效率更高。根据以下测试数据的结果进行分析。

下面数据当中的 “opt” 的数值来源于一些期刊文献的较优结果,并不一定是算例的最优解。

MK01.fjs 的结果数据:

总的工序数 55
设备可加工工序总时长的方差 568.8
当前解:40  # opt: 40
求解时间: 0.3088191s
Gap:  0.0

MK02.fjs 的结果数据:

总的工序数 58
设备可加工工序总时长的方差 213.36666666666667
当前解: 27.0  # opt: 26
求解时间: 11.130754476s
Gap:  0.038461538461538464

易知,MK01 和 MK02 规模相近,前者的设备可加工工序总时间方差较大,因此Gap的收敛效率远高于后者。符合我们的猜想。


MK03.fjs 的结果数据:

总的工序数 150
设备可加工工序总时长的方差 8341.839285714286
当前解: 214.0  # opt: 204
求解时间: 6.5982534390000005s
Gap:  0.049019607843137254

MK06.fjs 的结果数据:

总的工序数 150
设备可加工工序总时长的方差 4061.904761904762
当前解: 93.0  # opt: 69
求解时间: 11.204710798s
Gap:  0.9375

易知,MK03 和 MK06 的结果数据也符合我们的猜想。


MK04.fjs 的结果数据:

总的工序数 90
设备可加工工序总时长的方差 3893.4761904761904
当前解: 63.0  # opt: 60
求解时间: 0.7978846620000001s
Gap:  0.05

MK05.fjs 的结果数据:

总的工序数 106
设备可加工工序总时长的方差 3220.3333333333335
当前解: 180.0  # opt: 172
求解时间: 6.609042677000001s
Gap:  0.046511627906976744

MK07.fjs 的结果数据:

总的工序数 100
设备可加工工序总时长的方差 8147
当前解: 151.0  # opt: 146
求解时间: 11.379868928s
Gap:  0.09420289855072464

易知,MK04 和 MK05 的结果数据符合我们的猜想,但是 MK07 的数据规模比 MK05 的小,且方差大更多,求解效率却不占优。因此,我们围绕我们的猜想进一步讨论下方差以外的信息。

打印出MK07的设备的可加工工序总时间:

{1: 231, 2: 189, 3: 334, 4: 249, 5: 87}

而MK04的相应数据为:

{1: 188, 2: 24, 3: 6, 4: 58, 5: 30, 6: 57, 7: 14}

明显的,可以发现MK04的可加工工序总时间中,有唯一的显著的瓶颈设备(1),而这在MK07的数据中,可加工工序总时间领先的比例不大。因此我们进一步猜想2若“瓶颈设备”的可加工总时长领先的比例越大,即瓶颈越明显,求解效率才会有较大的提升,反之,即使有较大的方差也对求解效率的提升有限

因此,我们对前面的算例以及后续的算例,都增加分析“瓶颈”设备的可加工工序的总时长的领先第2名的设备的比例,该比例值越大,则说明瓶颈设备越明显。

max__ = 0
min_ratio_ = 1
for m in new1:
    if new1[m] > max__:
        max__ = new1[m]
for m in new1:
    if new1[m] == max__:
        continue
    if (max__ - new1[m])/max__ < min_ratio_:
        min_ratio_ = (max__ - new1[m])/max__

print(min_ratio_)
# MK01:0.222
# MK02:0.184
# MK03:0.063
# MK04:0.691
# MK05:0.119
# MK06:0.391
# MK07:0.254

根据MK01和MK02的瓶颈领先比例可知,MK01的瓶颈更显著,求解结果符合猜想2的预期。MK03和MK06的结果,两者并不呈现谁支配谁的关系(若方差更大,且瓶颈领先比例更大视为支配),因此并不能支持猜想2。MK04和MK05的结果能证明猜想2,但MK05和MK07的数据结果却与猜想2相悖,经分析发下在MK05的工序平均会在1.5台设备间进行选择,而MK07的工序平均会在3台设备间进行选择,因此两者在求解规模上并不相同,后者显然更复杂(柔性更大)。

因此MK07的结果并不完全否定猜想2,只是在比较问题规模,并不能简单地通过问题中的工序总数进行判断,而应结合考虑问题的柔性。所以,接下来比较的MK09和MK10两个算例的工序总数相等,且工序的平均可选设备数都为3。


MK09.fjs 的结果数据:

总的工序数 240
设备可加工工序总时长的方差 23662.48888888889
当前解: 397.0  # opt: 307
求解时间: 11.159340499s
Gap:  0.3277591973244147
瓶颈领先比例:0.31095406360424027

MK10.fjs 的结果数据:

总的工序数 240
设备可加工工序总时长的方差 20955.61111111111
当前解: 341.0  # opt: 220
求解时间: 11.237421519000002s
Gap:  0.8839779005524862
瓶颈领先比例:0.018907563025210083

上述MK09和MK10的结果数据支持我们的猜想2。

综上所述:我们可以通过以下的办法粗略地判断FJSP问题的求解效率,先通过问题当中的工序总数,以及工序的平均可上设备数(柔性)判断不同问题之间的规模大小,若在规模相近的前提下,可以查看数据当中是否存在显著的瓶颈设备(即有大量工序竞争的设备资源视为瓶颈),若存在,则该问题在最优性验证处的效率会极高。

你可能感兴趣的:(#,生产调度优化,运筹优化决策,OR-Tools,Python,作业车间调度,运筹优化)