python实现电影院仿真(SimPy)

2023-11-15

SimPy: Simulating Real-World Processes With Python
仿真环境:电影院仿真
目标:减少顾客的平均等待时间,少于10分钟
在开始仿真前,先思考这个仿真过程,顾客在坐下来看电影前需要经过哪些步骤

  • 到达影院
  • 排队买票
  • 买到票
  • 排队检票
  • 检查完票
  • 决定是否买零食
  • 买零食或者直接入场坐下

这些步骤中又一些是可以控制的,比如有多少雇员在卖票或者卖小零食,有一些步骤需要依赖之前的数据进行预测,比如有多少顾客到达,接下来开始仿真过程,首先导入需要的库

import random
import simpy
import statistics

记录优化目标:找到雇员的最佳数量,使所有顾客的平均等待时间小于10分钟,使用列表存储顾客等待时间

wait_times = []

1.Creating the Environment: Class Definition

构建仿真第一步是分析系统蓝图,也就是你的整个环境中会发生的事,会从某个地方移动到另一个地方的人或物,环境可以是任何类型的系统,如银行,洗车所,安全检查等,在本例中,环境是一个电影院,因此定义类名:Theater

class Theater(object):
    def __init__(self):
        pass

现在开始思考电影院的组成部分,首先肯定有theater本身,也就是你的environment,之后,你会用simpy的一些函数补充theater使其更像真实的环境,现在只需要简单将其添加到类定义中

class Theater(object):
    def __init__(self, env):
        self.env = env

接下来继续思考电影院还会有什么,通过之前分析的步骤可以知道,当顾客到达的时候,他们需要在指定位置排队,然后有收银员帮助顾客购票,也就是说,环境里有以下两件事:

  • 收银员cashiers
  • 顾客可以从收银员手中买票purchase tickets

cashiers可以看成电影院提供给顾客的资源resource,他们帮助顾客完成买票这个进程process,但是目前你不知道在仿真环境里有多少cashiers,实际上,这就是你需要解决的问题,顾客等待时间也取决于cashier数量,你可以把这个未知数量称为num_cashiers,实际取值可以之后筛选,现在你只知道cashier是theater环境必不可少的部分,将其添加到类定义中

class Theater(object):
    def __init__(self, env, num_cashiers):
        self.env = env
        self.cashier = simpy.Resource(env, num_cashiers)

这样就增加了一个新参数num_cashiers,创建了一个资源self.cashier,使用simpy.Resource()表示在某时刻有多少资源。还需要考虑的是cashier帮顾客买票是需要时间的,可以通过历史数据得出买一张票大概需要1-3分钟,那么如何在simpy中表示这个过程?只需要使用timeout这个事件

yield self.env.timeout(random.randint(1, 3))

env.timeout()告诉simpy在经过指定时间后去触发某个事件,在本例中这个事件就是顾客买到票。现在把这个事件封装到函数里

class Theater(object):
    def __init__(self, env, num_cashiers):
        self.env = env
        self.cashier = simpy.Resource(env, num_cashiers)
    
    def purchase_ticket(self, moviegoer):
        yield self.env.timeout(random.randint(1, 3))

触发purchase_ticket()事件的事顾客,因此需要传入moviegoer。目前为止就定义了一个有时间限制的资源,以及与它相关的进程,除了cashier,还有两类资源需要定义

  • 用来检票的Ushers
  • 用来卖食物的Servers

假设经过历史数据得知,一位Usher检查一次票只需要3秒,一位Server交易一次需要1-5分钟,这俩类资源的处理过程如同上面的cashier,代码类似

class Theater(object):
    def __init__(self, env, num_cashiers, num_ushers, num_servers):
        self.env = env
        self.cashier = simpy.Resource(env, num_cashiers)
        self.usher = simpy.Resource(env, num_ushers)
        self.server = simpy.Resource(env, num_servers)
    
    def purchase_ticket(self, moviegoer):
        yield self.env.timeout(random.randint(1, 3))
    
    def check_ticket(self, moviegoer):
        yield self.env.timeout(3 / 60)
    
    def sell_food(self, moviegoer):
        yield self.env.timeout(random.randint(1, 5))

2.Moving Through the Environment: Function Definition

目前为止已经通过定义类组建完了environment,有资源和进程,现在开始模拟一位顾客使用它们,当一位moviegoer到达影院时,就会开始申请各种资源,直到顾客要做的事件全部完成。

def go_to_movies(env, moviegoer, theater):
    # Moviegoer到达theater
    arrival_time = env.now

需要传入三个参数:

  • env:moviegoer的行为在环境中指定
  • moviegoer:可以当成是顾客编号
  • theater:调用theater中定义好的进程

使用env.now可以获取每位moviegoer到达时间。moviegoer在theater中的每个process都有对应的requests来申请资源,例如第一个process是purchase_ticket(),需要使用一个cashier资源,moviegoer需要申请获取cashier才能执行process
在这里插入图片描述
cashier是一类共享资源,这意味着许多moviegoer使用相同的cashier,但是同一时刻,一个cashier只能帮助一位moviegoer,因此需要一些等待时间。

def go_to_movies(env, moviegoer, theater):
    # Moviegoer到达theater
    arrival_time = env.now
    
    with theater.cashier.request() as request:
        yield request
        yield env.process(theater.purchase_ticket(moviegoer))
  • theater.cashier.request():moviegoer申请一个cashier
  • yield request:如果所有的cashier都在使用中,moviegoer需要等待直到一个cashier空闲
  • yield env.process():moviegoer借助可用的cashier完成指定的process,也就是theater.purchase_ticket()

资源使用完后必须被释放release(),此处使用with语句,资源就会自动释放。当一个cashier空闲,moviegoer就会花一些时间买票,env.process()告诉仿真进入到Theater实例中运行purchase_ticket()。在检票这里也是一样的流程:request,use,release。但是顾客买零食是随机可选的,这种不确定性的事件可以使用随机数来表示。

  • True:moviegoer申请server然后订购食物
  • False:moviegoer直接入座
def go_to_movies(env, moviegoer, theater):
    # Moviegoer到达theater
    arrival_time = env.now
    
    with theater.cashier.request() as request:
        yield request
        yield env.process(theater.purchase_ticket(moviegoer))
        
    with theater.usher.request() as request:
        yield request
        yield env.process(theater.check_ticket(moviegoer))   
    
    if random.choice([True, False]):
        with theater.server.request() as request:
            yield request
            yield env.process(theater.sell_food(moviegoer))   

仿真的目的是为了确定cashier,usher,server数量,减少顾客等待时间,因此关键在于记录顾客从到达到入座经过了多长时间

def go_to_movies(env, moviegoer, theater):
    # Moviegoer到达theater
    arrival_time = env.now
    
    with theater.cashier.request() as request:
        yield request
        yield env.process(theater.purchase_ticket(moviegoer))
        
    with theater.usher.request() as request:
        yield request
        yield env.process(theater.check_ticket(moviegoer))   
    
    if random.choice([True, False]):
        with theater.server.request() as request:
            yield request
            yield env.process(theater.sell_food(moviegoer))   
    
    wait_times.append(env.now - arrival_time)

这里也可以用单独的列表departure_time存储离开时间,但是没有必要,这只是重复的代码,DRP(do not repeat yourself)

3.Making Things Happen: Function Definition

现在,你需要定义一个函数来运行仿真,run_theater()会实例化theater,不停的生成moviegoers直到仿真停止

def run_theater(env, num_cashiers, num_servers, num_ushers):
    theater = Theater(env, num_cashiers, num_ushers, num_servers)

传入之前定义的三个参数:

  • num_cashiers
  • num_ushers
  • num_servers

这些参数决定了仿真环境的配置。假设在仿真开始的时候就有一些moviegoers在电影院等候,现实生活中也可能会有电影院还没开门的时候就有人等着。设置初始时刻有3位moviegoer在排队等待买票

def run_theater(env, num_cashiers, num_servers, num_ushers):
    theater = Theater(env, num_cashiers, num_ushers, num_servers)

    for moviegoer in range(3):
        env.process(go_to_movies(env, moviegoer, theater))

使用range()生成3位moviegoer,然后使用env.process()告诉仿真准备按流程流动moviegoer,剩下的moviegoer会在某时刻到达theater,因此这个函数应该能不停的在仿真运行期间生成新的moviegoer。假设每12秒(0.2分钟)到达一位顾客,使用timeout表示时间间隔

def run_theater(env, num_cashiers, num_servers, num_ushers):
    theater = Theater(env, num_cashiers, num_ushers, num_servers)

    for moviegoer in range(3):
        env.process(go_to_movies(env, moviegoer, theater))
    
    while True:
        yield env.timeout(0.2)
        moviegoer += 1
        env.process(go_to_movies(env, moviegoer, theater))

4.Calculating the Wait Time: Function Definition

运行完wait_times列表记录了所有顾客的等待时间,对数据进行处理,计算均值,输出值用每分每秒表示

def calculate_wait_time(wait_times):
    average_wait = statistics.mean(wait_times)
    minutes, frac_minutes = divmod(average_wait, 1)
    seconds = frac_minutes * 60
    return round(minutes), round(seconds)

5.Choosing Parameters: User Input Function Definition

之前的环境取决于以下三个变量的值:

  • num_cashiers
  • num_servers
  • num_ushers

仿真的优势在于你可以随意测试这些参数改变仿真场景,可以单独设置函数来获取仿真配置参数

def get_user_input():
    num_cashiers = input('cashier number: ')
    num_ushers = input('ushers number: ')
    num_servers = input('servers number: ')
    params = [num_cashiers, num_ushers, num_servers]
    # 检查输入是否错误
    if all(str(i).isdigit() for i in params):
        params = [int(x) for x in params]
    else:
        print('input wrong, simulation start with default value')
        params = [1, 1, 1]
    return params

6.Finalizing the Setup: Main Function Definition

最后一步就是创建主函数

def main():
    # setup
    random.seed(42)
    num_cashiers, num_ushers, num_servers = get_user_input()
    
    # run
    env = simpy.Environment()
    env.process(run_theater(env, num_cashiers, num_ushers, num_servers))
    env.run(until=90)
    
    # output
    mins, secs = calculate_wait_time(wait_times)
    print( "Running simulation...",
      f"\nThe average wait time is {mins} minutes and {secs} seconds.")
  • 设置随机数,获取环境配置参数
  • 创建environment
  • 告诉simpy运行run_theater()进程:创建theater环境和生成moviegoer
  • 设置运行时间为90
  • 计算并输出平均等待时间

7.How to Run the Simulation

最后回顾一下定义的类和函数

  • Theater:此类定义了你要模拟的环境的蓝图。它确定有关该环境的一些信息,例如可用的资源类型以及与它们相关联的进程
  • go_to_movies():此函数发出使用资源的明确请求,执行完相关的进程,然后将其释放给下一个电影观众
  • run_theater():该函数控制仿真过程,使用Theater蓝图创建一个剧院的实例,然后调用go_to_movies()来生成和移动人们完成所有步骤
  • calculate_wait_time():计算并输出等待时间
  • get_user_input():获取环境配置参数
  • main():集成以上内容作为主函数
if __name__ == '__main__':
    main()
cashier number: 2
ushers number: 2
servers number: 2
Running simulation... 
The average wait time is 36 minutes and 44 seconds.

8.Full code

class Theater(object):
    def __init__(self, env, num_cashiers, num_ushers, num_servers):
        self.env = env
        self.cashier = simpy.Resource(env, num_cashiers)
        self.usher = simpy.Resource(env, num_ushers)
        self.server = simpy.Resource(env, num_servers)
    
    def purchase_ticket(self, moviegoer):
        yield self.env.timeout(random.randint(1, 3))
    
    def check_ticket(self, moviegoer):
        yield self.env.timeout(3 / 60)
    
    def sell_food(self, moviegoer):
        yield self.env.timeout(random.randint(1, 5))
        
        
def go_to_movies(env, moviegoer, theater):
    # Moviegoer到达theater
    arrival_time = env.now
    
    with theater.cashier.request() as request:
        yield request
        yield env.process(theater.purchase_ticket(moviegoer))
        
    with theater.usher.request() as request:
        yield request
        yield env.process(theater.check_ticket(moviegoer))   
    
    if random.choice([True, False]):
        with theater.server.request() as request:
            yield request
            yield env.process(theater.sell_food(moviegoer))   
    # Moviegoer离开theater
    wait_times.append(env.now - arrival_time)


def run_theater(env, num_cashiers, num_servers, num_ushers):
    theater = Theater(env, num_cashiers, num_ushers, num_servers)

    for moviegoer in range(3):
        env.process(go_to_movies(env, moviegoer, theater))
    
    while True:
        yield env.timeout(0.2)
        moviegoer += 1
        env.process(go_to_movies(env, moviegoer, theater))
    
    
def calculate_wait_time(wait_times):
    average_wait = statistics.mean(wait_times)
    minutes, frac_minutes = divmod(average_wait, 1)
    seconds = frac_minutes * 60
    return round(minutes), round(seconds)


def get_user_input():
    num_cashiers = input('cashier number: ')
    num_ushers = input('ushers number: ')
    num_servers = input('servers number: ')
    params = [num_cashiers, num_ushers, num_servers]
    # 检查输入是否错误
    if all(str(i).isdigit() for i in params):
        params = [int(x) for x in params]
    else:
        print('input wrong, simulation start with default value')
        params = [1, 1, 1]
    return params


def main():
    # setup
    random.seed(42)
    num_cashiers, num_ushers, num_servers = get_user_input()
    
    # run
    env = simpy.Environment()
    env.process(run_theater(env, num_cashiers, num_ushers, num_servers))
    env.run(until=90)
    
    # output
    mins, secs = calculate_wait_time(wait_times)
    print( "Running simulation...",
      f"\nThe average wait time is {mins} minutes and {secs} seconds.")
    

if __name__ == '__main__':
    main()
cashier number: 2
ushers number: 3
servers number: 1
Running simulation... 
The average wait time is 36 minutes and 43 seconds.
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

python实现电影院仿真(SimPy) 的相关文章

随机推荐

  • redis数据库hset(有序集合)类型常用命令

    redis数据库hset类型常用命令 1 向有序集合添加一个或多个成员 或者更新已存在成员的分数 zadd key score1 member1 score2 member2 2 获取有序集合的成员数 zcard key 3 计算在有序集合
  • C++之监控文件是否被修改

    软件开发过程中经常会用到配置文件 某些应用场景要求在软件运行时动态修改配置文件 此时就需要监控配置文件是否被修改 如果修改了 重新加载 FileWatcher h ifndef FILEWATCHER H define FILEWATCHE
  • openfire服务器源码,Openfire源码部署以及编译运行.doc

    Openfire源码部署以及编译运行 Openfire源码下载 可以去官方网站 官网地址 projects openfire 也可以利用eclispe自带的SVN插件导入 再次就过多介绍 官网上写的很清楚 源码部署编译 将源码解压到硬盘上
  • git:恢复文件

    如果需要在提交历史中跳转查看某个文件 可以使用 git restore 仅仅恢复工作树为某个提交版本 而不用切换分支 HEAD 仍然保持不变 假设现在git仓库如图 git restore 命令用于从 index 或某个 commit 恢复
  • dedecms的图片轮换

    思路 在dedecms中引进js和css要用 dede global cfg templets skin 引入文件用 dede include filename head htm 当你点图片的时候会到那一篇文章中 他用到的是dede arc
  • 对于女生来说,软件测试和前端,学哪一个更好啊

    其实前端和软件测试都算是对新手比较友好的学科了 而且是两个女生选择相对比较多的学科 简单好学要看你是从哪方面来考虑 至于哪家培训机构好 这个还是要看你自己的综合考量 选择学科还是要综合考量一下 发展前景 学习内容 发展方向 薪资 自己兴趣
  • 华为慧通真相--关联企业迷局

    华为慧通真相 关联企业迷局 回顾我自己已走过的历史 扪心自问 我一生无愧于祖国 无愧于人民 无愧于事业与员工 无愧于朋友 在 我的父亲母亲 一文中 华为的灵魂人物誓言旦旦 但是随着近期一系列真正 华为真相 浮出水面 不得不让人怀疑 这位笼罩
  • git 仓库 端口 prot22 :拒绝连接

    今天新入职一家公司 遇到了git 拉不下代码的问题 http 方式是可以的 但是 ssh 方式是不行的 所以今天记录一下 我是配置了的 生成ssh ssh keygen t rsa C your email example com 然后 配
  • 基于元数据的数据治理分析功能说明

    数据对于企业来说是非常重要的 因为企业数据需要保证其完整性和准确性 所以需要数据治理 MDM基础数据平台是对各个业务系统的主数据进行治理 而各个业务系统中的业务数据则需要在DAP数据分析平台进行治理 DAP数据分析平台通过与ESB应用集成平
  • 华为od机试 Python 【寻找最大距离】

    题目 小明需要在一个沙地上种下一些树木 但是这片沙地上只有特定的一些位置可以种树 小明想要尽可能地增大树之间的距离来更好地防止沙尘暴 你的任务是帮助他找到这样一个距离 使得在这个距离下他可以种下所有的树 而且这个距离是所有可能距离中最大的
  • MySQL查看锁的sql

    MySQL查看锁的sql 查看数据库状态 会显示加锁的信息等等 show engine innodb status 查看正在执行的线程信息 show full processlist 查看正在锁的表 show open tables whe
  • 利用Nacos服务获取配置逻辑的特点,实现动态配置kafak认证

    我要做什么 实现Nacos动态配置kafka认证信息 使每个微服务读取同一个kafka配置 并生成文件注入到环境变量中 为什么要这么做 首先我们看下 Kafka java接入demo 如图 1 prod client jaas conf文件
  • [GVIM] Increasing or decreasing numbers

    原文链接 https vim fandom com wiki Increasing or decreasing numbers In normal mode typing Ctrl A will increment the next num
  • 怎么用chatgpt润色

    有需要润色的小伙伴 我来帮你们
  • 计算机应用基础本形考任务模块一测验题,国家开放大学《计算机应用基础》考试与答案形考任务模块1-、2—客观题测验答案(59页)-原创力文档...

    模块1 windows 7 操作系统 客观题测验 1 以 为核心组成的微型计算机属于集成电路计算机 A 微处理器 B 电子管 C 晶体管 D 机械 答案是 微处理器 题目2 电子计算机诞生于1946 A 第四台 B 第一台 C 第二台 D
  • Kaptcha配置CaptchaConfig和控制CaptchaController

    前端调用对应的后端接口即可使用验证码 Kaptcha所需依赖 版本号不一
  • python的types模块

    python的types模块 1 types是什么 types模块中包含python中各种常见的数据类型 如IntType 整型 FloatType 浮点型 等等 gt gt gt import types gt gt gt dir typ
  • 华为机试题:求偶数个复数的平均值

    题目描述 由实部和虚部组成 形如 a bi 这样的数 称为复数 通信系统中 通常用32bit数来表示复数 高16bit表示实部 低16bit表示虚部 如整数524295 16进制为0x00080007 所代表的复数 实部为0x0008 虚部
  • Linux系统创建桌面快捷方式,安装idea,配置idea环境

    一 下载Linux版IDEA 使用浏览器打开IDEA官网的链接 https www jetbrains com idea 或https www jetbrains com idea download other html 使用wget命令下
  • python实现电影院仿真(SimPy)

    SimPy Simulating Real World Processes With Python 仿真环境 电影院仿真 目标 减少顾客的平均等待时间 少于10分钟 在开始仿真前 先思考这个仿真过程 顾客在坐下来看电影前需要经过哪些步骤 到