Simpy 模拟排队系统

以下代码是模拟了一个 M/M/c/k/n 系统
M 顾客以指数分布达到
M 服务顾客的事件服从指数分布
c 队列人数上限
k 服务台的数目
n 最长等待时间

项目地址:
https://github.com/eftales/PythonDemo/blob/master/%E7%A6%BB%E6%95%A3%E4%BA%8B%E4%BB%B6%E4%BB%BF%E7%9C%9F/simpyDemo.py

'''
基础知识:
1. random.expovariate(miu) 生成均值为 1/miu 的指数分布的随机数 
2. 泊松过程的强度参数的意义:如果泊松过程的强度参数为 lambda,则在单位时间上新增一次的概率为 lambda,lambda 越大事件越可能发生
3. 泊松事件的事件间隔彼此独立且服从参数为 lambda 的指数分布
4. ρ = λ/μ 
5. 平均等待时间 = ρ/(μ-λ)
6. 平均队列长度(包含正在被服务的人) = λ/(μ-λ)
'''

'''
实现的细节:
1. 统计函数没有将仿真结束时没有被服务完的人算入
'''

import simpy
import random
from time import time
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

## 随机种子
randomSeed = time() # time()

## 指数分布的均值
miuService = 1  # 单位时间平均离开 1 个人
lambdaReachInterval = 0.5  # 单位时间平均来 0.5 个人

## 服务台的数目
numService = 1

## 仿真程序运行的时间 min
Until = 100

## 系统容量
systemCapacity = None # None 表示无容量限制 max(10,numService)

## 最大等待时间 超过这个事件之后顾客会离开队伍
maxWaiteTime = Until

## 初始队列长度
initLen = 1

## 客户类
class Customer(object):
    def __init__(self,index_,startTime_,queueLenStart_,reachInterval_):
        self.index = index_ # 第几个来到队列中来的
        self.startTime = startTime_ # 开始时间
        self.getServedTime = None # 开始被服务的时间
        self.endTime = None # 结束时间 
        self.queueLenStart = queueLenStart_ # 开始排队时队列长度
        self.queueLenEnd = None # 结束排队时队列长度
        self.reachInterval = reachInterval_  # 空闲了多长时间本 customer 才到达


## 顾客列表
customerList = []

## 当前队列长度
queueLen = 0


class System(object):
    def __init__(self, env, numService,miuService_):
        self.env = env
        self.service = simpy.Resource(env, numService)
        self.miuService =  miuService_

        
    def beingServed(self):
        # 服务事件为均值为 miuService 的指数分布
        yield self.env.timeout(random.expovariate(self.miuService))

def inoutQueue(env, moviegoer, sys):
    # 等待被服务 
    with sys.service.request() as request:  #观众向收银员请求购票
        yield request  | env.timeout(maxWaiteTime)  #观众等待收银员完成面的服务,超过最长等待事件maxCashierTime就会离开
        global customerList
        customerList[moviegoer].getServedTime = env.now
        yield env.process(sys.beingServed()) 
            
    # 完善统计资料
    global queueLen
    queueLen -= 1
    customerList[moviegoer].endTime = env.now
    customerList[moviegoer].queueLenEnd = queueLen


def runSys(env, numService,miuService):
    sys = System(env, numService, miuService)
    global initLen,customerList
    moviegoer = initLen
    for moviegoer in range(initLen):#初始化设置,开始的队伍长度为 initLen
        customerList.append(Customer(moviegoer,env.now,initLen,0))
        env.process(inoutQueue(env, moviegoer, sys))
    global queueLen
    queueLen = initLen 
    while True:
        reachInterval_ = random.expovariate(lambdaReachInterval)
        yield env.timeout(reachInterval_) # 顾客到达时间满足 lambdaReachInterval 的指数分布
        if systemCapacity == None or queueLen <= systemCapacity:  
            moviegoer += 1
            queueLen += 1
            customerList.append(Customer(moviegoer,env.now,queueLen,reachInterval_))
            env.process(inoutQueue(env, moviegoer, sys))

def plotSimRes(customerList):
    #! 初始设置
    # 用于正常显示中文标签
    plt.rcParams['font.sans-serif']=['SimHei']
    # 用来正常显示负号
    plt.rcParams['axes.unicode_minus']=False


    def plotTime_Service(customerList):
        plt.figure(figsize=(14,7)) # 新建一个画布
        plt.xlabel('时间/min')
        plt.ylabel('用户序列')
        servedUser = 0
        for customer in customerList:
            y = [customer.index]*2

            # 等待时间 
            if customer.endTime == None:
                customer.endTime = Until
                color = 'r'
            else:
                color = 'b'
                servedUser += 1
            x = [customer.startTime,customer.endTime]
            plt.plot(x,y,color=color)

            # 被服务的时间
            if customer.getServedTime != None and customer.endTime != Until:
                color = 'g'
                x = [customer.getServedTime,customer.endTime]
                plt.plot(x,y,color=color)

        plt.title("时间-队列-服务图 服务的用户数:%d" % servedUser)

    def plotQueueLen_time(customerList):
        plt.figure(figsize=(14,7)) # 新建一个画布
        plt.xlabel('时间/min')
        plt.ylabel('队列长度/人')
        

        queueLenList = []

        for customer in customerList:
            queueLenList.append([customer.startTime,customer.queueLenStart])
            queueLenList.append([customer.endTime,customer.queueLenEnd])
        queueLenList.sort()

        preTime = 0
        preLen = 0
        integralQueueLen = 0
        maxLen = 0
        global Until
        timeInCount = Until
        for each in queueLenList:
            if each[1] != None:
                x = [each[0]] * 2
                y = [0,each[1]]
                plt.plot(x,y,color='b')
                plt.plot(each[0],each[1],'bo')
            else:
                timeInCount = preTime
                break # 没有把仿真结束时未被服务完的人算进来
            integralQueueLen += (each[0] - preTime) * preLen
            preTime = each[0]
            preLen = each[1]
            maxLen = max(maxLen,each[1])
        
        averageQueueLen = integralQueueLen / timeInCount
        plt.title("时间-队列长度图 平均队列长度:%f" % averageQueueLen)
        
        
    def plotWaiteTime_time(customerList):
        plt.figure(figsize=(14,7)) # 新建一个画布
        plt.xlabel('时间/min')
        plt.ylabel('等待时间/min')
        

        queueLenList = []
        peopleInCount = 0
        for customer in customerList:
            if customer.getServedTime != None:
                peopleInCount += 1
                queueLenList.append([customer.startTime,customer.getServedTime - customer.startTime])
        queueLenList.sort()
        
        integralWaiteTime = 0
        maxWaiteTime = 0
        for each in queueLenList:
            x = [each[0]] * 2
            y = [0,each[1]]
            integralWaiteTime += each[1]
            maxWaiteTime = max(maxWaiteTime,each[1])
            plt.plot(x,y,color='b')
            plt.plot(each[0],each[1],'bo')

        averageWaiteTime = integralWaiteTime / peopleInCount
        
        plt.title("时间-等待时间图 平均等待时间:%f" % averageWaiteTime)

    def plotWaiteTime_time_QueueLen(customerList):
        fig = plt.figure(figsize=(14,7)) # 新建一个画布
        ax = fig.gca(projection='3d')
        plt.xlabel('时间/min')
        plt.ylabel('队列长度/人')
        ax.set_zlabel('等待时间/min')
        plt.title("时间-队列长度-等待时间图")

        queueLenList = [] # 格式:时间 队列长度 等待时间

        global Until
        for customer in customerList:
            if customer.getServedTime != None: # 没有把仿真结束时未被服务完的人算进来
                queueLenList.append([customer.startTime,customer.queueLenStart,customer.getServedTime-customer.startTime])
        queueLenList.sort(key=lambda x:x[0])

        for each in queueLenList:
            if each[1] != None:
                x = [each[0]]*2
                y = [each[1]]*2
                z = [0,each[2]]
                ax.plot(x,y,z,color='b')
                ax.scatter(x[1],y[1],z[1],c='b')

    plotTime_Service(customerList)
    plotQueueLen_time(customerList)
    plotWaiteTime_time(customerList)
    # plotWaiteTime_time_QueueLen(customerList)
    plt.show()



def main():
    random.seed(randomSeed)
    #运行模拟场景
    env = simpy.Environment()
    env.process(runSys(env, numService,miuService))
    env.run(until=Until) 
    
    #查看统计结果
    plotSimRes(customerList)

main()


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容