使用SimPY进行离散事件仿真

使用SimPY进行离散事件仿真

SimPY是一个Python下的第三方库,可以方便的进行离散事件的仿真。仿真速度比较快。下面记录一下我的一点心得,不保证完全正确,供参考。

安装

$ pip install -U simpy

##学习资源
-可爱的PYTHON:SimPy 简化了复杂模型-基于Python 2.x
-Pypi库
-主页readthedoc.io
-介绍PPT
-PPT配套Youtube视频

主要概念

  • Environment
  • Process
  • Event
  • Resource

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

逻辑很简单,

  1. 创建一个env
  2. 以env为参数创建process, process有名字和参数,process内部使用生成器直接调用了超时事件。
  3. 运行该env

不错吧,很简单就有了一个开始。

##前导知识
简单开始之后需要回顾一下后面会使用到的其他知识。
因为离散事件仿真中会用到大量的随机函数,所以这里回顾一下主要的随机函数。

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中。

##稍复杂的实例
官方教程中的几个例子都比较经典。下面一一介绍

  1. 银行柜台排队问题
"""
银行柜台排队问题

知识点:

- 资源
- 条件事件

场景:
  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()
  1. 洗车问题
"""
洗车问题示例.

知识点:

- 等待其它流程
- 资源

场景介绍:
  一个有特定洗车机的洗车房,并且洗车流程会消耗随机长度的时间。
  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)

后续更新新的实例。


你可能感兴趣的:(仿真)