参考simpy官网
将SimPy 拆解开,可以看到它就是一个异步事件调度程序,你先生成某个事件(events)并在指定的仿真时间点调度该事件,事件按照优先级,仿真时间,递增的事件id进行排序。事件有一个回调列表(callback),这些回调会在事件被循环触发和处理时开始执行。事件也可能有返回值。
SimPy有三个关键的组成部分:
如果一个 process function生成一个event,SimPy将这个process添加到event的回调中并且暂停这个process,等待event被触发和处理。当等待event的进程恢复时,会收到event的返回值。下面是一个简单的例子
>>> import simpy
>>> def example(env):
value = yield env.timeout(env,delay=1,value=42)
print('now=%d,value=%d'%(env.now,value))
>>> env = simpy.Environment()
>>> p = env.process(example(env))
>>> env.run()
now=1,value=42
(1)example() 作为process首先创建一个 Timeout event ,参数包括env,delay,value,Timeout会在now+delay的时候开始执行,因此需要传入env获取now。其它的event通常会在当前仿真时间点被调用。
(2)process因为调用了event而中断,当SimPy处理完 Timeout event ,process就会恢复,并且接受来自event的值42,但是没有返回值也可以
(3)最后这个process输出当前仿真时间点(通过env.now获取)以及Timeout的值
如果已经定义了所有需要的process函数,就可以开始实例化仿真需要的对象,一般先要创建Environmet实例,因为后续的其它实例都是以仿真环境为基础。
SimPy中yield的使用非常关键,其理解过程为 iterables->generators->yield
当你创建一个列表时,你可以一个一个读取元素,这个过程称为迭代。
>>> mylist = [1,2,3]
>>> for i in mylist:
print(i)
1
2
3
mylist是一个迭代器,使用循环创建列表
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
print(i)
0
1
4
任何可以使用for…in… 操作的都是一个迭代器,如列表,字符串,文件。
这些迭代器是简单的,因为你可以按照自己的想法读取所有的值,所有的值都已经存储在记忆里,但是如果你某一时刻不需要这么多值,内存就被浪费了。
生成器也是迭代器,是一种你每次只能迭代一次的迭代器。生成器不会存储所有的值在记忆里,它们动态生成值。
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
print(i)
0
1
4
相同的代码只是用 () 代替 [],但是你不能执行 for i in mygenerator 两次,因为生成器只能使用一次,生成器计算出0然后忘记这个值,再计算出1,最后是4,一个接着一个
yield 是一个用法与return类似的关键词,只是这个函数返回一个生成器
>>> def create_generator():
mylist = range(3)
for i in mylist:
yield i*i
>>> mygenerator = create_generator()
>>> print(mygenerator)
<generator object create_generator at 0x7f7e702c86d0>
>>> for i in mygenerator:
print(i)
0
1
4
总结:调用包含yield的函数会返回一个生成器,函数中的代码并没有运行,调用生成器后,每次遇到 yield 时函数会暂停并保存当前所有的运行信息(保留局部变量),返回 yield 的值, 并在下一次迭代时从当前位置继续运行,直到生成器被全部遍历完。
def gen(n):
for i in range(n):
print('before')
yield i
print('after')
a = gen(4) # 没有运行代码
print(a.__next__()) # 运行到yield停止,返回yield值=1
before
0
print(a.__next__()) # 从上次停止的位置继续运行,直到再遇见yield
after
before
1
for i in a: # 直接用循环遍历生成器
print(i)
after
before
2
after
before
3
after
普通的仿真使用 Environment,实时的仿真使用RealtimeEnvironment
仿真控制中最重要的方法是 Environment.run()
for i in range(100):
env.run(until=i)
progressbar.update(i)
>>> import simpy
>>> def myproc(env):
yield env.timeout(1)
return 'fly'
>>> env = simpy.Environment()
>>> proc = env.process(myproc(env))
>>> env.run(until = proc)
'fly'
为了能逐步按event执行仿真,环境会提供peek()和step()
step() 只执行下一个进程,run() 一直执行直到条件终止
典型用法如下循环
until = 10
while env.peek() < until:
env.step()
import simpy
def subfunc(env):
print(env.active_process)
def proc(env):
while True:
print(env.active_process)
subfunc(env)
yield env.timeout(1)
env = simpy.Environment()
p1 = env.process(proc(env))
>>>env.active_process
None
>>>env.step()
<Process(proc) object at 0x7fb34007ee20>
<Process(proc) object at 0x7fb34007ee20>
>>>env.active_process
None
创建event需要导入simpy.events再实例化类并且传入环境中,为了方便,Environment提供了一些event创建的简写,如 Environment.event() 等同于simpy.events.Event(env)
- Environment.event()
- Environment.process()
- Environment.timeout()
- Environment.all_of()
- Environment.any_of()
simpy包含很多用于各种目的的event,均继承自simpy.events.Event
events.Event
|
+— events.Timeout
|
+— events.Initialize
|
+— events.Process
|
+— events.Condition
| |
| +— events.AllOf
| |
| +— events.AnyOf
.
.
.
一个event有以下三种状态:
event随着时间的推移,按照如上顺序遍历更新状态。初始时刻,events没有被触发,只是记忆中的对象。
event被触发可能成功或失败,例如一个event在计算快结束且一切正常时被触发将会succeed,但是如果在计算过程中被触发,将会fail。
import simpy
class school:
def __init__(self, env):
self.env = env
self.class_end = env.event()
# 创建三个pupil进程和一个bell进程
self.pupil_procs = [env.process(self.pupil()) for i in range(3)]
self.bell_procs = env.process(self.bell())
def bell(self):
for i in range(2): # (1)bell循环
yield self.env.timeout(45) # (2)等待45
self.class_end.succeed() # (3)触发class_end标记成功
self.class_end = self.env.event() # (4)生成新的class_end事件
def pupil(self):
for i in range(2): # (5)pupil循环
print(r'\0/',end=' ') # (6)输出\0/
yield self.class_end #(7)中断处理class_end事件
school = School(env)
env = simpy.Environment()
env.run()
\o/ \o/ \o/
\o/ \o/ \o/
首先创建三个pupil进程和一个bell进程,依次处理这些进程
为了在仿真中能真正让时间流动,需要使用Timeout event
process也是进程,如下代码,pa可以yield sub,处理pa时遇到yield会暂停,去处理sub,sub遇到yield先执行timeout(1),然后返回23给pa的yield,pa继续运行到return 23
def sub(env):
yield env.timeout(1)
return 23
def pa(env):
ret = yield env.process(sub(env))
return ret
import simpy
env = simpy.Environment()
env.run(env.process(pa(env)))
Out[6]: 23
如果需要process延迟开始,可以使用simpy.util.start_delayed()
def pa1(env):
sub_proc = yield simpy.util.start_delayed(env, sub(env), delay=3)
ret = yield sub_proc
return ret
如果想要同一时刻等待多件event,simpy提供AnyOf和AllOf,属于Condition 条件事件。
from simpy.events import AnyOf, AllOf, Event
events = [Event(env) for i in range(3)]
a = AllOf(env, events)
b = AnyOf(env, events)
一个condition event的值是以字典形式按序存储每个event的值,AnyOf和AllOf也可以用&和|代替
def test_condition(env):
t1, t2 = env.timeout(1, value='spam'), env.timeout(2, value='egg')
ret = yield t1 | t2
assert ret == {t1: 'spam'}
t1, t2 = env.timeout(1, value='spam'), env.timeout(2, value='egg')
ret = yield t1 & t2
assert ret == {t1: 'spam',t2:'egg'}
e1, e2, e3 = [env.timeout(1) 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_value(env):
t1,t2 = env.timeout(1,value='spam'), env.timeout(2, value='egg')
r1,r2 = (yield t1 & t2).values()
assert r1=='spam' and r2=='egg'
proc = env.process(fetch_value(env))
env.run()
离散事件的模拟主要通过进程交互实现
假设要模拟带有智能电池充电控制器的电动汽车,车辆行驶时,控制器睡眠,只要车子连接上充电桩开始充电,控制器就会被激活。
import simpy
import random
env = simpy.Environment()
class Charge:
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:
yield env.timeout(random.randint(20, 40)) # (1)骑行20-40min
print('start parking at:{}'.format(env.now)) # (2)停车时间点
self.bat_ctrl_reactivate.succeed() # (3)触发充电事件
self.bat_ctrl_reactivate = env.event() # (4)生成下一次充电事件
yield env.timeout(random.randint(60, 360)) # (5)停车1-6h
print('stop parking at:{}'.format(env.now)) # (6)停车结束事件点
def bat_ctrl(self, env):
while True:
print('passivating at:', env.now) # (7)充电未被激活
yield self.bat_ctrl_reactivate # (8)等待充电事件被触发
print('reactivated at:', env.now) # (9)充电激活
yield env.timeout(random.randint(30, 90)) # (10)充电30-90min
ev = Charge(env)
env.run(until=200)
passivating at: 0
start parking at:25
reactivated at: 25
passivating at: 74
stop parking at:106
start parking at:126
reactivated at: 126
passivating at: 160
物理过程:骑行,停车,充电,其中停车期间内完成充电,属于并行
逻辑过程:bat_ctrl_reactivate事件触发成功,电动车才能开始充电,而bat_ctrl_reactivate只有在停车后才能被触发,如此便能满足骑行,停车,开始充电,停止充电,结束停车,开始骑行的循环过程。
重复上述过程
前文的电动车充电模型有个问题:如果车子停车时间比充电时间要短,模型将不成立。因此需要调整,充电事件在开始停车的时候被触发,同时满足充电时间和停车时间,才会停车
import simpy
import random
env = simpy.Environment()
class Charge:
def __init__(self, env):
self.env = env
self.drive_proc = env.process(self.drive(env))
def drive(self, env):
while True:
yield env.timeout(random.randint(20, 40)) # 1.骑行20-40min
print('start parking at:{}'.format(env.now)) # 2.停车
charging = env.process(self.bat_ctrl(env)) # 3.创建充电进程(事件)
parking = env.timeout(random.randint(60, 360)) # 4.创建停车事件
yield charging & parking # 5.两个事件同时结束
print('stop parking at:{}'.format(env.now)) # 6.停车结束
def bat_ctrl(self, env):
while True:
print('bat start at:', env.now) # 7.开始充电点
yield env.timeout(random.randint(30, 90)) # 充电30-90min
print('bat done at:', env.now) # 8.充电结束
ev = Charge(env)
env.run(until=150)
start parking at:37
bat start at: 37
bat done at: 110
stop parking at:128
使用yield charging & parking控制充电和停车都结束才输出stop parking
假设你的行程很急,需要打断充电立刻骑行,可以使用进程的interrupt(),设置的parking时间结束而充电未结束时,打断充电事件
import simpy
import random
env = simpy.Environment()
class Charge:
def __init__(self, env):
self.env = env
self.drive_proc = env.process(self.drive(env))
def drive(self, env):
while True:
yield env.timeout(random.randint(20, 40)) # 1.骑行20-40min
print('start parking at:{}'.format(env.now)) # 2.停车
charging = env.process(self.bat_ctrl(env)) # 3.创建充电进程(事件)
parking = env.timeout(60) # 4.创建停车事件
yield charging | parking # 5.停车结束就可离开
if not charging.triggered: # 6.charging还没有结束执行干扰
charging.interrupt('need to go')
print('stop parking at:{}'.format(env.now)) # 7.停车结束
def bat_ctrl(self, env):
print('bat start at:', env.now) # 8.开始充电点
try:
yield env.timeout(random.randint(60, 90)) # 9.充电60-90min
print('bat done at:', env.now) # 10.充电结束
except simpy.Interrupt as i:
print('bat interrupt at:', env.now, 'mes', i.cause) # 11.充电被干扰
ev = Charge(env)
env.run(until=100)
start parking at:24
bat start at: 24
stop parking at:84
bat interrupt at: 84 mes need to go
parking时间:now+60
charging时间:now+60-90
yield charging | parking语句中满足parking