SimPY是一个Python下的第三方库,可以方便的进行离散事件的仿真。仿真速度比较快。下面记录一下我的一点心得,不保证完全正确,供参考。
$ pip install -U simpy
##学习资源
-可爱的PYTHON:SimPy 简化了复杂模型-基于Python 2.x
-Pypi库
-主页readthedoc.io
-介绍PPT
-PPT配套Youtube视频
SimPY使用Environment,Process,Event,Resource四大概念来进行离散事件的仿真。
Environment就是整体仿真所在的时间,主要用于提取时间。
Process就是仿真过程中的实体,如:顾客, 设备, 车辆等。 Process本质上也是一个event。源代码里面可以看到是继承Event的一个类。
Event是仿真中触发的事件,可以理解为一个定时器。当定时器到时时,触发事件。
Resource是仿真中的资源,如ATM机,服务器等。
##官方实例
>>> import simpy
>>>
>>> def clock(env, name, tick):
... while True:
... print(name, env.now)
... yield env.timeout(tick)
...
>>> env = simpy.Environment()
>>> env.process(clock(env, 'fast', 0.5))
<Process(clock) object at 0x...>
>>> env.process(clock(env, 'slow', 1))
<Process(clock) object at 0x...>
>>> env.run(until=2)
fast 0
slow 0
fast 0.5
slow 1
fast 1.0
fast 1.5
逻辑很简单,
不错吧,很简单就有了一个开始。
##前导知识
简单开始之后需要回顾一下后面会使用到的其他知识。
因为离散事件仿真中会用到大量的随机函数,所以这里回顾一下主要的随机函数。
import random
random.uniform(3,4) # 3到4之间的均匀分布
random.gauss(5,1) # 以5为均值,1为方差的高斯分布。
random.normalvariate(5,1) # 以5为均值,1为方差的高斯分布。
random.randint(4,8) # 从4,5,6,7,8中随机挑出一个整数值。
random.choice([1,4,6,8,0]) # 从1,4,6,8,0中随机挑出一个值。
random.random() # 0-1之间均匀分布的一个实数。
##深入原理
通过SimPY的源代码可以了解到,SimPY使用了一个heapq队列,这个队列中的元素是事件。Environment中对这个 队列进行调度,实际上是将事件压入队列中,environment中还有step方法,就是从队列中取出时间最小的一个事件(也就是时间点上最接近当前时间的下一个事件,使用heapq的heappop方法),然后运行这个事件的callback函数,一般就是Process。 因此仿真实际上是对一系列事件进行压入队列,按时间序弹出队列的过程。这样可以避免使用时间步长进行步进,时间步长步进的缺点就是太慢了。必须一个时间步长一个时间步长的挨个遍历过去,如果时间步长不合理的话,会有大量的计算时间上的浪费。
另外,为了语法上的优美易用,env中使用了Python的反射机制,将常用的几种事件,包括Process, Timeout, Anyof, Allof, Event都绑定为env的一种方法。 这个语法看上去很简单,但实现机制相对有点难以理解(我也只是了解是一种反射),只需要记住类似env.process, env.timeout, env.event, env.all_of, env.any_of的方法调用实际上都是声明了simpy.Process, simpy.Timeout等类的就可以了。详细实现在simpy.core.py中。
##稍复杂的实例
官方教程中的几个例子都比较经典。下面一一介绍
"""
银行柜台排队问题
知识点:
- 资源
- 条件事件
场景:
A counter with a random service time and customers who renege. Based on the
program bank08.py from TheBank tutorial of SimPy 2. (KGM)
一个银行柜台,其服务耗时随机分布,客户会因费时长而离开。
"""
import random
import simpy
RANDOM_SEED = 42
NEW_CUSTOMERS = 5 # 总的客户人数
INTERVAL_CUSTOMERS = 10.0 # 新的客户每隔大约10秒钟出现一位
MIN_PATIENCE = 1 # 最低耐心值
MAX_PATIENCE = 3 # 最高耐心值
def source(env, number, interval, counter):
"""本函数随机生成客户"""
for i in range(number):
c = customer(env, 'Customer%02d' % i, counter, time_in_bank=12.0)
env.process(c)
t = random.expovariate(1.0 / interval)
yield env.timeout(t)
def customer(env, name, counter, time_in_bank):
"""客户流程函数,客户到来,接受服务或者不耐烦,最后离开."""
arrive = env.now
print('%7.4f %s: Here I am' % (arrive, name))
with counter.request() as req:
patience = random.uniform(MIN_PATIENCE, MAX_PATIENCE)
# Wait for the counter or abort at the end of our tether
results = yield req | env.timeout(patience)
wait = env.now - arrive
if req in results:
# 得到服务
print('%7.4f %s: Waited %6.3f' % (env.now, name, wait))
tib = random.expovariate(1.0 / time_in_bank)
yield env.timeout(tib)
print('%7.4f %s: Finished' % (env.now, name))
else:
# 不耐烦离开
print('%7.4f %s: RENEGED after %6.3f' % (env.now, name, wait))
# 初始化然后开始模拟
print('Bank renege')
random.seed(RANDOM_SEED)
env = simpy.Environment()
# 开始流程函数并运行
counter = simpy.Resource(env, capacity=1)
env.process(source(env, NEW_CUSTOMERS, INTERVAL_CUSTOMERS, counter))
env.run()
"""
洗车问题示例.
知识点:
- 等待其它流程
- 资源
场景介绍:
一个有特定洗车机的洗车房,并且洗车流程会消耗随机长度的时间。
Car流程为,车到达洗车房,如果有空闲的洗车机,就立刻开始洗车,如果没有,就等待直到
有洗车机空闲下来。
"""
import random
import simpy
RANDOM_SEED = 42
NUM_MACHINES = 2 # 洗车房中洗车机的数量
WASHTIME = 5 # 使用洗车机洗车所需的时间,分钟
T_INTER = 7 # 来车的间隔时间,约7分钟
SIM_TIME = 20 # 总的模拟时间
class Carwash(object):
"""
一个洗车房,拥有特定数量的洗车机。 一个车首先申请洗车机。当来车申请到洗车机后
就可以开始洗车流程,并且在等待指定的洗车时间后完成洗车。
"""
def __init__(self, env, num_machines, washtime):
self.env = env
self.machine = simpy.Resource(env, num_machines)
self.washtime = washtime
def wash(self, car):
"""洗车流程。它使用一个car流程并试着清洗它。"""
yield self.env.timeout(WASHTIME)
print("Carwash removed %d%% of %s's dirt." %
(random.randint(50, 99), car))
def car(env, name, cw):
"""车 流程 (每辆车都有一个名字) 每辆车会到达洗车房(``cw``) 并申请洗车机.
然后开始洗车流程,等待洗车的完成然后离开再不回来...
"""
print('%s arrives at the carwash at %.2f.' % (name, env.now))
with cw.machine.request() as request:
yield request
print('%s enters the carwash at %.2f.' % (name, env.now))
yield env.process(cw.wash(name))
print('%s leaves the carwash at %.2f.' % (name, env.now))
def setup(env, num_machines, washtime, t_inter):
"""创建一个洗车房,几个初始车辆,然后持续创建车辆到达. 每隔``t_inter`` 分钟."""
# 创建洗车房
carwash = Carwash(env, num_machines, washtime)
# 创建4个初始车辆
for i in range(4):
env.process(car(env, 'Car %d' % i, carwash))
# 在仿真过程中持续创建车辆
while True:
yield env.timeout(random.randint(t_inter - 2, t_inter + 2))
i += 1
env.process(car(env, 'Car %d' % i, carwash))
# 初始化并开始仿真
print('Carwash')
print('Check out http://youtu.be/fXXmeP9TvBg while simulating ... ;-)')
random.seed(RANDOM_SEED) # This helps reproducing the results
# 创建一个环境并开始仿真
env = simpy.Environment()
env.process(setup(env, NUM_MACHINES, WASHTIME, T_INTER))
# 开始执行!
env.run(until=SIM_TIME)
后续更新新的实例。