SimPy是一个基于Python的异步事件调度器,产生一系列事件并按照仿真时间进行计划安排升序排列,在事件的循环序列中触发并执行,产生回调返回响应值,在物流,工厂制造业和餐饮服务业等工业仿真中都有所应用,比如优化快递分拣中的排班表,优化工厂制造的排班和成本,估算服务排队的时长等,还可以用于网络通信仿真.详细介绍可查阅官方文档和原理介绍,另有知乎简介.
pip install simpy
Environment管理仿真时间的进行,让各种组件和事件按照一定的计划表进行驱动,管理仿真元素之间的关联, 主要 API 有
simpy.Environment.process #添加仿真进程
simpy.Environment.event #创建事件
simpy.Environment.timeout #提供延时(timeout)事件
simpy.Environment.until #仿真结束的条件(时间或事件)
simpy.Environment.run #仿真启动
simpy.Environment.all_of
simpy.Environment.any_of
创建一个空的仿真环境并实例化,然后执行一段时间,这时由于没有创建任何组件和事件,所以实际上什么也没有发生,也可以使用处理进程或事件作为结束条件
env=simpy.Environment()#创建环境并实例化
env.run(until=100)#运行仿真环境,100s后停止
simpy.events.Event是仿真事件,simpy是以事件作为基础组件进行构建的,各种其它类型如events.Process,events.Timeout都是以事件派生出来的,本质上还是Event.
events.Event
|
+— events.Timeout
|
+— events.Initialize
|
+— events.Process
|
+— events.Condition
| |
| +— events.AllOf
| |
| +— events.AnyOf
创建一个事件并定义响应函数,这个事件一旦调用之后就结束了,不会再响应了,在process event中也是如此.
import simpy
def my_callback(event):
print('Called back from', event)
env = simpy.Environment()
event = env.event()
event.callbacks.append(my_callback)#给事件添加回调
event.callbacks
事件若被触发,按给定的调度事件放入队列,Event.triggered变为True,事件响应函数理完后Event.processed设为True,能获取返回值
Event.value或value=yield event
也可以将事件记为成功或失败
Event.succed(value=None)#记为成功
Event.fail(exception)#记为失败
Event.trigger(event)#记为触发
process进程也是事件
env.process()#处理时间的进程也是一个事件
所以process能产生执行另一个process,其执行结束后这个process恢复,另一个process能传给这个process返回值,创建process后,触发执行时会有Initialize事件,但不必手动处理这个.
simpy.util.start_delayed()#使process进程延迟触发
>>> from simpy.util import start_delayed
>>>
>>> def sub(env):
... yield env.timeout(1)
... return 23
...
>>> def parent(env):
... start = env.now
... sub_proc = yield start_delayed(env, sub(env), delay=3)
... assert env.now - start == 3
...
... ret = yield sub_proc
... return ret
...
>>> env.run(env.process(parent(env)))
23
Condition条件事件,有时需要等待其它的事件执行出想要的结果,然后以此为条件进行事件的触发或执行,有events.AllOf和events.AnyOf事件.
>>> from simpy.events import AnyOf, AllOf, Event
>>> events = [Event(env) for i in range(3)]
>>> a = AnyOf(env, events) # Triggers if at least one of "events" is triggered.
>>> b = AllOf(env, events) # Triggers if all each of "events" is triggered.
也可以不用AllOf和AnyOf,转而使用逻辑运算符&,|定义条件事件
>>> def test_condition(env):
... t1, t2 = env.timeout(1, value='spam'), env.timeout(2, value='eggs')
... ret = yield t1 | t2
... assert ret == {t1: 'spam'}
...
... t1, t2 = env.timeout(1, value='spam'), env.timeout(2, value='eggs')
... ret = yield t1 & t2
... assert ret == {t1: 'spam', t2: 'eggs'}
...
... # You can also concatenate & and |
... e1, e2, e3 = [env.timeout(i) for i in range(3)]
... yield (e1 | e2) & e3
... assert all(e.processed for e in [e1, e2, e3])
...
>>> proc = env.process(test_condition(env))
>>> env.run()
>>> def fetch_values_of_multiple_events(env):
... t1, t2 = env.timeout(1, value='spam'), env.timeout(2, value='eggs')
... r1, r2 = (yield t1 & t2).values()
... assert r1 == 'spam' and r2 == 'eggs'
...
>>> proc = env.process(fetch_values_of_multiple_events(env))
>>> env.run()
模型为,一辆汽车在行驶一段时间后停车一段时间,停车期间进行充电,所以需要在执行停车事件时激活充电时间.
>>> from random import seed, randint
>>> seed(23)
>>>
>>> import simpy
>>>
>>> class EV:
... def __init__(self, env):
... self.env = env
... self.drive_proc = env.process(self.drive(env))
... self.bat_ctrl_proc = env.process(self.bat_ctrl(env))
... self.bat_ctrl_reactivate = env.event()
...
... def drive(self, env):
... while True:
... # Drive for 20-40 min
... yield env.timeout(randint(20, 40))
...
... # Park for 1–6 hours
... print('Start parking at', env.now)
... self.bat_ctrl_reactivate.succeed() # "reactivate"
... self.bat_ctrl_reactivate = env.event()
... yield env.timeout(randint(60, 360))
... print('Stop parking at', env.now)
...
... def bat_ctrl(self, env):
... while True:
... print('Bat. ctrl. passivating at', env.now)
... yield self.bat_ctrl_reactivate # "passivate"
... print('Bat. ctrl. reactivated at', env.now)
...
... # Intelligent charging behavior here …
... yield env.timeout(randint(30, 90))
...
>>> env = simpy.Environment()
>>> ev = EV(env)
>>> env.run(until=150)
Bat. ctrl. passivating at 0
Start parking at 29
Bat. ctrl. reactivated at 29
Bat. ctrl. passivating at 60
Stop parking at 131
上一个充电休眠的例子中会有一个问题,停车事件可能比充电时间短,一方面在停车充电,一方面停车结束正在驾驶,这时矛盾的.为此可以这样解决,开始停车时开始充电,然后只有到停车和充电都结束的时候,才开始驾驶,这里用到了条件事件&.
>>> class EV:
... def __init__(self, env):
... self.env = env
... self.drive_proc = env.process(self.drive(env))
...
... def drive(self, env):
... while True:
... # Drive for 20-40 min
... yield env.timeout(randint(20, 40))
...
... # Park for 1–6 hours
... print('Start parking at', env.now)
... charging = env.process(self.bat_ctrl(env))
... parking = env.timeout(randint(60, 360))
... yield charging & parking
... print('Stop parking at', env.now)
...
... def bat_ctrl(self, env):
... print('Bat. ctrl. started at', env.now)
... # Intelligent charging behavior here …
... yield env.timeout(randint(30, 90))
... print('Bat. ctrl. done at', env.now)
...
>>> env = simpy.Environment()
>>> ev = EV(env)
>>> env.run(until=310)
Start parking at 29
Bat. ctrl. started at 29
Bat. ctrl. done at 83
Stop parking at 305
现在又有一个问题,汽车只能等待电池完全充满的时候才能开始驾驶,如果将条件事件改为停车或充电结束的时候就开始驾驶,那么有时就要中断充电进程,使用process.interrupt()中断一个进程.
class EV:
def __init__(self, env):
self.env = env
self.drive_proc = env.process(self.drive(env))
def drive(self, env):
while True:
# Drive for 20-40 min
yield env.timeout(randint(20, 40))
# Park for 1 hour
print('Start parking at', env.now)
charging = env.process(self.bat_ctrl(env))
parking = env.timeout(60)
yield charging | parking
if not charging.triggered:
# Interrupt charging if not already done.
charging.interrupt('Need to go!')
print('Stop parking at', env.now)
def bat_ctrl(self, env):
print('Bat. ctrl. started at', env.now)
try:
yield env.timeout(randint(60, 90))
print('Bat. ctrl. done at', env.now)
except simpy.Interrupt as i:
# Onoes! Got interrupted before the charging was done.
print('Bat. ctrl. interrupted at', env.now, 'msg:',
i.cause)
>>> env = simpy.Environment()
>>> ev = EV(env)
>>> env.run(until=100)
Start parking at 31
Bat. ctrl. started at 31
Stop parking at 91
Bat. ctrl. interrupted at 91 msg: Need to go!