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

2024-01-09 00:52

本文主要是介绍【SimPy系列博客之官方example学习与解读】—— Example 1: Bank Renege,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

文章目录

  • 例程背景
  • 例程代码分析
  • 结果分析
  • 扩展

例程背景

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

例程代码分析

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

import simpy
import random

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

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

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

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

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:# 说明用户等到了counterprint('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后要干啥。

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加入到仿真环境中:

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,我们还需要能够灵活改变源码以满足自己的想法。

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

class Customer():def __init__(self, env, name, counter, time_in_bank):self.env = envself.name = nameself.counter = counterself.time_in_bank = time_in_bankself.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:# 说明用户等到了counterprint('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就会在对应的时间自动被触发:

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

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

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。因此,我们通过下面的代码启动仿真:

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类

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(…)),而是直接运行这个函数:

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

这篇关于【SimPy系列博客之官方example学习与解读】—— Example 1: Bank Renege的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/585420

相关文章

Redis与缓存解读

《Redis与缓存解读》文章介绍了Redis作为缓存层的优势和缺点,并分析了六种缓存更新策略,包括超时剔除、先删缓存再更新数据库、旁路缓存、先更新数据库再删缓存、先更新数据库再更新缓存、读写穿透和异步... 目录缓存缓存优缺点缓存更新策略超时剔除先删缓存再更新数据库旁路缓存(先更新数据库,再删缓存)先更新数

C#反射编程之GetConstructor()方法解读

《C#反射编程之GetConstructor()方法解读》C#中Type类的GetConstructor()方法用于获取指定类型的构造函数,该方法有多个重载版本,可以根据不同的参数获取不同特性的构造函... 目录C# GetConstructor()方法有4个重载以GetConstructor(Type[]

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl