• python离散事件仿真库SimPy官方教程(1)


    参考simpy官网

    1.SimPy basics

    1.1 how SimPy work

    将SimPy 拆解开,可以看到它就是一个异步事件调度程序,你先生成某个事件(events)并在指定的仿真时间点调度该事件,事件按照优先级,仿真时间,递增的事件id进行排序。事件有一个回调列表(callback),这些回调会在事件被循环触发和处理时开始执行。事件也可能有返回值。
    SimPy有三个关键的组成部分:

    • environment(环境):存储events在事件列表里,不断追踪当前仿真时间
    • event(事件):仿真过程中的各种异步事件
    • process function(进程函数):用来实现你的仿真模型,也就是定义你的仿真行为,它们是普通的Python生成器函数,可以生成events实例

    如果一个 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (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实例,因为后续的其它实例都是以仿真环境为基础。

    1.2 yield

    SimPy中yield的使用非常关键,其理解过程为 iterables->generators->yield

    • iterables:迭代器

    当你创建一个列表时,你可以一个一个读取元素,这个过程称为迭代。

    >>> mylist = [1,2,3]
    >>> for i in mylist:
    	    print(i)
    1
    2
    3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    mylist是一个迭代器,使用循环创建列表

    >>> mylist = [x*x for x in range(3)]
    >>> for i in mylist:
    	    print(i)
    0
    1
    4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    任何可以使用for…in… 操作的都是一个迭代器,如列表,字符串,文件。
    这些迭代器是简单的,因为你可以按照自己的想法读取所有的值,所有的值都已经存储在记忆里,但是如果你某一时刻不需要这么多值,内存就被浪费了。

    • Generators:生成器

    生成器也是迭代器,是一种你每次只能迭代一次的迭代器。生成器不会存储所有的值在记忆里,它们动态生成值。

    >>> mygenerator = (x*x for x in range(3))
    >>> for i in mygenerator:
    	    print(i)
    
    0
    1
    4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    相同的代码只是用 () 代替 [],但是你不能执行 for i in mygenerator 两次,因为生成器只能使用一次,生成器计算出0然后忘记这个值,再计算出1,最后是4,一个接着一个

    • yield

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    总结:调用包含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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2.Environment

    普通的仿真使用 Environment,实时的仿真使用RealtimeEnvironment

    2.1 Simulation control

    仿真控制中最重要的方法是 Environment.run()

    • 直接调用env.run(),仿真会一直进行直到没有事件要处理了
    • 多数情况下需要设置仿真时间,可以传入until参数,如env.run(until=10),仿真将会在时钟到达10时结束。随着不断增加的until值重复调用此函数可以绘制出进程图
    for i in range(100):
        env.run(until=i)
        progressbar.update(i)
    
    • 1
    • 2
    • 3
    • until不一定是数值,可以传入任何其它event,当event执行完run()后就会返回。如果当前时间为0,env.run(until=env.timeout(5))等价于env.run(until=5)。一个process也是一个event
    >>> import simpy
    >>> def myproc(env):
    	    yield env.timeout(1)
    	    return 'fly'
    >>> env = simpy.Environment()
    >>> proc = env.process(myproc(env))
    >>> env.run(until = proc)
    'fly'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    为了能逐步按event执行仿真,环境会提供peek()step()

    • peek():返回下一个调度event的时间,如果没有,返回infinity
    • step():执行下一个调度的event,如果没有,响应EmptySchedule

    step() 只执行下一个进程,run() 一直执行直到条件终止
    典型用法如下循环

    until = 10
    while env.peek() < until:
        env.step()
    
    • 1
    • 2
    • 3

    2.2 State access

    • Environment.now:通过Environment.now可以获取当前仿真时间,默认可以now在0时刻开始,可以传入initial_time设置开始时间
    • Environment.active_process:获取当前执行的进程,只有在进程函数里能获取到该信息
    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))        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    
    >>>env.active_process
    None
    
    >>>env.step()
    <Process(proc) object at 0x7fb34007ee20>
    <Process(proc) object at 0x7fb34007ee20>
    
    >>>env.active_process
    None
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.3 Event creation

    创建event需要导入simpy.events再实例化类并且传入环境中,为了方便,Environment提供了一些event创建的简写,如 Environment.event() 等同于simpy.events.Event(env)

    • Environment.event()
    • Environment.process()
    • Environment.timeout()
    • Environment.all_of()
    • Environment.any_of()

    3.Events

    simpy包含很多用于各种目的的event,均继承自simpy.events.Event

    events.Event
    |
    +— events.Timeout
    |
    +— events.Initialize
    |
    +— events.Process
    |
    +— events.Condition
    |  |
    |  +— events.AllOf
    |  |
    |  +— events.AnyOf
    .
    .
    .
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3.1 Event basics

    一个event有以下三种状态:

    • 可能发生(triggered = False)
    • 将要发生(triggered = True)
    • 已经发生(processed = True))

    event随着时间的推移,按照如上顺序遍历更新状态。初始时刻,events没有被触发,只是记忆中的对象。

    • Event.triggered:True,event被触发也就是在某个时间点被调用,进入simpy的事件队列里
    • Event.callbacks:回调列表,只要event没有被处理,就可以添加回调。回调就是event以参数的形式存储在Event.callbacks列表中
    • Event.processed:True,simpy把event从事件队列中弹出,开始处理并调用所有回调信息,之后将不再添加回调
    • Event.value:events返回的值,也可以在process中使用value = yield event

    event被触发可能成功或失败,例如一个event在计算快结束且一切正常时被触发将会succeed,但是如果在计算过程中被触发,将会fail。

    • Event.succeed(value=None):触发一件event并将其标记为成功
    • Event.fail(exception):触发event失败,并闯入原因
    • Event.trigger(event):触发event通用方法,返回成功或者失败的值

    3.2 Example usages for Event

    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()            
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
     \o/ \o/ \o/
     \o/ \o/ \o/
    
    • 1
    • 2

    首先创建三个pupil进程和一个bell进程,依次处理这些进程

    • event1:语句5,6,7,输出\0/,等待class_end
    • event2:语句5,6,7,输出\0/,等待class_end
    • event3:语句5,6,7,输出\0/,等待class_end
    • event4:语句1,2,处理timeout(45),无输出
    • event5:语句3,4,class_end触发成功,执行5,6,7,输出 \o/ \o/ \o/

    3.3 Let time pass by: the Timeout

    为了在仿真中能真正让时间流动,需要使用Timeout event

    • Timeout(delay, value=None):Timeout事件在创建时被触发,在now+delay时被调用。

    3.4 Processes are events, too

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果需要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
    
    • 1
    • 2
    • 3
    • 4

    3.5 Waiting for multiple events at once

    如果想要同一时刻等待多件event,simpy提供AnyOf和AllOf,属于Condition 条件事件。

    • AnyOf(env, events):events中至少有个event被触发
    • AllOf(env, events):events中所有event被触发
    from simpy.events import AnyOf, AllOf, Event
    events = [Event(env) for i in range(3)]
    a = AllOf(env, events)
    b = AnyOf(env, events)
    
    • 1
    • 2
    • 3
    • 4

    一个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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    也可以直接取值

    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.Process Interaction

    离散事件的模拟主要通过进程交互实现

    • Sleep until woken up (passivate/reactivate)
    • Waiting for another process to terminate
    • Interrupting another process

    4.1 Sleep until woken up

    假设要模拟带有智能电池充电控制器的电动汽车,车辆行驶时,控制器睡眠,只要车子连接上充电桩开始充电,控制器就会被激活。

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    物理过程:骑行,停车,充电,其中停车期间内完成充电,属于并行
    逻辑过程:bat_ctrl_reactivate事件触发成功,电动车才能开始充电,而bat_ctrl_reactivate只有在停车后才能被触发,如此便能满足骑行,停车,开始充电,停止充电,结束停车,开始骑行的循环过程。

    • event1:语句1,无输出
    • event2:语句7,8,输出passivating at: 0
    • event3:语句2,3,输出start parking at:25
    • event4:语句8,9,输出reactivated at: 25,
    • event5:语句7,8,输出passivating at: 74
    • event6:语句5,6,输出stop parking at:106

    重复上述过程

    4.2 Waiting for another process to terminate

    前文的电动车充电模型有个问题:如果车子停车时间比充电时间要短,模型将不成立。因此需要调整,充电事件在开始停车的时候被触发,同时满足充电时间和停车时间,才会停车

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    start parking at:37
    bat start at: 37
    bat done at: 110
    stop parking at:128
    
    • 1
    • 2
    • 3
    • 4

    使用yield charging & parking控制充电和停车都结束才输出stop parking

    • event1:语句1,无输出
    • event2:语句2,3,4,5,输出start parking at:37
    • event3:语句7,8,输出bat start at: 37
    • event4:语句9,输出bat done at: 110
    • event5:语句5,6,输出stop parking at:128

    4.4 Interrupting another process

    假设你的行程很急,需要打断充电立刻骑行,可以使用进程的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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    start parking at:24
    bat start at: 24
    stop parking at:84
    bat interrupt at: 84 mes need to go
    
    • 1
    • 2
    • 3
    • 4

    parking时间:now+60
    charging时间:now+60-90
    yield charging | parking语句中满足parking

  • 相关阅读:
    JS淘宝搜索框防抖策略
    11-css3新增选择器
    Mybatis-数据源与连接池
    redis之变慢了该如何排查?
    去中心化标志符在DID中的核心地位
    线程交替输出(你能想出几种方法)
    JDBC的事务自动提交机制的演示
    PaddleOCR文字识别C#部署-升级V2
    【信号去噪】基于鲸鱼算法优化VMD实现信号去噪附matlab代码
    数学概念(mathematical concepts)持续更新
  • 原文地址:https://blog.csdn.net/weixin_45526117/article/details/125603777