数据集
第一目标船员最少,即极限情况下至少需要多少船员才能保证稳定运行,延展可以考虑鲁棒性的问题和最短移动时间
pro = """
CrewScheduling
There are currently 10 data files.
These data files are the ten test problems from J.E.Beasley
and B.Cao "A tree search algorithm for the crew scheduling
problem" European Journal of Operational Research 94 (1996)
pp517-526.
The test problems solved in that paper are available in
the files csp50, csp100, csp150, csp200, csp250, csp300,
csp350, csp400, csp450 and csp500 (where the number for
each csp file is the number of tasks).
The format of these data files is:
number of tasks (N), time limit
for each task i (i=1,...,N): start time, finish time
for each transition arc between two tasks (i and j):
i, j, cost of transition from i to j
The value of the optimal solution for each of these data files
for a varying number of crews is given in the above paper.
The largest file is csp500 of size 250Kb (approximately).
The entire set of files is of size 900Kb (approximately).
"""
import pyscipopt
import re
import time
import math
#记录每个函数执行的时间
loop = 1
def timeLog(f):
def wrapper(*args, **kw):
global loop
now = time.perf_counter()
res = f(*args, **kw)
print("%s-%s:"%(loop, f.__name__), time.perf_counter()-now)
loop += 1
return res
return wrapper
class Task:
def __init__(self, id, timeStart, timeEnd):
self.id = id
self.timeStart = timeStart
self.timeEnd = timeEnd
def __repr__(self):
return str([self.id, self.timeStart, self.timeEnd])
class Case:
def __init__(self, name, n, tMax, tasks, T):
self.name = name
self.n = n
self.tMax = tMax
self.tasks = tasks
self.T = T
def __repr__(self):
return str([self.name, self.n, self.tMax, self.tasks])
class SubTask:
def __init__(self, id, taskId, timeStart, timeEnd):
self.id = id
self.taskId = taskId
self.timeStart = timeStart
self.timeEnd = timeEnd
def __repr__(self):
return str([self.id, self.taskId, self.timeStart, self.timeEnd])
def dataRead():
data_paths = [r"/Users/zhangchaoyu/PycharmProjects/pythonProject/OR_Library/CrewScheduling/data/csp50.txt",
r"/Users/zhangchaoyu/PycharmProjects/pythonProject/OR_Library/CrewScheduling/data/csp100.txt",
r"/Users/zhangchaoyu/PycharmProjects/pythonProject/OR_Library/CrewScheduling/data/csp150.txt",
r"/Users/zhangchaoyu/PycharmProjects/pythonProject/OR_Library/CrewScheduling/data/csp200.txt",
r"/Users/zhangchaoyu/PycharmProjects/pythonProject/OR_Library/CrewScheduling/data/csp250.txt",
r"/Users/zhangchaoyu/PycharmProjects/pythonProject/OR_Library/CrewScheduling/data/csp300.txt",
r"/Users/zhangchaoyu/PycharmProjects/pythonProject/OR_Library/CrewScheduling/data/csp350.txt",
r"/Users/zhangchaoyu/PycharmProjects/pythonProject/OR_Library/CrewScheduling/data/csp400.txt",
r"/Users/zhangchaoyu/PycharmProjects/pythonProject/OR_Library/CrewScheduling/data/csp450.txt",
r"/Users/zhangchaoyu/PycharmProjects/pythonProject/OR_Library/CrewScheduling/data/csp500.txt"]
cases = []
for path in data_paths:
f = open(path)
row = f.readline().replace('\n', '').strip()
row = [int(w) for w in re.split(" ", row)]
n, tMax = row[0], row[1]
tasks = {}
for i in range(n):
row = f.readline().replace('\n', '').strip()
row = [int(w) for w in re.split(" ", row)]
timeStart, timeEnd = row[0], row[1]
tasks[i+1] = Task(i+1, timeStart, timeEnd)
T = {}
row = f.readline().replace('\n', '').strip()
while row != "":
row = [int(w) for w in re.split(" ", row)]
T[row[0], row[1]] = row[2]
row = f.readline().replace('\n', '').strip()
cases.append(Case(re.split("/", path)[-1], n, tMax, tasks, T))
return cases
#将任务按照时间限制打散
def subTaskGenerate(case, gap=60):
subTasks = []
loop = 1
for task in case.tasks.values():
for t in range(task.timeStart, task.timeEnd, gap):
subTasks.append(SubTask(loop, task.id, t, min(t+gap, task.timeEnd)))
loop += 1
return subTasks
#将时间线能够衔接上的组合在一起
def subTaskCombined(case, subTasks):
T = case.T
lines = []
for i in range(len(subTasks)):
si = subTasks[i]
#单个任务
lines.append([si])
for j in range(i+1, len(subTasks)):
sj = subTasks[j]
#如果两个任务之间可以走过去,走过去的时间够,且时间约束也满足
if ((si.taskId, sj.taskId) in T and si.timeEnd + T[si.taskId, sj.taskId] <= sj.timeStart or si.taskId == sj.taskId) \
and si.timeStart + case.tMax >= sj.timeEnd:
lines.append([si, sj])
for k in range(j+1, len(subTasks)):
sk = subTasks[k]
if ((sj.taskId, sk.taskId) in T and sj.timeEnd + T[sj.taskId, sk.taskId] <= sk.timeStart or sj.taskId == sk.taskId) \
and si.timeStart + case.tMax >= sk.timeEnd:
lines.append([si, sj, sk])
for l in range(k + 1, len(subTasks)):
sl = subTasks[l]
if ((sk.taskId, sl.taskId) in T and sk.timeEnd + T[sk.taskId, sl.taskId] <= sl.timeStart or sk.taskId == sl.taskId) \
and si.timeStart + case.tMax >= sl.timeEnd:
lines.append([si, sj, sk, sl])
return lines
##保证每个subtask都被分配了
#假设有m个任务组,n个任务,aij表示第i个任务组里是否含有第j个任务,那么即min sum(xi) s.t. sum(aij*xi) = 1
def setPartition(lines, subTasks, time_limit, printLog=False):
S = {s.id for s in subTasks}
A = {i: {s.id for s in lines[i]} for i in range(len(lines))}
model = pyscipopt.Model("setPartition")
X = {i:model.addVar(vtype="B", name="X[%s]" % i) for i in range(len(lines))}
model.setObjective(pyscipopt.quicksum(X[i] for i in range(len(lines))), "minimize")
#每个任务都被分配
for id in S:
model.addCons(pyscipopt.quicksum(X[i] for i in range(len(lines)) if id in A[i]) == 1)
# 设置求解时间
model.setRealParam("limits/time", time_limit)
if not printLog:
model.hideOutput()
model.optimize()
print("\ngap:", model.getGap())
# 拿结果
X1 = {i: round(model.getVal(X[i])) for i in range(len(lines))}
return X1
#先将可能的组合列出来,然后作集合划分,尽量保证集合划分是精确解,后面想办法提升前面列举的全面性(由于组合太多应该不可能列举完)
def f(case):
gap = 120
#将任务切成小任务
subTasks = subTaskGenerate(case, gap)
#将能衔接上的小任务组合起来
lines = subTaskCombined(case, subTasks)
#保证每个subtask都被分配了
linesRes = setPartition(lines, subTasks, time_limit=100, printLog=True)
linesRes = [lines[k] for k,v in linesRes.items() if v == 1]
return len(linesRes)
if __name__ == '__main__':
cases = dataRead()
# 第一目标是最少船员,即极限情况下至少需要多少个船员才能保证事情的稳定运行,延展的化可以考虑鲁棒性的问题和最短移动时间
case = cases[3]
times = [task.timeEnd-task.timeStart for task in case.tasks.values()]
times.sort()
# 先将可能的组合列出来,然后作集合划分,尽量保证集合划分是精确解,后面想办法提升前面列举的全面性(由于组合太多应该不可能列举完)
n = f(case)
第一目标是最少船员,即极限情况下至少需要多少个船员才能保证事情的稳定运行,延展的化可以考虑鲁棒性的问题和最短移动时间