之前用java写的串行调度代码有问题,但是翻以前的代码的那啥感觉大家都懂,哪怕是自己写的再看一遍也是折磨。正好最近有门课需要写点代码,也要用到串行调度,因此借此机会了了我的这一心病。找了个小算例试了下,和同门手画的结果一样,这次应该没啥问题。
因为我串行调度的代码用的是我读文件的返回参数,所以我把读文件的代码也一并贴上。
首先是读代码,这次用的是PSPLIB里面的.RCP文件,相较于.sm文件更简洁,没有多余的花哨的东西。
借鉴了一下张静文老师书里的思路,我把每个活动看作一个对象:
class Activity(object):
'''
活动类:包含 1.活动ID 2.活动持续时间 3.活动资源需求量 4.活动紧前活动 5.活动最早开始时间 6.活动最晚开始时间 7.活动是否被访问
'''
def __init__(self, id, duration, resourceRequest, successor):
self.id = id
self.duration = duration
self.resourceRequest = np.array(resourceRequest)
self.predecessor = None
self.successor = successor
self.es = -1
self.ef = -1
self.ls = -1
self.lf = -1
self.visited = False
各种属性的含义应该都看得懂,我对应写在注释里面了,最后的visited我先写着可能后面算法要用,如果你用不上的话也可以删掉。
下面是读取.RCP文件:
RCP文件可以去PSPLIB下载
def readData(fileName):
'''
读取标准化文件中的所有活动信息,包括 1.活动数 2.项目资源数 3.项目资源种类数 4.项目资源限量
5.所有活动的ID,持续时间,资源需求,紧前活动
:param fileName:
:return: 标准化文件数据
'''
f = open(fileName)
taskAndResourceType = f.readline().split(' ') # 第一行数据包含活动数和资源数
taskSum = int(taskAndResourceType[0]) # 得到活动数
resourceType = int(taskAndResourceType[1]) # 得到资源数
resourceAvail = np.array([int(value) for value in f.readline().split(' ')[:-1]]) # 获取资源限量
# 将每个活动的所有信息存入到对应的Activity对象中去
allTasks = {}
preActDict = defaultdict(lambda: [])
for i in range(taskSum):
nextLine = [int(value) for value in f.readline().split(' ')[:-1]]
task = Activity(i + 1, nextLine[0], nextLine[1:5], nextLine[6:])
allTasks[task.id] = task
for act in nextLine[6:]:
preActDict[act].append(i + 1)
f.close()
# 给每个活动加上紧前活动信息
for actKey in allTasks.keys():
allTasks[actKey].predecessor = preActDict[allTasks[actKey].id].copy()
return taskSum, resourceType, resourceAvail, allTasks # 活动数int, 资源数int, 资源限量np.array, 所有活动集合dic{活动代号:活动对象}
稍微留意下返回值的类型,用了个defaultdict来存紧前活动,活动我都是放在字典里的,key是活动的代号,value就是活动对应的Activity类。
下面是串行调度:
def serialGenerationScheme(allTasks, resourceAvail, priority: list):
'''
串行调度生成机制,传入所有活动,资源限量,优先序列
:param allTasks:
:param resourceAvail:
:param priority:
:return:
'''
priorityToUse = priority.copy()
ps = [1] # 局部调度计划初始化
priorityToUse.remove(1)
en = allTasks[1].successor.copy() # 合格活动初始化
allTasks[1].es = 0 # 活动1的最早开始时间设为0
allTasks[1].ef = allTasks[1].es + allTasks[1].duration
for stage in range(1, len(priority)):
selectTaskID = -1
# 选出合格活动集合En中优先级最高的活动作为这一阶段要安排的活动
for taskID in priorityToUse:
if taskID in en:
selectTaskID = taskID
break
# 检查要安排的活动所有紧前活动的结束时间,最大的作为当前活动的理论最早开始时间
earliestStartTime = 0
for preTaskID in allTasks[selectTaskID].predecessor:
if allTasks[preTaskID].ef > earliestStartTime:
earliestStartTime = allTasks[preTaskID].ef
startTime = earliestStartTime
# 检查满足资源限量约束的时间点作为活动最早开始时间,即在这一时刻同时满足活动逻辑约束和资源限量约束
t = startTime + 1
# 计算t时刻正在进行的活动的资源占用总量,当当前时刻大于活动开始时间小于等于活动结束时间时,说明活动在当前时刻占用资源
while t <= startTime + allTasks[selectTaskID].duration :
resourceSum = np.zeros(len(resourceAvail))
for taskIDA in ps:
if allTasks[taskIDA].es + 1 <= t <= allTasks[taskIDA].es + allTasks[taskIDA].duration:
resourceSum = resourceSum + allTasks[taskIDA].resourceRequest
# 加上当前阶段需要安排的活动的资源占用,对比总资源限量是否超过
resourceSum = resourceSum + allTasks[selectTaskID].resourceRequest
# 若超出资源限量,则向后推一个单位时间
if (resourceSum > resourceAvail).any():
startTime += 1
t = startTime + 1
else:
t += 1
# 若符合资源限量则将当前活动开始时间安排在这一时刻
allTasks[selectTaskID].es = startTime
allTasks[selectTaskID].ef = startTime + allTasks[selectTaskID].duration
priorityToUse.remove(selectTaskID) # 更新优先序列
# 更新合格活动集合en,和局部调度计划ps
en.remove(selectTaskID)
ps.append(selectTaskID)
for taskToAdd in allTasks[selectTaskID].successor:
if set(allTasks[taskToAdd].predecessor) < set(ps):
en.append(taskToAdd)
这个是按照时间从0时刻开始的情况,也就是说t时刻左边才是占用资源的,也就是活动开始时间es+1到es+duration之间的时刻这个活动才会占用相应的资源。挺复杂挺绕的,不明白的画可以画个图想一想~~
串行调度运行完只是改变了每个活动类里面的es和ef属性,可以自己写几行代码输出下看看,再不行就找个小算例手算和程序结果对比下。
就这样~~
代码中我删去了一些不重要的东西,可能多删了,跑不了的可以私信我~~,太专业的就别了,哥们也不懂~~
6.14 使用中发现一些问题:
1.合格活动集合en初始化时应该使用深拷贝,否则后续更新合格活动集合时会修改活动的紧后活动列表
2.资源限量检验的时候时间区间为t <= 活动开始时间+活动持续时间,t时刻正在进行活动资源使用情况从活动开始时间+1