[Pyhton] SimPy 離散事件模擬框架詳解 —— 以一個簡單的汽車充電排隊模擬爲例



一、背景知識

人們在生產活動和社會活動中,經常遇到一類複雜的系統,這類系統中有許多事件時而出現,時而消失,時而動作,時而停止,而啓動和停止都發生在一些離散的時刻,並帶有一定的隨機性。例如,港口中船舶的停靠碼頭、生產線上機牀的啓停、電話的接通和斷開、計算機系統中某項作業的進行和退出,凡此種種,都帶有上述特點,這類系統叫做離散事件動態系統(DEDS)。隨着生產和科技的日益發展,以及人類社會交往的日趨頻繁,這類系統的數量和種類也越來越多。

設計此類系統時,往往需要仿真來評估算法或方案的性能,常見的軟件有:


商業軟件:

名字 簡介 圖示
AnyLogic 通用多方法建模工具。結合了基於代理、系統動力學和離散事件建模。
Arena 一種離散事件模擬程序,也允許對連續過程進行建模。
Care pathway simulator 專門爲服務行業(如醫療保健)設計的離散事件模擬程序。
Enterprise Dynamics 一個模擬軟件平臺,用於模擬和分析幾乎任何製造、材料處理和物流挑戰。
ExtendSim 通用仿真軟件包
DELMIA 3DEXPERIENCE 平臺的一部分
FlexSim 拖拖拉拉做離散事件模擬,3D
GoldSim 將動態離散事件模擬嵌入到 Monte Carlo 框架
GPSS 離散事件模擬語言。供應商可以提供不同的實現
Micro Saint Sharp 通用離散事件建模工具,使用拖放界面和C#編程語言
MS4 Modeling Environment 基於離散事件和混合模型的通用DEVS方法的軟件環境
Plant Simulation 能夠模擬和優化生產系統和流程的軟件
ProModel AutoCAD® Edition 在數字孿生Autodesk®工具集(AutoCAD®和Inventor®)中快速構建空間精確的佈局和過程仿真模型。
Simcad Pro 實時變化,零代碼有界面,支持 VR
SimEvents 向MATLAB / Simulink環境添加離散事件仿真。
SIMUL8 基於對象的仿真軟件
VisualSim 基於時序、功耗和功能的電子、嵌入式軟件和半導體的基於模型的系統架構探索
WITNESS 可在桌面和雲端使用 VR 進行離散事件模擬

開源軟件:

名字 語言 類型 License 簡介
JaamSim Java App Apasche 2.0 JaamSim是一款自由開源的離散事件模擬軟件,包括拖放式用戶界面、交互式3D圖形、輸入和輸出處理以及模型開發工具和編輯器。
CPN Tools BETA App GPLv2 用於分析所有類型應用中的物流/排隊模型的工具。
DESMO-J Java Lib Apasche 2.0 Java離散事件模擬框架,支持混合事件/過程模型,並提供2D和3D動畫。
Facsimile Scala Lib LGPLv3 離散事件模擬/仿真庫。
PowerDEVS C++ App AFL, GPLv2 基於DEVS形式的混合系統建模和仿真集成工具。
Ptolemy II Java Lib BSD 支持面向角色設計實驗的軟件框架。
SIM.JS JavaScript Lib LGPL JS是一個完全用JavaScript編寫的通用離散事件模擬庫。在瀏覽器中運行,支持基於GUI的建模工具。
SimPy Python Lib MIT SimPy是基於標準Python的基於過程的離散事件模擬框架。
Simula Simula Language 一種專門爲模擬而設計的編程語言。
SystemC C++ Lib Apache 2.0 提供事件驅動模擬內核。

注:軟件示意圖見《附錄二》


二、SimPy 講解

2.1 SimPy 概述

1)SimPy 是 python 的離散事件模擬框架。
2)SimPy 中的流程由 Python 生成器函數定義。例如,可以用於爲客戶、車輛或代理等活動組件建模。SimPy 還提供各種類型的共享資源來模擬容量有限的擁塞點(如服務器、結賬櫃檯和隧道)。
3)模擬可以要多快有多快、實時、或者手動步進三種方式進行。
3)儘管理論上 SimPy 可以用於連續模擬,但它沒有任何功能來幫助您實現這一點。另一方面,SimPy 對於具有固定步長的模擬來說是過火的,在這種情況下,您的流程不會相互交互或共享資源。

模擬兩個時鐘在不同時間間隔滴答作響的簡短示例如下:

>>> 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))
<Process(clock) object at 0x...>
>>> env.process(clock(env, 'slow', 1))
<Process(clock) object at 0x...>
>>> env.run(until=2)
fast 0
slow 0
fast 0.5
slow 1
fast 1.0
fast 1.5

2.2 基本概念

1)SimPy 是一個離散事件仿真庫。活動組件(如車輛、客戶或消息)的行爲是用流程建模的。所有進程都存在於一個環境中。它們通過事件與環境和彼此交互。
2)流程由簡單的 Python generator 描述。你可以將他們稱爲過程函數或過程方法,取決於它是函數還是類的方法。在其整個生命週期內,他們產生事件等待被觸發。
3)當一個過程產生一個事件時,該進程就會被掛起。當事件發生時(我們說事件被觸發),SimPy 恢復該過程。多個進程可以等待同一個事件。SimPy 以它們產生該事件的相同順序恢復它們。
4)一個最重要的事件類型就是 Timeout 類事件。它允許在進程給定的時間內休眠(或保持其他狀態)。


2.3 一個汽車開開停停的例子

下面是一個簡單的汽車走走停停的例子,打印其走停的時間戳:

>>> def car(env):
...     while True:
...         print('Start parking at %d' % env.now)
...         parking_duration = 5
...         yield env.timeout(parking_duration)
...
...         print('Start driving at %d' % env.now)
...         trip_duration = 2
...         yield env.timeout(trip_duration)

>>> import simpy
>>> env = simpy.Environment()
>>> env.process(car(env))
<Process(car) object at 0x...>
>>> env.run(until=15)
Start parking at 0
Start driving at 5
Start parking at 7
Start driving at 12
Start parking at 14

2.4 在走走停停過程中增加充電過程(過程交互)

我們在上面汽車例子基礎上引入充電的過程:車走一段時間,停下來充電,電充好了,才能繼續走。
這裏引入了 charge_duration 過程,在該過程中簡單寫了一個超過的掛起事件:

>>> class Car(object):
...     def __init__(self, env):
...         self.env = env
...         # Start the run process everytime an instance is created.
...         self.action = env.process(self.run())
...
...     def run(self):
...         while True:
...             print('Start parking and charging at %d' % self.env.now)
...             charge_duration = 5
...             # We yield the process that process() returns
...             # to wait for it to finish
...             yield self.env.process(self.charge(charge_duration))
...
...             # The charge process has finished and
...             # we can start driving again.
...             print('Start driving at %d' % self.env.now)
...             trip_duration = 2
...             yield self.env.timeout(trip_duration)
...
...     def charge(self, duration):
...         yield self.env.timeout(duration)

>>> import simpy
>>> env = simpy.Environment()
>>> car = Car(env)
>>> env.run(until=15)
Start parking and charging at 0
Start driving at 5
Start parking and charging at 7
Start driving at 12
Start parking and charging at 14

如果我們不想等充電結束,而是想中斷充電過程並開始駕駛,可以使用 SimPy 的 interrupt() 方法來中斷正在運行的進程:

>>> def driver(env, car):
...     yield env.timeout(3)
...     car.action.interrupt()

由於原來的充電過程被中斷會報異常,因此我們要對異常處理下:

...     		try:
...                 yield self.env.process(self.charge(charge_duration))
...             except simpy.Interrupt:
...                 # When we received an interrupt, we stop charging and
...                 # switch to the "driving" state
...                 print('Was interrupted. Hope, the battery is full enough ...')

再次運行:

>>> env = simpy.Environment()
>>> car = Car(env)
>>> env.process(driver(env, car))
<Process(driver) object at 0x...>
>>> env.run(until=15)
Start parking and charging at 0
Was interrupted. Hope, the battery is full enough ...
Start driving at 3
Start parking and charging at 5
Start driving at 10
Start parking and charging at 12

2.5 共享資源

SimPy 提供三種類型的資源,用於解決建模中多個進行希望使用有限資源的問題(例如:加油站汽車場景中的燃油泵)或典型的生產者-消費者問題。

我們還用汽車充電的例子:汽車開到充電樁旁 a battery charging station (BCS),向兩個充電樁申請使用其一進行充電,如果兩個樁都在被使用,它將會等待直到可用,然後開始充電,然後開走。

>>> def car(env, name, bcs, driving_time, charge_duration):
...     # Simulate driving to the BCS
...     yield env.timeout(driving_time)
...
...     # Request one of its charging spots
...     print('%s arriving at %d' % (name, env.now))
...     with bcs.request() as req:
...         yield req
...
...         # Charge the battery
...         print('%s starting to charge at %s' % (name, env.now))
...         yield env.timeout(charge_duration)
...         print('%s leaving the bcs at %s' % (name, env.now))

備註: bcs.request() 將會產生一個事件,該事件會阻塞直到資源可用,一般情況下使用資源後需要調用 release 對資源進行釋放,這裏的 with 語句意味着自動釋放。

我們創建有兩個充電樁的資源:

>>> import simpy
>>> env = simpy.Environment()
>>> bcs = simpy.Resource(env, capacity=2)

然後我們創建 4 輛車:

>>> for i in range(4):
...     env.process(car(env, 'Car %d' % i, bcs, i*2, 5))

最後,我們可以開始模擬了。由於汽車進程在此模擬中都自行終止,因此我們無需指定直到時間——當沒有更多事件時,模擬將自動停止:

>>> env.run()
Car 0 arriving at 0
Car 0 starting to charge at 0
Car 1 arriving at 2
Car 1 starting to charge at 2
Car 2 arriving at 4
Car 0 leaving the bcs at 5
Car 2 starting to charge at 5
Car 3 arriving at 6
Car 1 leaving the bcs at 7
Car 3 starting to charge at 7
Car 2 leaving the bcs at 10
Car 3 leaving the bcs at 12

注意到前兩輛車到達BCS後可以立即開始充電,而2號車和3號車需要等待,符合預期。


三、後續

之後我將用 SimPy 模擬射頻節點的數據收發,進一步做一個 MESH 通信的模擬程序,用於驗證不同的算法對 MESH 網絡帶來的性能差異。

敬請期待!!!

注:BLUETOOTH MESH 是利用藍牙廣播鏈路,基於洪範算法做的一種簡單的組網協議(這裏可以將廣播理解爲喊話、UDP廣播等)


參考鏈接

[1]. SimPy 主頁
[2]. 百度百科離散事件動態系統
[3]. List of discrete event simulation software
[4]. 離散事件系統仿真(原書第5版)



: 如果覺得不錯,幫忙點個支持哈~

附錄二

AnyLogic:

Arena:

FlexSim:

GoldSim:

Plant Simulation:

ProModel AutoCAD® Edition:

VisualSim:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章