【SimPy系列博客之官方example学习与解读】—— Example 1: Bank Renege

Hello,CSDN的各位小伙伴们,拖更了几年后,又重新开始记录这段时间准备学习的东西啦!最近博主在学习 SimPy 的相关知识,发现国内关于 SimPy 的介绍还是相对较少,且缺乏系统性,因此博主打算用1~2周的时间,边学边记,打算从 SimPy 官方给出的几个例子出发分析 SimPy 的使用场景和用法,希望能和大家互勉!那废话不多说,我们直接开始吧!今天要学习的例程是 Bank Renege

文章目录

例程背景

这个example最初的流程是这样的:有一个银行,银行里面只有一个柜台 (Counter),众多顾客以一个随机时间到达银行,柜台一次只能服务一个用户,并会消耗一定的服务时间。抵达银行的顾客如果发现柜台有人,则需要排队等候。而每一个顾客的等待时间是有限制的,如果超过一定的时间后还没有轮到自己,那么该顾客就会离开。我们的目的就是用 SimPy 对这个流程进行建模。

例程代码分析

首先自然是导入一些必要的模块,这里不再赘述

python 复制代码
import simpy
import random

接下来我们定义一些仿真相关的参数:

python 复制代码
RANDOM_SEED = 2024  # 随机种子
NUM_OF_CUSTOMERS = 5  # 仿真过程中顾客的总数量
INTERVAL_CUSTOMERS = 10.0  # 每个顾客的到达时间大致相差10s
MIN_PATIENCE = 1  # 顾客的最小允许等待时间
MAX_PATIENCE = 3  # 顾客的最大允许等待时间

下面是整个仿真的关键部分:我们首先来定义单个用户的行为。即在用户到达银行后,他的一些行为。用户到达银行后,先会看counter资源是否空闲 ,如果空闲,则直接开始使用柜台一段时间用完柜台后释放柜台资源 。如果柜台正在被占用,那么用户就会等待 ,如果在允许的时间内等到了空闲的counter,那么就开始使用;否则用户将离开

我们来看看这部分代码怎么写的:

python 复制代码
def customer(env, name, counter, time_in_bank):
    """
    定义用户的行为 (排队等待,使用柜台,离开等)
    :param env: 仿真环境
    :param name: 用户的标识号
    :param counter: 柜台资源
    :param time_in_bank: 用户使用柜台的时间
    :return:
    """

    arrival_time = env.now  # 用户抵达银行的时间
    print('Customer: ', name, ' arrive the bank at: ', arrival_time)

    with counter.request() as req:
        patience = random.uniform(MIN_PATIENCE, MAX_PATIENCE)  # 定义该用户的允许等待时间

        results = yield req | env.timeout(patience)

        waiting_time = env.now - arrival_time  # 该用户的等待时间

        if req in results:
            # 说明用户等到了counter
            print('Customer: ', name, ' is ready to use the counter at: ', env.now)

            tib = random.expovariate(1.0 / time_in_bank)  # 用户使用柜台的时间
            yield env.timeout(tib)
            print('Customer: ', name, ' finished at: ', env.now)
        else:
            # 说明等待时间超过了用户的最大允许等待时间
            print('Customer: ', name, ' has waited for: ', waiting_time, ' cannot wait any longer!')

其中,env是由simpy创建的仿真环境的名字,env.now可以获取到当前仿真的时间。另外,我们看到这句话:with counter.request() as req:因为 counter是仿真中的一个资源 (Resource) ,resource在simpy中的定义就是在某个时刻只能被一定数量的processes使用,我们在这里定义的customer函数就是一个process。当process使用resource时,它就变成了该resource的owner,当process使用完resource时,就必须将resource释放,以便后面其他process可以继续使用该resource。

而代码中的with...as...的语句,则可以实现process对resource的请求和释放。在with的缩进里面,我们可以写一些代码,用来表示执行一些需要使用资源的操作,一旦跳出了with的缩进,那么resource就会被自动释放。

那么对于后面的 yield req语句,它的意思就是:当程序执行到这句话时,customer这个函数就会暂停,并等待,直到resource被分配到这个用户。一旦resource被分配到了这个用户,那么代码将继续从yield req后面的语句开始继续执行。

接下来,我们再看:yield req | env.timeout(patience)符号 "|"在simPy中的意思是在多个事件中进行选择,会选择最先发生的那个事件

至此,我们对单个用户的行为建模已经有所了解,下面我们再来看看如何建模用户到达bank的过程,这就包括:用户什么时候到达bank, 再根据我们上面的代码决定用户到达bank后要干啥。

python 复制代码
def customers_arrival(env, num_of_customers, interval_customers, counter):
    """
    定义用户抵达银行的这个随机过程
    :param env: simpy的仿真环境
    :param num_of_customers: 用户的总数量
    :param interval_customers: 用户抵达银行的大致时间间隔
    :param counter: 银行柜台 (是simpy中的resource)
    :return:
    """

    for i in range(num_of_customers):
        c = customer(env, str(i), counter, time_in_bank=12.0)
        env.process(c)
        t = random.expovariate(1.0 / interval_customers)
        yield env.timeout(t)

可以看到,对于每一个用户,我们首先定义了它个体到达银行后的行为,c = customer(env, str(i), counter, time_in_bank=12.0),将其加入到仿真环境中,然后再模拟下一个用户到达的时间。

在所有相关process都定义好后,就正式进入仿真,即我们需要定义counter资源,然后把上面定义的一些process加入到仿真环境中:

python 复制代码
random.seed(RANDOM_SEED)
env = simpy.Environment()  # 创建仿真环境

counter = simpy.Resource(env, capacity=1)  # counter是整个仿真环境的资源,且只有一个柜台
env.process(customers_arrival(env, NUM_OF_CUSTOMERS, INTERVAL_CUSTOMERS, counter))
env.run()

仿真输出的结果为:

Customer:  0  arrive the bank at:  0
Customer:  0  is ready to use the counter at:  0
Customer:  0  finished at:  4.344581265041675
Customer:  1  arrive the bank at:  6.350494539377634
Customer:  1  is ready to use the counter at:  6.350494539377634
Customer:  1  finished at:  21.48185544267454
Customer:  2  arrive the bank at:  28.180599526311216
Customer:  2  is ready to use the counter at:  28.180599526311216
Customer:  3  arrive the bank at:  31.262456454262153
Customer:  3  has waited for:  1.8317442018885792  cannot wait any longer!
Customer:  4  arrive the bank at:  38.16001347598838
Customer:  4  has waited for:  2.9264992955047973  cannot wait any longer!
Customer:  2  finished at:  48.273541968295305

结果分析

我们可以看到,当第一个用户抵达bank时,counter空闲,因此用户0可以直接用,在4.344min用完counter并释放资源。值得注意的是:用户2使用counter的时间比较长,大概花了20min,而在此期间,用户3在第31.26min抵达bank,不巧用户3的最大允许等待时间比较短,因此,在等待了1.83min后,用户3离开。

扩展

我们希望通过对example的学习能够理解simpy其中的一些逻辑,因此,除了看懂example,我们还需要能够灵活改变源码以满足自己的想法。

如果我们想更好的模块化编程,那么用户就是一个单独的类,我们先考虑只有一个用户的情况

python 复制代码
class Customer():
    def __init__(self, env, name, counter, time_in_bank):
        self.env = env
        self.name = name
        self.counter = counter
        self.time_in_bank = time_in_bank
        self.action = env.process(self.waiting())

    def waiting(self):
        arrival_time = self.env.now  # 用户抵达银行的时间
        print('Customer: ', self.name, ' arrive the bank at: ', arrival_time)

        with counter.request() as req:
            patience = random.uniform(MIN_PATIENCE, MAX_PATIENCE)  # 定义该用户的允许等待时间

            results = yield req | env.timeout(patience)

            waiting_time = env.now - arrival_time  # 该用户的等待时间

            if req in results:
                # 说明用户等到了counter
                print('Customer: ', self.name, ' is ready to use the counter at: ', env.now)

                tib = random.expovariate(1.0 / self.time_in_bank)  # 用户使用柜台的时间
                yield env.timeout(tib)
                print('Customer: ', self.name, ' finished at: ', env.now)
            else:
                # 说明等待时间超过了用户的最大允许等待时间
                print('Customer: ', self.name, ' has waited for: ', waiting_time, ' cannot wait any longer!')

其中,用户类里面的waiting方法就是我们原本的process,但是我们加了一句话:self.action = env.process(self.waiting()),相当于是把waiting方法加入到仿真中了。这样,我们在定义好这个类时,process就会在对应的时间自动被触发:

python 复制代码
env = simpy.Environment()
counter = simpy.Resource(env, capacity=1)  # counter是整个仿真环境的资源,且只有一个柜台
C0 = Customer(env, name='C0', counter=counter, time_in_bank=12.0)
env.run()

输出如下:

Customer:  C0  arrive the bank at:  0
Customer:  C0  is ready to use the counter at:  0
Customer:  C0  finished at:  1.7192205283488458

那么,继续,如果我们想要用很多用户,代码可以这样写:

python 复制代码
def customers_arrival(env, num_of_customers, interval_customers, counter):
    """
    定义用户抵达银行的这个随机过程
    :param env: simpy的仿真环境
    :param num_of_customers: 用户的总数量
    :param interval_customers: 用户抵达银行的大致时间间隔
    :param counter: 银行柜台 (是simpy中的resource)
    :return:
    """

    for i in range(num_of_customers):
        Customer(env, name=str(i), counter=counter, time_in_bank=12.0)
        t = random.expovariate(1.0 / interval_customers)
        yield env.timeout(t)

因为我们在Customer类里面已经把waiting方法加入到env中了,所以此时我们只需要把Customer实例化,就能够自动执行process。因此,我们通过下面的代码启动仿真:

python 复制代码
env = simpy.Environment()
counter = simpy.Resource(env, capacity=1)  # counter是整个仿真环境的资源,且只有一个柜台
env.process(customers_arrival(env, NUM_OF_CUSTOMERS, INTERVAL_CUSTOMERS, counter))
env.run()

结果如下:

Customer:  0  arrive the bank at:  0
Customer:  0  is ready to use the counter at:  0
Customer:  0  finished at:  2.185043045116575
Customer:  1  arrive the bank at:  27.834642847492574
Customer:  1  is ready to use the counter at:  27.834642847492574
Customer:  1  finished at:  30.715420426177218
Customer:  2  arrive the bank at:  47.63187135568359
Customer:  2  is ready to use the counter at:  47.63187135568359
Customer:  3  arrive the bank at:  52.263879692999126
Customer:  3  has waited for:  2.0577798944440318  cannot wait any longer!
Customer:  4  arrive the bank at:  69.6644022591966
Customer:  4  has waited for:  2.1264195985524026  cannot wait any longer!
Customer:  2  finished at:  86.37400774992372

那么。进一步地,如果我们希望很多用户同时到达bank,要怎么做呢?有两种方法:

(1)不执行customers_arrival函数,直接实例化多个Customer类

python 复制代码
env = simpy.Environment()
counter = simpy.Resource(env, capacity=1)  # counter是整个仿真环境的资源,且只有一个柜台

customers = []
for i in range(NUM_OF_CUSTOMERS):
    customers.append(Customer(env, name=str(i), counter=counter, time_in_bank=12.0))
env.run()

(2)修改customers_arrival函数,在遍历所有用户时,将yield的timeout去掉,但值得注意的是此时customers_arrival就不再是一个generator,所以不能用env.process(customers_arrival(...)),而是直接运行这个函数:

python 复制代码
def customers_arrival(env, num_of_customers, interval_customers, counter):
    """
    定义用户抵达银行的这个随机过程
    :param env: simpy的仿真环境
    :param num_of_customers: 用户的总数量
    :param interval_customers: 用户抵达银行的大致时间间隔
    :param counter: 银行柜台 (是simpy中的resource)
    :return:
    """

    for i in range(num_of_customers):
        Customer(env, name=str(i), counter=counter, time_in_bank=12.0)
        # t = random.expovariate(1.0 / interval_customers)
        # yield env.timeout(t)

env = simpy.Environment()
counter = simpy.Resource(env, capacity=1)  # counter是整个仿真环境的资源,且只有一个柜台
customers_arrival(env, NUM_OF_CUSTOMERS, INTERVAL_CUSTOMERS, counter)
# env.process(customers_arrival(env, NUM_OF_CUSTOMERS, INTERVAL_CUSTOMERS, counter))
env.run()

结果是一样的:

Customer:  0  arrive the bank at:  0
Customer:  1  arrive the bank at:  0
Customer:  2  arrive the bank at:  0
Customer:  3  arrive the bank at:  0
Customer:  4  arrive the bank at:  0
Customer:  0  is ready to use the counter at:  0
Customer:  1  has waited for:  1.1659990389377906  cannot wait any longer!
Customer:  3  has waited for:  1.5290675757676708  cannot wait any longer!
Customer:  2  has waited for:  2.3110494692657597  cannot wait any longer!
Customer:  4  has waited for:  2.856268835569244  cannot wait any longer!
Customer:  0  finished at:  18.837525165283687
相关推荐
梦想画家3 分钟前
Python Polars快速入门指南:LazyFrames
python·数据分析·polars
程序猿000001号15 分钟前
使用Python的Seaborn库进行数据可视化
开发语言·python·信息可视化
API快乐传递者24 分钟前
Python爬虫获取淘宝详情接口详细解析
开发语言·爬虫·python
公众号Codewar原创作者26 分钟前
R数据分析:工具变量回归的做法和解释,实例解析
开发语言·人工智能·python
FL162386312931 分钟前
python版本的Selenium的下载及chrome环境搭建和简单使用
chrome·python·selenium
巫师不要去魔法部乱说35 分钟前
PyCharm专项训练5 最短路径算法
python·算法·pycharm
Chloe.Zz42 分钟前
Python基础知识回顾
python
骑个小蜗牛1 小时前
Python 标准库:random——随机数
python
Trouvaille ~1 小时前
【机器学习】从流动到恒常,无穷中归一:积分的数学诗意
人工智能·python·机器学习·ai·数据分析·matplotlib·微积分
汤姆和佩琦1 小时前
2024-12-25-sklearn学习(20)无监督学习-双聚类 料峭春风吹酒醒,微冷,山头斜照却相迎。
学习·聚类·sklearn