Hello,CSDN的各位小伙伴们,又见面啦!今天我们要学习的例程是:Machine Shop!我们开始吧!
例程背景
有一个工厂,工厂里面有 n 台一样的机器,每台机器都需要不停地加工一个又一个的零件。但是机器会周期性地发生故障,一旦机器发生故障,工厂里面唯一的一个维修工 repairman 就需要立即放下手里原本那个没那么紧急的任务,转而去维修机器,当修好机器后,维修工又会继续做他原本被打断的那个任务。我们的目的就是用 SimPy 对这个流程进行建模。
例程代码分析
类似地,一些头文件和参数的定义就不再赘述,直接贴代码
import random
import simpy
RANDOM_SEED = 2024
PT_MEAN = 10.0 # 一个零件制作的平均时间
PT_SIGMA = 2.0 # 一个零件制作时间的方差
MTTF = 300.0 # machine损坏的平均时间
BREAK_MEAN = 1 / MTTF # machine的损坏时间服从指数分布
REPAIR_TIME = 30.0 # 维修时间
JOB_DURATION = 30.0 # repairman另一个没那么紧急的工作的持续时间
NUM_MACHINES = 10 # 机器的数量
WEEKS = 4
SIM_TIME = WEEKS * 7 * 24 * 60 # 总仿真时间
下面才是重点部分,我们首先来定义一个零件的加工需要多长时间,这个比较简单,如下:
# 定义machine制作一个零件的时间
def time_per_part():
t = random.normalvariate(PT_MEAN, PT_SIGMA)
while t <= 0:
t = random.normalvariate(PT_MEAN, PT_SIGMA)
return t
接下来,我们需要定义machine什么时候会出故障:
# 定义machine什么时候损坏
def time_to_failure():
return random.expovariate(BREAK_MEAN)
然后,我们将machine作为一个类,machine这个类需要实现什么功能呢?首先第一个就是正常的生产零件(在没有故障的时候),第二个则是发生故障后请求repairman资源对自己进行修复。
# 下面定义machine的行为
class Machine:
def __init__(self, env, name, repairman):
self.env = env
self.name = name # machine的编号
self.parts_made = 0 # 统计该机器一共制造了多少个零件
self.broken = False # 指示该机器是否发生故障
self.process = env.process(self.working(repairman))
env.process(self.break_machine())
def working(self, repairman):
while True: # 表示机器一直会去生产零件
to_produce = time_per_part()
while to_produce:
start = self.env.now
try:
# 如果机器没有出故障就正常花时间加工零件
yield self.env.timeout(to_produce)
to_produce = 0
except simpy.Interrupt:
# 被打算则说明机器出故障了,需要repairman来立即进行修复
self.broken = True
# 先计算上一个零件生产了多久
already_produce = self.env.now - start
# 下面计算上一个零件还要多久才做完
to_produce -= already_produce
with repairman.request(priority=1) as req:
# "machine故障需要维修"这个process很紧急,因此设为高优先级
yield req # 等待repairman资源
print('Repairman come to repair machine: ', self.name, ' at: ', self.env.now)
yield self.env.timeout(REPAIR_TIME)
print('Repair Done at: ', self.env.now)
self.parts_made += 1 # 表示一个零件制作完成
def break_machine(self):
while True:
yield self.env.timeout(time_to_failure())
if not self.broken: # 只有在machine正常工作时才会打断
print('Machine: ', self.name, ' broken at time: ', self.env.now)
self.process.interrupt()
上面的代码中,当我们定义了working函数,并利用env.process(working(…))将其加入到仿真环境后,他就变成了一个process,可以调用process的interrupt对其进行打断。值得注意的是:如果working被打断了,它会保存打断前自己的执行状态,当打断working的process执行完毕后(也就是except内的程序执行完毕后),working还是接着打断前的状态继续执行。
我们接着看 working函数里面 except simpy.Interrupt:
里面的部分:在正常加工零件被打断后,我们需要记录这个零件还要加工多久,方便我们后面machine被修复后,继续加工。因为machine坏了之后自然需要请求repairman进行修复,所以有:with repairman.request(priority=1) as req:
,其中,priority表示这个请求的优先级,数字越小表示优先级越高。yield req
则表示machine会一直等,直到repairman对其进行修复。(不像我们在example 1中的情况,用户还有一个patience值)
我们知道了machine的活动,接下来还需要定义repairman的另一个没那么紧急的任务(也是一个process,也需要请求repairman资源,只不过优先级不如对machine的修复)
def other_jobs(env, repairman):
"""
定义repairman的另外一个没那么紧急的工作
:param env: 仿真环境
:param repairman: 表示一个preemptive资源
:return:
"""
while True:
to_do = JOB_DURATION
while to_do:
with repairman.request(priority=2) as req:
yield req
start = env.now
try:
# 如果没有被"维修“这一更紧急的任务打断,则正常做这个任务
yield env.timeout(to_do)
to_do = 0
except simpy.Interrupt:
# 先计算这个任务已经做了多久
already_do = env.now - start
# 计算这个任务还要多久
to_do -= already_do
这里可能大家会有疑问:我们刚刚说某个process,通过调用另一个process的interrupt方法,实现对另一个process的打断。可是在other_jobs这个函数中,似乎没有其他敌方调用对other_jobs的打断,为什么还会出现 except simpy.Interrupt:
呢?原因在于,本例中使用了两种方法对process进行打断,第一种是显式的调用 interrupt方法,另一种是通过 preemptiveresource,优先级高的process会抢占这个资源,从而实现对低优先级process的打断。所以,只要repair machine的那个process以更高的优先级获得了repairman的资源,那么这个other_jobs的process就会自动被打断。
那么最后,我们创建仿真环境、实例化Machine类、定义资源等,就能够开始执行仿真:
print('Example 2: Machine Shop...')
random.seed(RANDOM_SEED)
env = simpy.Environment()
repairman = simpy.PreemptiveResource(env, capacity=1)
machines = [Machine(env, f'Machine {
i}', repairman) for i in range(NUM_MACHINES)] # 实例化machine类
env.process(other_jobs(env, repairman))
env.run(until=SIM_TIME)
for machine in machines:
print('Machine: ', machine.name, ' has made: ', machine.parts_made)
输出结果如下:
Example 2: Machine Shop...
Machine: Machine 8 broken at time: 18.07951144463326
Repairman come to repair machine: Machine 8 at: 18.07951144463326
Machine: Machine 9 broken at time: 39.77961184319781
Repair Done at: 48.079511444633255
Repairman come to repair machine: Machine 9 at: 48.079511444633255
Machine: Machine 7 broken at time: 48.18142965423535
Repair Done at: 78.07951144463325
Repairman come to repair machine: Machine 7 at: 78.07951144463325
Repair Done at: 108.07951144463325
Machine: Machine 0 broken at time: 108.61453162604185
Repairman come to repair machine: Machine 0 at: 108.61453162604185
Repair Done at: 138.61453162604187
Machine: Machine 6 broken at time: 189.63437415331165
Repairman come to repair machine: Machine 6 at: 189.63437415331165
Repair Done at: 219.63437415331165
Machine: Machine 4 broken at time: 365.2793434875798
Repairman come to repair machine: Machine 4 at: 365.2793434875798
Machine: Machine 1 broken at time: 378.28402258242255
Machine: Machine 3 broken at time: 390.3204213097364
Repair Done at: 395.2793434875798
Repairman come to repair machine: Machine 1 at: 395.2793434875798
Repair Done at: 425.2793434875798
Repairman come to repair machine: Machine 3 at: 425.2793434875798
Repair Done at: 455.2793434875798
Machine: Machine 2 broken at time: 502.3235610496023
Repairman come to repair machine: Machine 2 at: 502.3235610496023
Repair Done at: 532.3235610496023
Machine: Machine 5 broken at time: 2398.7360300209943
Repairman come to repair machine: Machine 5 at: 2398.7360300209943
Repair Done at: 2428.7360300209943
Machine: Machine 0 has made: 4026
Machine: Machine 1 has made: 4019
Machine: Machine 2 has made: 4029
Machine: Machine 3 has made: 4037
Machine: Machine 4 has made: 4042
Machine: Machine 5 has made: 4035
Machine: Machine 6 has made: 4020
Machine: Machine 7 has made: 4012
Machine: Machine 8 has made: 4028
Machine: Machine 9 has made: 4046
其中,我们注意到:machine 9发生故障后,由于machine 8还没修好,所以它就得等到machine 8修好,才能获得repairman的资源。