填坑:Python实现RCPSP问题串行调度代码

        之前用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

你可能感兴趣的:(嚯茶,python,开发语言)