4 规划问题的介绍

从本章开始进入规范性分析的核心问题:Flow Shop Scheduling,可以理解为流水车间问题,即怎样合理安排生产顺序以实现利润最大化和成本最小化。

本章的案例是一家生产牙膏的企业,由于产品种类的增加,以前的规划方法逐渐到达了极限。现在我们需要利用python为这家牙膏厂开发合适的规划方法。

该公司的生产概况如下:
一个集合N = {1, 2, . ... , n}的n个订单要在m不同的工位上依次处理,每个工位只有一台机器可用。所有订单的操作顺序都是相同的。所有机器都是连续可用的,一次可以处理一个订单。机器之间的缓冲可能性被认为是无限的。然而,在机器上处理订单之前,必须对机器进行整备或设置。

生产规划的一个重要原则就是以客户为导向,我们总是希望尽可能准时完成订单的生产和交付。然而,一些客户比其他客户更重要,这种重要性体现在,这些客户的订单延期会造成高额的罚款。

我们现在的工作就是找到一个合适的启发式算法,怎么找?一方面我们需要知道订单数量n和机器数量m,另一方面需要每台机器加工每个订单的加工时间和整备时间。

首先我们从导入数据开始,所有需要的信息都存储在一个json文件中。json文件可以类比python中的字典,实际上是一个“字典的字典”,

1 读入json数据

文件名: "InputFlowshopSIST.json"
请找出哪个模块可以用来读入json文件。你怎样才能读入这个json文件,以便进一步使用它?


InputFlowshopSIST.json

答案是json模块(也叫库),安装完之后导入即可:

import json
with open('InputFlowshopSIST.json') as json_file:
    data = json.load(json_file)  # As a dictionary

print(data)

json库读取数据时,首先要用with open('路径') as fileName:来将json文件在python中打开,fileName就相当于这个json文件在编译器里的名字,然后调用json.load(fileName)将json文件导入并赋值给另一个变量data,这个变量的类型是字典。

json.load()的结果是字典类型

上图中一共有4个键值对,四个键分别是:文件名Name,机器数量nMachines=5,订单数量nJobs=11,和订单Jobs。注意,键Jobs对应的值是一个列表,列表的元素是字典,每个字典容纳了每个订单的必要信息,例如-

  • SetupTimes表示这个订单在所有5台机器上相应的整备时间(整备时间以列表形式给出,列表中的元素是有顺序的),
  • ProcessingTimes表示这个订单在每台机器上的加工时间(加工时间以列表形式给出,列表中的元素是有顺序的
  • DueDate表示这个订单的交货期限
  • TardCosts表示延期交货的罚款数额

2 @property修饰器的用法简介

@property是一种修饰器,用于修饰方法或函数,我们可以用@property来创建只读属性,在函数定义的上一行加入这个装饰器,就会把这个函数转换成相同名称的只读属性。这样可以防止原本定义的属性被修改。

被装饰的函数在调用时,不能用普通函数的调用方式来调用,而是要用属性的调用方式,也就是说调用时不能加括号
下面展开来说说:

# 定义学生类
class Student:
    def __init__(self, name, score):
        self.Name = name
        self.__Score = score

# 修改学生的分数属性:
s = Student('Bob', 59)
print(s)
s.Score = 60
print(s.Score)
修改学生的分数属性

但是这样修改有隐患,我们可以给学生的分数属性赋任何值。为了消除隐患我们通常会用get和set方法,即在类的定义中增加setScore和getScore两个方法。这里的get方法仅仅读取类的属性。代码如下:

class Student:
    def __init__(self, name, score):
        self.Name = name
        self.__Score = score

    def getScore(self):
        return self.__Score

    def setScore(self, newScore):
        # 在set方法中,如果newScore不满足条件,则会报错
        if newScore < 0 or newScore > 100:
            raise ValueError('Invalid score')
        self.__Score = newScore

s2 = Student('Alice', 99)
print(s2.getScore())

s2.setScore(100)
print(s2.getScore())

s2.setScore(120)

set和get方法修改和读取属性信息

*注意:通常情况下这样做是可行的,而且更容易理解。无论进行读还是写操作,都可以通过相应的函数调用来实现

为了图省事,我们会引入修饰器,这样我们在修改属性信息的时候就不用通过函数来操作了。原理是我们把set和get函数装饰成属性来调用。试想一下,借助装饰器,我们实际上结合了前面两种方式的优点,既能享受给属性赋值的方便又能保证分数不会超出范围。两全其美!

class Student:
    def __init__(self, name, score):
        self.Name = name
        self.__Score = score

    @property
    def score(self):
        # 这个score函数相当于get方法,没有参数
        return self.__Score

    @score.setter
    def score(self, newScore):
        # 这个score是set方法,用@score.setter来修饰,@score.setter是前面@property修饰后的副产品
        if newScore < 0 or newScore > 100:
            raise ValueError('Invalid score')
        self.__Score = newScore


s3 = Student('Cindy', 88)
print(s3.score)

s3.score = 90
print(s3.score)
修饰器的用法

3 用代码描述真实世界

第一节只是基础,我们掌握了如何用json包读入数据。在实际操作中我们很少会直接这样写,更普遍的用法是将第一节的逻辑封装在类(或者函数,类本质上是特殊的函数)中,后期直接通过接口调用这个类。这样可以避免代码重复,比如说,在这个json文件中有11个订单,可以想象成今年的第一季度工厂收到了11个订单,那么第二季度的生产中,工厂会收到其他的订单。如果现在我们将代码封装在类中,第二季度的订单到来的时候直接调用类即可。

思考一下,我们应该定义哪几个类来描述json文件中的各种信息?注意一定要全面!

通过观察不难发现,我们首先需要一个“机器”类class DataMachine,我们一共有5台机器,每台都有一个唯一的编号;另一方面,我们还需要创建一个“订单”类class DataJob,用于描述订单的信息,这个类可能会稍微复杂些。

记住要定义__init__()__str__()函数。此外,来自json文件的输入数据应该反映在类中。

class DataMachine:
    def __init__(self, machineId):
        self.MachineId = machineId

    # 返回机器的序号
    def __str__(self):
        return f'Machine {self.MachineId}'

class DataJob:
    def __init__(self, idJob, setupTimes, processingTimes, dueDate, tardinessCost):
        self.__JobId = idJob
        self.__SetupTimes = setupTimes
        self.__ProcessingTimes = processingTimes
        self.__DueDate = dueDate
        self.__TardCost = tardinessCost

    # 返回订单job的序号和这个订单需要经过几步操作
    def __str__(self):
        return f'Job {self.__JobId} with {len(self.__ProcessingTimes)} operations.\n'

    # __str__函数只是返回一个泛泛的信息,如:订单1有5个操作
    # 现在写几个函数,输出Job对象的特定信息,例如id, 某台机器的整备时间和处理时间
    @property
    def JobId(self):
        # 这个函数的目的是可以用job对象.JobId()来直接获取订单号。因为类中定义的订单号是私有的,在别的类中没有访问权限
        return self.__JobId

    @JobId.setter
    def JobId(self, newId):
        self.__JobId = newId  # 通过这个函数我们可以修改订单的编号。跟上一个函数同名,但是参数表不同,编译器会根据参数表来区分同名函数
    
    @property
    def TardCost(self):
        return self.__TardCost

    @property
    def DueDate(self):
        return self.__DueDate

    @property
    def Operations(self):
        # 这个函数会输出一个元组的列表,元组第一个元素是加工订单的机器的编号,第二个元素是加工时长
        # 注意这里的Operations和str函数里的不同,str中仅仅输出订单被处理几次,这里输出每次加工用到机器编号和时长
        return [(optId, processingTime) for optId, processingTime in enumerate(self.__ProcessingTimes)]

    # 下面这两个函数分别输出某个订单对象在某台机器上的整备时间和加工时间。注意看json截图,这两个时间保存在列表中
    @property
    def SetupTimes(self, position):
        return self.__SetupTimes[position]

    @property
    def ProcessdingTimes(self, position):
        return self.__ProcessingTimes[position]

# 构造一个DataJob对象
newJob = DataJob(1, [22,33,44,55,66], [1,2,3,4,5], 290, 50)
print(newJob)

newJob.JobId = 3
print(newJob.JobId)

# 用调用属性的方式调用函数
print(newJob.DueDate)
print(newJob.Operations)
修饰器在声明的时候别忘了写@

4 定义一个类,用于导入数据

创建一个InputData类,该类处理.json文件并创建前一节中的相应类对象。
创建这个类的时候需要考虑两件事:

  1. 这个类的对象如何构造?
    导入数据通常需要一个路径,也就是文件存储的位置,每次只需要粘贴路径就能导入相应的文件
  2. 这个类要实现哪些具体的操作?
    由于我们处理的都是json文件,所以需要用到json.load()。接下来就是在InputData类中构造两个类的对象,我选择的方案是分别在两个空列表中,借助for循环,每次循环都要执行一次构造函数,然后把构造出来的实例放到列表中。注意这个函数是不需要返回值的。
import json
class InputData:
    def __init__(self, path):
        self.__path = path

        # 构造函数中调用这个类的成员函数,即:实例化的同时执行成员函数!!
        self.DataLoad()

    def DataLoad(self):
        with open(self.__path, 'r') as inputFile:
            inputData = json.load(inputFile)

            self.m = inputData['nMachines']
            self.n = inputData['nJobs']

            self.InputJobs = []
            for job in inputData['Jobs']:
                # 这一步并不难,需要熟悉json文件的嵌套
                self.InputJobs.append(DataJob(job['Id'], job['SetupTimes'], 
                job['ProcessingTimes'], job['DueDate'], job['TardCosts']))

            # 注意:InputData的对象的属性是个列表,调用的时候需要写索引
            self.InputMachines = []
            for k in range(self.m):
                self.InputMachines.append(DataMachine(k))

# 构造一个InputData类的对象
data = InputData("InputFlowshopSIST.json")

# 调用data的InputJobs属性,这个属性是个列表,列表元素是对象。
# 由对象组成 的列表不能直接输出,而是要彻底实例化,即说清楚具体的那个元素
print(data.InputJobs[1])

print(data.InputJobs)

print(data.InputMachines[1])

在循环中实例化,并append进列表

*注意:在我们的InputData类中,囊括了json文件中的全部数据,既有订单job的信息,又有机器machine的信息。这显然不符合我们的要求,因为在做生产排期时,我们需要把输入信息处理一下,只关心订单在每台机器上的整备和加工时间以及期限和罚款。因此我们需要一个输出数据的类,叫做OutputJob,下节会讲!

5 把订单信息job筛选出来

创建一个OutputJob类,用于描述预定的订单信息。
首先来明确继承关系,OutputJob应该是DataJob的子类;然后需要明确,OutputJob的数据来源是InputData,所以我们在定义OutputJob类时需要导入InputData

# from InputData import *

class OutputJob(DataJob):
    def __init__(self, dataJob):
        super().__init__(dataJob.JobId, [dataJob.SetupTimes(i) for i in range(len(dataJob.Operations))], 
        [dataJob.ProcessingTimes(i) for i in range(len(dataJob.Operations))], dataJob.DueDate, dataJob.TardCost)

        self.StartSetups = [0]*len(self.Operations)
        self.EndSetups = [0]*len(self.Operations)
        self.StartTimes = [0]*len(self.Operations)
        self.EndTimes = [0]*len(self.Operations)
        self.Tardiness = 0
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容