iT邦幫忙

1

淺談『模擬』(Simulation) 【1】-- 概念介紹

  • 分享至 

  • xImage
  •  

前言

這幾年台北大巨蛋炒得沸沸騰騰,容留人數上限由原來規劃的13萬人,經過都發局防災避難系統『模擬』,最多只能以59,833人為上限,透過模擬我們可以評估整個系統的承載容量。也可以透過資源的配置,改善系統的服務水準(Service Level),其他應用還包括客服中心人力的規劃、生產資源的配置、火車/客機/客運售票窗口規劃、郵局/便利商店的服務櫃台設置等等,應用範疇很廣,值得我們好好的研究一番,進而建構相關的系統。

簡單的說,模擬(Simulation)是希望建構電腦模型以模擬真實世界的運作狀況,並進一步分析系統的承載容量(Capacity)或找出瓶頸,進而針對瓶頸增加資源(Resource)或變更流程,從而改善系統運作的順暢度。

系統建構

要建構一套模擬系統,我們必須先描繪出真實世界的流程及各項作業,以戲院入場流程為例:

  1. 觀眾要先至售票口買票。
  2. 進場驗票。
  3. 有些人會去小吃部購買零食。
  4. 進入戲院,找到座位。

https://ithelp.ithome.com.tw/upload/images/20221212/20001976ZbHZNp4ldT.jpg
圖一. 戲院入場流程

接著,要描述各個作業的到站人數與作業時間,例如:

  1. 觀眾進場人數與頻率。
  2. 售票口的作業時間。
  3. 驗票的作業時間。
  4. 購買零食的人數、小吃部的作業時間。
  5. 找到座位要花費的時間。

之後就可以配置資源,進行系統模擬:

  1. 要安排幾個售票窗口:窗口太少,造成大排場龍,有些顧客可能放棄觀看,造成損失,反之,窗口太多,人力成本過多,設備也要增購。
  2. 要安排幾個驗票人力:驗票速度太慢,會造成人潮擁擠、影響公共場所的安全與品質、影片放映的延遲。
  3. 要安排幾個小吃部銷售員。

依上述說明,定義模擬的一些專業術語:

  1. 環境(Environment):即整個系統流程,並串連各項處理作業先後順序。
  2. Agent:被服務的對象或生產/處理的物品,以戲院入場為例,觀眾就是Agent。如為洗車場系統,車輛為Agent。
  3. 處理(Process):售票、驗票等處理作業。
  4. 資源(Resource):如售票窗口、驗票人力,以生產系統為例,設備、工作站也是資源,通常資源是有限的。

模擬的專業術語與『強化學習』(Reinforcement Learning)相似,但強化學習目標是優化Agent的行動策略,而模擬是評估或優化整個系統流程。

實作

筆者在校時,曾學習相關軟體GPSS,它是安裝在計算機中心的大型主機上,離開校園就無法使用了,非常可惜,還好,有善心人士以Python開發許多套件,雖然功能沒有那麼強,但比較容易自行修改擴充。以下就以SimPy套件進行實作。

安裝:
pip install simpy

範例 1. 簡單範例,說明程式基本架構。

  1. 載入套件。
import simpy
  1. 建立一個處理作業,輸入參數為env(環境):一般以generator函數實作,通常是無窮迴圈,yield會傳回數值,函數暫停(Suspend)在該行程式碼,下次被再次呼叫時,會從下一行開始。env.now()會傳回當前時間,以0為起始時間。env.timeout(1)要求環境等待1單位時間再觸發事件,SimPy不設定時間的單位為秒或分,由開發者自己認定。
def clock(env):
    while True:
        print("開始:", env.now)  # env.now獲取當前時間
        yield env.timeout(1)  # 等待1秒
        print("結束:", env.now)  # env.now獲取當前時間
  1. 主程式碼:建立環境及各項處理作業,env.run(until=5)會運行整個環境5單位時間(0~4),程式即結束。
# 建立環境
env = simpy.Environment()
# 啟動處理作業
env.process(clock(env))
# 運行模擬環境5秒
env.run(until=5)
  1. 輸出結果:每隔1單位時間觸發clock函數,結果如下。
    開始: 0
    結束: 1
    開始: 1
    結束: 2
    開始: 2
    結束: 3
    開始: 3
    結束: 4
    開始: 4

範例 2. 建立兩個平行處理作業,一個每隔0.5秒觸發clock函數,另一個每隔1秒觸發clock函數。

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))
env.process(clock(env, 'slow', 1))
env.run(until=2)
  1. 輸出結果:兩個處理作業各自被按時觸發,不會被阻塞。
    fast 0
    slow 0
    fast 0.5
    slow 1
    fast 1.0
    fast 1.5

範例 3. 實作戲院入場流程,程式來自『SimPy: Simulating Real-World Processes With Python』

  1. 載入套件。
import simpy
import random
import statistics
  1. 建立List,記錄每位觀眾總等待時間,系統以縮短觀眾等待時間為目標。
wait_times = [] # 記錄觀眾等待時間
  1. 建立戲院的各項作業:包括購票、驗票、購物,並設定作業時間,可以是固定時間或機率分配。
# 戲院類別
class Theater(object):
    def __init__(self, env, num_cashiers, num_servers, num_ushers):
        self.env = env
        self.cashier = simpy.Resource(env, num_cashiers) # 售票口
        self.server = simpy.Resource(env, num_servers)   # 驗票口
        self.usher = simpy.Resource(env, num_ushers)     # 小吃部銷售員

    # 購票時間 1~3分鐘
    def purchase_ticket(self, moviegoer):
        yield self.env.timeout(random.randint(1, 3))

    # 驗票時間 3秒鐘
    def check_ticket(self, moviegoer):
        yield self.env.timeout(3 / 60)

    # 購物時間 1~5分鐘
    def sell_food(self, moviegoer):
        yield self.env.timeout(random.randint(1, 5))

  1. 定義觀眾入場的流程:按購票、驗票、購物、入座順序進行,購物是隨機行為,平均機率0.5。流程依實際狀況設定,可以非常複雜,包括平行處理。
# 觀眾進場
def go_to_movies(env, 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))

    # 購物:1/2機率會購物
    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)
  1. 建立模擬函數:依上述類別建立物件或呼叫函數,並設定進場人數及頻率,通常會使用卜瓦松分配(Poisson Distribution),即單位時間內隨機事件發生的次數,以下程式設定較單純,每隔0.2分鐘有一觀眾到場。
# 模擬函數
def run_theater(env, num_cashiers, num_servers, num_ushers):
    # 建立環境
    theater = Theater(env, num_cashiers, num_servers, num_ushers)

    # 初始人數:3人
    for moviegoer in range(3):
        env.process(go_to_movies(env, moviegoer, theater))

    # 模擬
    while True:
        yield env.timeout(0.20)  # 每隔0.2分鐘有一觀眾到場

        moviegoer += 1
        env.process(go_to_movies(env, moviegoer, theater))
  1. 統計等待時間的函數。
def get_average_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)
  1. 設定資源個數的函數:包括售票口個數、驗票口個數及小吃部銷售員人數。
# 設定資源個數
def get_user_input():
    # 輸入資源個數
    num_cashiers = input("售票口個數: ")
    num_servers = input("驗票口個數: ")
    num_ushers = input("小吃部銷售員人數: ")
    params = [num_cashiers, num_servers, num_ushers]
    if all(str(i).isdigit() for i in params):  # Check input is valid
        params = [int(x) for x in params]
    else:
        print(
            "Could not parse input. Simulation will use default values:",
            "\n1 cashier, 1 server, 1 usher.",
        )
        params = [1, 1, 1]
    return params
  1. 主程式:程序包括設定資源個數、建立環境、處理作業,之後即啟動模擬90分鐘。
def main():
    # Setup
    random.seed(42)
    num_cashiers, num_servers, num_ushers = get_user_input()

    # 啟動模擬
    env = simpy.Environment()
    env.process(run_theater(env, num_cashiers, num_servers, num_ushers))
    env.run(until=90) # 模擬90分鐘

    # 統計等待時間
    mins, secs = get_average_wait_time(wait_times)
    print(
        "Running simulation...",
        f"\n觀眾平均進場時間:{mins} 分 {secs} 秒.",
    )

if __name__ == "__main__":
    main()
  1. 測試:輸出結果如下,每隔0.2分鐘有一觀眾到場,售票需要3分鐘,故會大排長龍,觀眾平均進場時間高達23分30 秒。
    售票口個數: 5
    驗票口個數: 5
    小吃部銷售員人數: 5
    Running simulation...
    觀眾平均進場時間:23 分 30 秒.

  2. 測試2:輸出結果如下,安排大量人力資源,觀眾幾乎不必等待。
    售票口個數: 50
    驗票口個數: 50
    小吃部銷售員人數: 50
    Running simulation...
    觀眾平均進場時間:3 分 29 秒.

小結

戲院入場流程只是一個很簡單的場景,實際上的應用可能複雜許多,因此,SimPy、GPSS等套件都支援更多的功能,另外,如何將模擬視覺化,讓使用者可以觀察到模擬的景象,進而察覺瓶頸,也是一個很重要的課題,大巨蛋緊急疏散模擬動畫,就讓我們看到光復南路/忠孝東路交界處是一個瓶頸,一堆人堵塞在那裏。

後續文章希望能作更深入的探討,本篇的程式碼放在GitHub,讀者可自行下載。


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言