simpylogo SimPy Manual

Author: Tony Vignaux <Vignaux@users.sourceforge.net>
Author: Klaus Muller <Muller@users.sourceforge.net>
SimPy version: 1.6
Date: 2005-Jun-5 gav
Web-site: http://simpy.sourceforge.net/
Python-Version: 2.2, 2.3, 2.4

Contents

SimPy is an efficient, process-based, open-source simulation language using Python as a base. The facilities it offers are Processes, Resources, and Monitors.

SimPy是利用 Python 為基礎的有效率的,process-based, 開放源的模擬程式語言,他提供下列功能:rocessesResources, 和 Monitors

This document describes version 1.6 of SimPy.這文件介紹 1.6 版本的 SimPy

使用 SimPy 1.5-x 版本注意事項

版本之前 SimPy 提供的 API 未改變,而且程式 如同以前可以正常運作。

SimPy 1.6已增加如功能允許等待服 務模 型過程實體獲取資 源 (reneging) 之 前離開。API提供像這 樣子複 合式的混 合物yield即:

介 紹

SimPy Python 之中離 散事 件的模擬系統,它 使用平行過程的方式模 型化進行中諸 如消息、顧客、卡 車、飛機它提供使用者若 干功能包 括 過程 (Processes)資源 (Resources)和各種記 錄 監控 (Monitor) 結果的方法

過程 Processes . SimPy 模擬命令稿語法的主要部分。一個過程可以用來模型化進行中的事件 ( 例如:卡車,顧客,信息等等 ),例如:為了少數資源而等候服務,固定或者任意次數的工作, 以及和其他過程和部分的互動行為。

.一 個SimPy 命令稿包括1個或數個過程 類別宣告並且實體化過程物件, ( 也就是宣告產生過程的物件),每個物件就這樣被啟動,並且執行描述行動的過 程實行方法 Process Execution Method (稍後簡稱為PEM ) ,數個物件則會以並行的方式執行。

信息 Resources 用來模型化過程物件等待服務的聚集的位置,例如:一個 “信息” 可以用來等待有限通訊連結中的一個。這些會被為模型化數個單位通訊 “信息” 。“信息” 將會自動等待過程物件,直到被提供有一個單位給它。 一個過程物件會持續保有它的資源直到服務完成,再釋放資源給其他物件使用。

監視器 Monitors 用來記錄變數,例如等待的時間以及等待行列的長度。一個 ”監視器” 稍後可以用來提供輸出時的統計資料。這些統計資料包含簡單的平均值, 變異數而時間加權平均值和變異數或者用來畫長條圖。

Before attempting to use使用SimPy 之前,你應該會使用Python。 特別的你應該會使用或者定義類別物件。Python 是自由軟體同時提供給許多不同機器類型使用, 將不在這裏介紹,你可以從 Python 的官方網站查到許多資料以及下載軟體: http://www.python.org/

SimPy需要使用 Python 2.2 或者更新版,注意如果使用Python 2.2 ,下列必須擺在 SimPy 命令稿的最上端: from __future__ import generators. 下面的例子都不包含此列。

使用 SimPy模擬

所有離散型事件的模擬程式會自動使用軟體內的計時器紀錄模擬的時間。 在 SimPy 之中可以用 now() 來存取時間。這可以被使用於控制模擬和製作操作時的工作記錄。模擬開始的軟體時間被設定為  0.0。 

當模擬開始的時候,時間就隨著事件前進。當模擬系統改變,就會產生一個新事件。比如說,一個顧客到達就是一個事件,當然離開也是。

為了要使用 SimPy 的事件目標機制,必須事先載入  Simulation 模組:

from SimPy.Simulation import *

在所有使用 SimPy 開始模擬之前,例如定義過程、資源等等,在命令稿之前必須先加入下列陳述: 

initialize()

然後會有其他 SimPy 陳述產生並且啟動物件。當下列陳述出現時候,時間機制則開始啟動: 

simulate(until=endtime)

並開始模擬,計時器程式則尋找第一個既定的事件。執行完這個事件之後,計時器程式繼續找下一個事件。模擬會繼續下去直到下列任一個情況 產生: 

  • 沒有其他事件可以執行 (now()== 最後事件的時間 )
  • 模擬時間等於 endtime (now() == endtime)
  • 執行 stopSimulation()  (now() == 呼叫 stopSimulation() 的模擬時間  )

雖然可以使用  simulateuntil 停止模擬,同樣也可以使用下列指定停止模擬:

stopSimulation()

這會立即停止模擬。 

還有其他的陳述可以在停止 simulate 之後繼續執行,這對於結果報告將會非常有用,例如平均延遲時間或者等待服務的長度。.

下面是模擬程式 main 部分的片段程式碼。 Arrival  為過程類別 ( 之前定義過 ), 其中 p 為此類別中的一個物件。 啟動 p 則連帶使得至少一個事件開始執行Process Execution Method ( 這裏稱為 execute).  這1個 simulate(until=1000.0) 除了開始模擬之外,並且立刻跳到第一個計劃中的事件。直到執行完所有的事件或者一直模 擬到 1000.0 的時間,他才會停止。直到模擬結束之後,Report ( 先前定義過 ) 就會顯示執行結果: 

initialize()
p = Arrival()
activate(p,p.execute(),at=0.0)
simulate(until=1000.0)

Report() # report results when the simulation finishes

過 程

The active objects for discrete-event simulation inc使用 SimPy 的離散事件模擬的的啟動物件是從 Process 類別繼承的類別。.

舉例來說,如果我們模擬一個信息系統,我們可以將每一個信息模擬成 "Processes" 。 信息會在電腦網路中傳遞,交換每個節點之間的信息,等待服務之後才離開這個系統。 Message 類別在 Process Execution Method. 描述這些動作。程式執行之後,個別的信息會獨自產生並且直到時間結束才消逝。

定義一個 process

一個 process 即繼承自 Process 普類別的類別,舉例來說,下列就是定義一個新的 Message Process class 的起頭 :

  • class Message(Process):

The user must define a使用者必須定義一個 Process Execution Method (PEM) 或者可能定義一個包含 __init__  method 的其它 PEM:

  • __init__(self,...), 其中 ... 代表 method 的參數,這個函數會初始化 Process 的內含值, setting values for any attributes. The first line of this這個 method 的第一行必須像下列一樣下列呼叫  __init__() 類別: Process.__init__(self,name='a_process')

    並且使用其他的指令初始化物件的屬性,當產生一個新的信息時,會自動呼叫   __init__()

    下面這個例子當中,在 Message 類別的  __init__() ,每一個新的 message 都被賦予一個識別數字, i ,和一個信息長度, len& nbsp;,當作實體的變數:

    def __init__(self,i,len):
    Process.__init__(self,name='Message'+str(i))
    self.i = i
    self.len = len

    假如不想設定任何屬性(不同於 name ), 則可以省略 __init__ 方法。

  • 過程執行方法 process execution method (PEM) 這將描述 process 物件的動作,並且必須包含至少一個 yield 陳述,(稍後介紹),作為 Python 的生成函數。它可以使用參數,常常被叫做 execute() 或者 run() 之類的名稱,但是任何名字都沒有限制。

    當 process 被啟動而且呼叫 simulate(until=...) 之後,執行方法即開始執行。

    在下列同樣 Message 類別的 PEM (go()) 例子中,信息會印出現在的時刻、識別號碼和 'Starting'。經過一段模擬延遲之後 ( yield hold ... 所造成),它會宣布已經 'Arrived':

    def go(self):
    print now(), self.i, 'Starting'
    yield hold,self,100.0
    print now(), self.i, 'Arrived'

開始一個 process

為了要開始操作必須啟動過程物件操作 ( 見 開 始和停止 SimPy 過程 )

過程執行時間

實行方法能夠使用 yield hold 指令來控制執行時間:

  • yield hold,self,t & nbsp;引起物件等候 t 時 間單位 (除非 中 斷 它). 接著進行下一個陳述的操作,在等待的時候物件停止運作。
  • yield passivate,self 不主動停止 process 運作。

下列例子的命令稿中,使用 Customer 類別示範在啟動時 PEM 方法 (buy) 能使用參數。當一個物件產生時,全部的過程能有一個可以被設定的屬性的 name。  在這裡 yield hold 將被執行4次,間隔時間為5.0:

from SimPy.Simulation import *


class Customer(Process):
def buy(self,budget=0):

print 'Here I am at the shops ',self.name
t = 5.0
for i in range(4):
yield hold,self,t
print 'I just bought something ',self.name
budget -= 10.00
print 'All I have left is ', budget,\

' I am going home ',self.name,

initialize()
C = Customer(name='Evelyn')
activate(C,C.buy(budget=100),at=10.0)
simulate(until=100.0)

開始和停止  SimPy 過程

一但產生新的物件,它是  'passive', 也就是沒有事先規劃任何事件,必須 "啟動" 它開始執行 PEM:

  • activate(p,p.PEM(args)[,at=t][,delay=period][,prior=false])& nbsp;啟動 Process 實體,p 和參數 args,的執 行方法 p.PEM ()

    即時啟動是預設動作,否則會運作一個額外的時間調整,at=t,或者delay=period

    正常 prior 是預設為 False, 假如它設定為 True, 這會在指定時間比其它在事件目錄上的其他過程先行啟動。

過程能夠被停止後再重新啟動:

  • yield passivate,self 停止過程本身,進入 'passive' 狀態。
  • reactivate(p,at=t,delay=period,prior=boolean)& nbsp;重新啟動 'passive' 狀態的過程,p, 並成為 'active' 狀態。 可以使用上述 activate 時間調整選項。 過程本身不能夠啟動它自己,假如一定需要這樣,這使用 yield,hold,self,t 代替。
  • self.cancel(p) 刪除全部過程 p 未來的行程中的事件。唯有 'active' 狀態的過程才會被刪除,而過程不得刪除它自己。 假如有需要,使用 yield passivate,self  代替. 注意事項: 這新的命令格式取代原先 SimPy 的 p.cancle()

當 PEM 完成所有陳述之後,過程變成 'terminated' 狀態。假如這個實體仍然被引用的,則它僅僅為資料容器,不然的話,它自動地被銷毀。

一但到 simulate(until=T) 被執行,甚至被啟動的過程也不會開始作用。模擬開始之後會繼續直到時間 T ( 除非它超出事件執行的時間或命令 stopSimulation () 被執行 )

完整的 SimPy 指令稿

在介紹更複雜的過程能力之前,讓我們看參考下面可以執行的可 SimPy 例子。這個例子模擬定時炸彈。其中為了讓炸彈不提前引爆,則加入了 yield hold 控制:

from SimPy.Simulation import *

class Firework(Process):

def execute(self):
print now(), ' firework activated'
yield hold,self, 10.0
for i in range(10):
yield hold,self,1.0
print now(), ' tick'
yield hold,self,10.0
print now(), ' Boom!!'

initialize()
f = Firework()
activate(f,f.execute(),at=0.0)
simulate(until=100)

輸出如以下的結果,因為沒有刻意修飾所以輸出比較不美觀:

0.0 firework activated
11.0 tick
12.0 tick
13.0 tick
14.0 tick
15.0 tick
16.0 tick
17.0 tick
18.0 tick
19.0 tick
20.0 tick
30.0 Boom!!

source 片斷程式碼

source 是 一個非常有用的程式型態,它是一個具有執行方法過程,可以產生事件或者啟動一系列的其他的過程 -- 它是其他的過程的來源。可以使用兩個啟動的過程之間的間隔 ( 指數分配 ) 來模擬隨機到達的數目。

下列有關 source 的例子,是啟動一系列 customers (顧客在) 在 10.0單位時間內到達。一直持續到模擬時間指定的 finishTime. ( 當然為了達到隨機的目的 '' 抵達的顧客數目使用的 yield hold 方法必須使用隨機指數分配的產生數目,而非常數 10.0) 例子假設的 Customer 類別已定義一個 PEM 呼叫 run

class Source(Process):

def execute(self, finish):
while now() < finish:
c = Customer() ## 新顧客
activate(c,c.run()) ## 現在啟動它
print now(), ' customer'
yield hold,self,10.0

initialize()
g = Source()
activate(g,g.execute(33.0),at=0.0) ## source 開始
simulate(until=100)

非同步中斷

活動中的過程能被另外的過程中斷,但是不得中斷它自己本身。這 interrupter 過程會使用下列的陳述中斷 victim 過 程,

  • self.interrupt(victim)

中斷僅僅是個信號. 經過這陳述,interrupter 會繼續它的現在的方法。

victim 必定是其中狀態 - 也就是為它設計了一個事件 (即正在執行的 yield hold.self.t )。 假如 victim 還未啟動 (也就是 passivive 或者已被終止), 則中斷對它沒有任何作用。等候 resource 過程如果是 passive 狀態則不能夠中斷。已獲取 Resource 的過程為啟動狀態所以能夠被中斷。

假如被中斷,立刻從 yield hold 傳回來 victim。 然後會檢查是否有被下列呼叫

  • self.interrupted() 如果已 經被中斷,則傳回 True。 它能夠繼續目前的狀態或者切換到另外一個狀態。要確認它已經收拾好目前的狀態, 諸如釋放任何它們自己擁有的資源。 當 self.interrupted() == True
    • self.interruptCause  為 interrupter 實 體的參考,
    • self.interruptLeft 為在 yield hold 中斷的剩餘時間,

victim 呼 叫下一次 yield hold 會重新設定,也可以用以下設定

  • self.interruptReset()

以下為一個具有中斷的模擬程式,bus 的 breakdown 即為中斷,同時注意在第一個 yield hold 出現的時候,中斷就有可能產生,所以使用測試 self.interrupted 作為對於這種中斷的回應 (=repair)。 Bus 過程不需用 __init__ 函式:

from SimPy.Simulation import *

class Bus(Process):

def operate(self,repairduration,triplength): # process execution method (PEM)
tripleft = triplength
while tripleft > 0:
yield hold,self,tripleft # try to get through trip
if self.interrupted():
print self.interruptCause.name, "at %s" %now() # breakdown
tripleft=self.interruptLeft # yes; time to drive
self.interruptReset() # end interrupt state
reactivate(br,delay=repairduration) # delay any breakdowns
yield hold,self,repairduration
print "Bus repaired at %s" %now()
else:
break # no breakdown, bus arrived
print "Bus has arrived at %s" %now()

class Breakdown(Process):
def __init__(self,myBus):
Process.__init__(self,name="Breakdown "+myBus.name)
self.bus=myBus

def breakBus(self,interval): # process execution method
while True:
yield hold,self,interval
if self.bus.terminated(): break
self.interrupt(self.bus)

initialize()
b=Bus("Bus")
activate(b,b.operate(repairduration=20,triplength=1000))
br=Breakdown(b) # breakdown to bus b
activate(br,br.breakBus(300))
print simulate(until=4000)

這個例子的輸出如下:

Breakdown Bus at 300
Bus repaired at 320
Breakdown Bus at 620
Bus repaired at 640
Breakdown Bus at 940
Bus repaired at 960
Bus has arrived at 1060
SimPy: No more events at time 1260

中斷發生的時候,既然過程有可能是中斷的對象,所以在每一個 yield hold 之後,必須檢驗是否有中斷產生並且立即作出反應。如果這個過程發生中斷時,還擁有 resource ,則它會繼續擁有這些 resource。

進階 同步化/排程 功能

(自從 SimPy 1.5 之後)到目前為止,所討論的排程架構,要不是與時間為基礎,即經過一段時間才產生過程,就是直接啟動過程。對於大部份的模型而言,這些架構已經足過應用。

對於某些模型,SimPy 所提供的排程過於多樣化,所以可以用更簡單更安全的架構取代。在 SimPy 1.5 的時候,已經引進的事件訊號的同步方法的架構。

另外,也有模型以時間等待額外訊號的條件作為同步排程,SimPy 也引進 "wait until" 的方式作為這些模型的實作。

過程之間的訊號

事件訊號對於必須等待任務完成的過程非常有用,這些狀況經常碰到,例如:即時系統及作業系統。

SimPy 利用執行 SimEvent 類別產生事件,之所以如此命名,是因為 Event 已經為 Python 使用,如 tkinter events 和 Python 標準函式庫中的 signal 模組 -- Set handlers for asynchronous events.

一個 SimEvent 實體是利用像下列 myEvent=SimEvent ("MyEvent") 所產生的,和 SimEvent 有關的是

  • 布耳值的 occurred ,用來出示是否事件已發生 (已被發信號)
  • list 型態的 waits, 執行等候事件產生的全部過程
  • list 型態的 queues 執行等待事件服務的先進先出等待過程
  • 屬性 signalparam& nbsp;從訊號函式接收一個 ( 隨意的 ) 有效負載

過程能 wait 到 宣告的事件發生:

yield waitevent,self,<events part>

<events part> 可以為:

  • 事件變數,例如 myEvent
  • tuple 型態的的事件,例如 (myEvent,myOtherEvent,TimeOut)
  • list 型態的事件,例如  [myEvent,myOtherEvent,TimeOut]

假如在 <events part> 的事件,已經已發生,過程則持續進行。事件的 occurred 標誌變為 False。

假如在 <events part> 事件沒有發生,過程則加入等待事件的過程之中,並呈現 passivated 狀態。

過程能 queue 到宣告的事件發生:

yield queueevent,self,<events part> ( 如上述定義的 <events part> )

假如在 <events part> 的事件,已經已發生,過程則持續進行。事件的 occurred 標誌變為 False。

假如在 <events part> 事件沒有發生,過程則加入先進先出陣列等待的過程之中等待事件發生,並呈現 passivated 狀態。

發生的事件以下列方式的發出信號:

<event>.signal(<payload parameter>)

這 <payload parameter> 是隨意的. 它可以為任何 Python 類型。 它能被屬性為 signalparam 的 SimEvent 引發信號的過程所讀取,例如 message = MySignal.signalparam.

當這樣的事情發生時,如果都沒有任何 wait 和 queue,signal 會讓事件的 occurred 標誌改變為 True。不然的話,在事件 waits 列表之中的過程即刻被啟動,如同 FIFO 排列的第一個過程。

這裡為一個描述新架構的完整 SimPy 命令稿 : 

from __future__ import generators
from SimPy.Simulation import *

class Waiter(Process):
def waiting(self,myEvent):
yield waitevent,self,myEvent
print "%s: after waiting, event %s has happened"%(now(),myEvent.name)

class Queuer(Process):
def queueing(self,myEvent):
yield queueevent,self,myEvent
print "%s: after queueing, event %s has happened"%(now(),myEvent.name)
print " just checking: event(s) %s fired"%([x.name for x in self.eventsFired])

class Signaller(Process):
def sendSignals(self):
yield hold,self,1
event1.signal()
yield hold,self,1
event2.signal()
yield hold,self,1
event1.signal()
event2.signal()

initialize()
event1=SimEvent("event1"); event2=SimEvent("event2")
s=Signaller(); activate(s,s.sendSignals())
w0=Waiter(); activate(w0,w0.waiting(event1))
w1=Waiter(); activate(w1,w1.waiting(event1))
w2=Waiter(); activate(w2,w2.waiting(event2))
q1=Queuer(); activate(q1,q1.queueing(event1))
q2=Queuer(); activate(q2,q2.queueing(event1))
simulate(until=10)

執行的結果如下:

1: after waiting, event event1 has happened
1: after waiting, event event1 has happened
1: after queueing, event event1 has happened
just checking: event(s) ['event1'] fired
2: after waiting, event event2 has happened
3: after queueing, event event1 has happened
just checking: event(s) ['event1'] fired

兩個過程 ( w0w1) 都在等候 event1,因此當它在時間 1 開始時,兩者立刻啟動。 q1q2 兩個過程也在等待它啟動的行列中,但是只有一個會被啟動。當 event1 再度啟動時,等候行列中的第兩者才會被啟動。 just checking' 這一行會反應過程的屬性 self.eventsFired 的內容。

"wait until" 同步性 -- 等候任何狀況

利用 SimPy 1.5 之前的版本,模擬依賴具有為非-時間-相關狀態變數的條件 (諸如 "goodWeather OR (nrCustomers> 50 AND price<22.50")) 的過程,是不太容易的。當其他的 SimPy 同步性建構是 絕對必要 的時候,就需要 interrogative (檢視) 排程:在每一個 SimPy 事件產生之後,必須持續測試一直到他們成為 True。新的 (隱藏,系統) 過程必須有效地檢視各種狀況內容。
很明顯的,比起其他 SimPy 架構所使用的事件表列式的排程更沒有執行效力。
因此 SimPy 1.5.1 就採用當有1個過程是在等待狀況發生時,才啟動檢視過程。
如果不是這種狀況,執行過頭的的代價就會降為最低 (大約只需要 1% 額外的執行時間)。

新的架構使用下列形式:

yield waituntil, self, <cond>

<cond> 是沒有參數的函式的參考值,它會傳回代表必須等待的條件狀態的布林值。

這裡是一個使用 yield waituntil 架構,簡單的程式:

from SimPy.Simulation import *
import random
class Player(Process):
def __init__(self,lives=1):
Process.__init__(self)
self.lives=lives
self.damage=0
def life(self):
self.message="I survived alien attack!"
def killed():
return self.damage>5
while True:
yield waituntil,self,killed
self.lives-=1; self.damage=0
if self.lives==0:
self.message= "I was wiped out by alien at time %s!"%now()
stopSimulation()
class Alien(Process):
def fight(self):
while True:
if random.randint(0,10)<2: #simulate firing
target.damage+=1 #hit target
yield hold,self,1

initialize()
gameOver=100
target=Player(lives=3); activate(target,target.life())
shooter=Alien(); activate(shooter,shooter.fight())
simulate(until=gameOver)
print target.message

扼要來說,這"wait until " 架構是最強大的同步性架構,它有效地擴展其他的 SimPy 同步性架構,即它可以替換他們全部 ( 但是必須付出執行時間浪費的代價).

資 源

一個 資源 模型就是在位於等待處理的聚集點,譬如,在工廠內, 工 作 (被模擬為 過程) 必須在機器的地方工作做在 機械 (被模擬為 資源 ). 假如沒有提供 機械 單位, 工作 會等待到有一個機器空出來。然後 工 作 會一直使用到工作結束。在其他 作工 釋出資源之前,都不可以使用。這些工作完全由 SimPy Resource 自動處理。

資源能有若干相同的單位, 因此可以擁有若干相同的 機械 單位. 過程會向資源1個單位尋求服務,完成的時候就 釋 放 它。 資源會處理一系列的等待過程和另外正在使用它的過程名單。這些資料都會自動被定義和並且更新。

定義資源

利用下列的陳述可以建立資源:

  • r=Resource(capacity=1, name='a_resource', unitName='units', qType=FIFO, preemptable=0, monitored=False)
    • capacity 資源所提供同一單位的數目。
    • name 資源的名字 (例如 gasStation )。
    • unitName 資源單位的名稱 ( 例如 馬達 )。
    • qType 描寫等待行列的過程的如何等待規則;通常預設為 FIFO (先入先出) ,另外一個選擇是 PriorityQ
    • preemptable, 假如它是非零,就代表被設定為 PriorityQ 狀態的過程,會佔用已經使用資源的某一個單位但是優先順序較低的過程,這僅僅當 qType == PriorityQ 時候才有作用。
    • monitored 代表假如 waitQ 的大小和 activeQ 等待的行列 (見下文) 是被監控。 (參考底下的 監控 )

資源,r, 有下列的屬性:

  • r.n 目前未使用的單位數目。
  • r.waitQ 過程的等待行列 (或者清單) (預設值為先入先出), len (r.waitQ) 是隨時仍待在等待行列之中的數目。
  • r.activeQ 擁有單位的過程的行列 (或者清單) ,len (r.activeQ) 是隨時仍待在啟動中等待行列的數目。
  • r.waitMon 自動地記錄在 r.waitQ 行動的& amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; nbsp;監控& nbsp;。
  • r.actMon 自動地記錄在 r.activeQ 行動的& amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; amp; nbsp;監控& nbsp;。

簡單的資源請求

過程能夠在 PEM 使用 yield 指令,向資源,r,發出服務請求,使用完畢之後再 釋回:

  • yield request, self, r 請求一個單位的資源,r, 直到被提供為止,過程都會暫時在等待行列之中。

    假如或者當某一個單位是被空下來的,要求服務的過程會立刻獲得它並且繼續它的實行。資源會記錄過程是使用一個單位 (也就是過程會被列在 r.activeQ 之中)

    假如沒有任何單位是被空下來的,過程會自動地放置於資源的等待行列中,r.waitQ,並且暫停工作。隨後如果有 某一個單位被空下來的時候,考量優先次序,第一個在等待行列之中的過程,會被允許使用它,過程然後被恢復活動。

    假如資源的 priorityQ 被設定為 preemption==1, 這請求會讓過程去佔用已經在使用資源的優先順序較低的過程。 ( 參考下面 具 有優先使用權的資源請求 )

  • yield release,self,r 釋放 r 單位的,分派 釋放單位到在資源的等待行列的下一個過程可能會有副作用。

    在這例子,目前的過程提出請求,並且如有必要的話會等候資源,r,的其中的一個單位。一旦獲得後,它會保 存一個隨機時間 (來自平均值為 20.0 的指數公布) 然後再釋放它:

    yield request,self,r
    yield hold,self,expovariate(1.0/20.0)
    yield release,self,r

具有優先權的資源請求

r 被 定義為具有優先權的資源時( 也就是 qType==PriorityQ ),則如下提出服務請求:

  • yield request,self,r,priority 具有優先權的服務請求,其中 priority 可以為實數或者整數,越大的數就代表優先權也高,如果資源不足,則他們會被列為 r.waitQ 的前面。

下面為使用優先權的例子,4 個優先權不同的 clients 同時向資源主機要求服務時,則依照他們的優先順序,先後獲得資源服務:

from SimPy.Simulation import *
class Client(Process):
inClients=[]
outClients=[]

def __init__(self,name):
Process.__init__(self,name)

def getserved(self,servtime,priority,myServer):
Client.inClients.append(self.name)
print self.name, 'requests 1 unit at t=',now()
yield request, self, myServer, priority
yield hold, self, servtime
yield release, self,myServer
print self.name,'done at t=',now()
Client.outClients.append(self.name)

initialize()
server=Resource(capacity=1,qType=PriorityQ)
c1=Client(name='c1') ; c2=Client(name='c2')
c3=Client(name='c3') ; c4=Client(name='c4')
activate(c1,c1.getserved(servtime=100,priority=1,myServer=server))
activate(c2,c2.getserved(servtime=100,priority=2,myServer=server))
activate(c3,c3.getserved(servtime=100,priority=3,myServer=server))
activate(c4,c4.getserved(servtime=100,priority=4,myServer=server))
simulate(until=500)
print 'Request order: ',Client.inClients
print 'Service order: ',Client.outClients

輸出如下:

c1 requests 1 unit at t= 0
c2 requests 1 unit at t= 0
c3 requests 1 unit at t= 0
c4 requests 1 unit at t= 0
c1 done at t= 100
c4 done at t= 200
c3 done at t= 300
c2 done at t= 400
Request order: ['c1', 'c2', 'c3', 'c4']
Service order: ['c1', 'c4', 'c3', 'c2']

雖然 c1 的優先權最低,但是他一樣會獲得的資源,當他完成服務時,c4 因為優先權較高,使得他先獲得資源服務,以此類推,這個例子沒有搶佔性的過程。

具有強佔優先權 的資源請求

某些模型中,當所有資源都在使用中時優先譴較高的過程,會強占優先權較低者的資源可以使用 qType==PriorityQ 和非 0 的 preemtable 定義1個具有強佔性的資源。

當1個過程要求資源時,縱然所有可用的單位,已經都在使用中,他當然可以搶佔由先前較低的過程所用的資源,假設有數個資源被啟動 ( 即 activeQ ),優先權較低的過程,會立刻停止,並列為 waitQ 的最前端,而強占其資源的過程則變為 activeQ, 數位另有其他資源搶佔行為發生,否則被搶走資源的過程將是下1個獲得資源的過程。一旦進入 activeQ 被強占資源過程的服務時間,立刻繼續計時,無論是否有被搶佔資源,過程和的資源服務的時間永遠不變。

下面為優先權不同的 clients 互搶資源的例子:

from SimPy.Simulation import *
class Client(Process):
def __init__(self,name):
Process.__init__(self,name)

def getserved(self,servtime,priority,myServer):
print self.name, 'requests 1 unit at t=',now()
yield request, self, myServer, priority
yield hold, self, servtime
yield release, self,myServer
print self.name,'done at t=',now()

initialize()
server=Resource(capacity=1,qType=PriorityQ,preemptable=1)
c1=Client(name='c1')
c2=Client(name='c2')
activate(c1,c1.getserved(servtime=100,priority=1,myServer=server),at=0)
activate(c2,c2.getserved(servtime=100,priority=9,myServer=server),at=50)
simulate(until=500)

輸入結果如下:

c1 requests 1 unit at t= 0
c2 requests 1 unit at t= 50
c2 done at t= 150
c1 done at t= 200

c2t=50 搶占 c1 的資源,此時 c1 已經使用了 50 單位時間,當 c2t=150 完成資源服務, c1 繼續使用剩下的 50 單位時間。

取 消 -- 獲得資源前離開等待行列

真實世界中,過程未必一直等待獲得資源服務,有可能在等待某一個時段之後或者某一事件發生就取消 (renege)  等待服務。

SimPy 提供了 yield request 陳述的取消動作。

yield (request,self,resource[,priority]),(<reneging clause>).

具有取消的 SimPy 模型架構如:

yield (request,self,resource),(<reneging clause>)
if self.acquired(resource):
## process got resource and did not renege
. . . .
yield release,self,resource
else:
## process reneged before acquiring resource
. . . . .

過程類別已經加入函式 acquired(resource), 在複合式的 yield request 陳述之後可以呼叫(self. acquired(resource))。那不僅僅是指出是不是這過程有獲得資源,但是它也會將從資源的 waitQ 名單中,去掉取消的過程。

取消的方式有兩種,1個等待某一時間才取消和另外1個為當某件事發生時才取消。

超過時限後的取消

為了能夠讓過程在超過等待時間才取消,使用 "yield hold" 的陳述它作為取消的語法,即
 hold,self,waittime:

  • yield (request,self,res,[,priority]),(hold,self,waittime) 過程程式自己 self 利用其相對的優先權 ( 如果有設定的的話) 要求1個資源 res 的單位,如果資源還沒有提供任何單位時,將會限制過程持續運作。如果超過等待的時間 waittime,過程將會離開等待行列並 且繼續它的運作。

例子的片段內容如下:

## Queuing for a parking space in a parking lot
. . . .
parking_lot=Resource(capacity=10)
patience=5 # time units
park_time=60 # time units
. . . .
# wait no longer than 'patience' time units for a parking space
yield (request,self,parking_lot),(hold,self,patience)
if self.acquired(parking_lot):
# park the car
yield hold,self,park_time
yield release,self,parking_lot
else:
# give up
print "I have had enough, I am going home"

當某一 個事件發生時才取消

為了能夠讓過程在某一個事件發生時才取消,使用 "yield waitevent" 的陳述它作為取消的語法,即 waitevent,self,events:

  • yield (request,self,res[,priority]),(waitevent,self,events) 過程程式自己 self 利 用其相對的優先權 ( 如果有設定 priority 的話) 要求1個資源 res 的單位, (events 可以為單一事件,或者是 list ( 或 tuple) 的 SimEvents。如果資源還沒有提供任何單位時,將會限制過程持續運作。如果 events 名單上面的任何1個事件發出信號,過程將會離開等待行列並且繼續它的運作。

例子的片段內容如下:

## Queuing for movie tickets
. . . .
tickets=Resource(capacity=100)
sold_out=SimEvent() # signals 'out of tickets'
too_late=SimEvent() # signals 'too late for this show'
. . . .
# Leave the ticket counter queue when movie sold out or its too late for show
yield (request,self,tickets),(waitevent,self,[sold_out,too_late])
if self.acquired(tickets):
# watch the movie
yield hold,self,120
yield release,self,tickets
else:
# did not get a ticket
print "Who needs to see this silly movie anyhow?"

監控資源

當資源 r 的參數 monitored 設為 True ,等待行列的長度 len(r.waitQ) 被啟動行列的長度 len(r.activeQ) 同時會被監控 (參考以下 監控 )。特別不能夠由資源外部監控等待行列時,極為有用。這些監控分別被命名為 r.waitMonr.actMon。兩者隨著時間變化的長度 都被記錄,因此可以計算相關之統計量,例如時間的平均值。

這個例子中,不但監控資源 server, 同時也計算時間的平均值:

from SimPy.Simulation import *
class Client(Process):
inClients=[]
outClients=[]

def __init__(self,name):
Process.__init__(self,name)

def getserved(self,servtime,myServer):
print self.name, 'requests 1 unit at t=',now()
yield request, self, myServer
yield hold, self, servtime
yield release, self,myServer
print self.name,'done at t=',now()

initialize()
server=Resource(capacity=1,monitored=True)
c1=Client(name='c1') ; c2=Client(name='c2')
c3=Client(name='c3') ; c4=Client(name='c4')
activate(c1,c1.getserved(servtime=100,myServer=server))
activate(c2,c2.getserved(servtime=100,myServer=server))
activate(c3,c3.getserved(servtime=100,myServer=server))
activate(c4,c4.getserved(servtime=100,myServer=server))
simulate(until=500)
print 'Average waiting',server.waitMon.timeAverage()
print 'Average in service',server.actMon.timeAverage()

輸出結果如下:

c1 requests 1 unit at t= 0
c2 requests 1 unit at t= 0
c3 requests 1 unit at t= 0
c4 requests 1 unit at t= 0
c1 done at t= 100
c2 done at t= 200
c3 done at t= 300
c4 done at t= 400
Average waiting 1.5
Average in service 1.0

產生亂數

模擬通常需要隨機變數,SimPy 使用標準的 Python random 模組來產生。詳見其相關文件。

可以用兩種方式使用它們:直接 import 函式或者 import Random 類別然後製造自己所需要的隨機物件。這將可以使我們在 Simscript 和 ModSim 可以使用許多不同的亂數,每個物件都提供獨特的隨機數列。

首先在這裏介紹簡單的函式,將會在所有的呼叫中使用單一的隨機數列。

你必須從 random 模組中載入需要的函式,例如:

模組提供多種公布,例如:

下列使用指數公布和常態公布的隨機變數, random 物件 g 的 初始值種子為 33355,使用這個物件 g 製造上述兩個公布的隨機變數 XY 的值:

from random import expovariate, normalvariate

X = expovariate(10.0)
Y = normalvariate(100.0, 5.0)

Monitors 監控

Monitor 為記錄一系列伴隨時間,t, 的觀察值,y, 的 list 子類別,我這些資料可以計算平均值,每一個 Monitor 只能夠觀察一系列的資料,譬如可以使用1個 Monitor 記錄顧客等待的平均時間,而使用另外1個記錄店裏面的顧客人數。另外 SimPy 離散型的事件系統,唯有當觀察事件發生,顧客的數目才會跟著改變。

為了觀察資料,必須使用 observe 的方法。

Monitors 並非要取代統計分析,但是事實顯示他們對 SimPy 的模擬非常有用。

Monitors 包含在 SimPy 的 Simulation 模組 ( 在 SimPy 1.3 之前的版本,Monitors 和 Simulation 分開的模組,使用前必須分別載入。當載入 SimPy.Simulation 之後,在載入 SimPy.Monitor 程式依然有用。)

定義 Monitor

定義新的 Monitor 的物件:

  • m=Monitor(name='a_Monitor', ylab='y', tlab='t'), 其中 name 為 Monitor 物件的名稱,ylabtlab 則是使用 SimPlot 套件製作 Monitor 之中的資料圖形所賦予的座標軸的名稱。

資 料觀察

Monitor 物件,m, 以成對表列保存觀察資料,每一對 [t,y] 為時間和觀察值的表列。 Monitor 使用 observe 函式觀察資料,同時資料可以從重新設定 reset

  • m.observe(y [,t]) 記錄此刻的數值,y 和時間 t (如果缺少 t,使用 now() ).
  • m.reset([t]) 重新設定觀察, 已記錄的時間數列預設為空的表列,[], 如果沒有模擬的時間,t 使用 now()

資 料摘要

簡單的資料摘要能從 monitor 的物件 m 取得:

  • m[i] 保存表列中第 i 筆觀察值 [ti,yi]
  • m.yseries() 一系列記錄數據值
  • m.tseries() 一列記錄的時間
  • m.total() y 總合
  • m.count() 目前觀察的數量,和 len (m)相同的。
Standard mean value
  • m.mean() 忽略取得的資料時間下,y 的平均數,也就是 m.total()/m.count()。 

    假如沒有任何觀察值,則會顯示 'SimPy: No observations for mean'.

  • m.var() 忽略取得的資料時間下,y 的變異數,如果要獲得變異數的估計量必須再乘上 n/(n-1)n=m.count()。標準差是這個數的 平方根。

    如沒有任何觀察值,則會顯示: 'SimPy: No observations for sample variance' .

Time Average
  • m.timeAverage([t]) 從時間 0 (或者 m.reset([t])) 到時間 t (如果缺少 t, 使用 now() ), 如圖形顯示,為所有的面積量除以全部觀察時間, y 被假設到是連續的,但是呼叫 observe(y) 時就會改變。

    假如沒有觀察資料,則顯示 'SimPy: 沒有觀察為了timeAverage '.  'SimPy: No observations for timeAverage'. 假如還未執行,則顯示 'SimPy: No elapsed time for timeAverage' .

Histogram
  • m.histogram(low=0.0,high=100.0,nbins=10) 是條狀圖 histogram 物件 (從 list 延伸出來的) 包含每一個條狀的 y 數值,它是從監控 y 的數值計算出來的。可以使用 SimPlot 套件的 plotHistogram 方法製作 histogram 的圖形。
    • low 是最低的條狀值
    • high 是最高的條狀值
    • nbins 是從最低的條狀值到最高的條狀值的分割數目,y 的數值則被放置自己的區域上, 另外會多兩條條狀,分別為 lowyhigh 的數值,一共有 nbins+2 個。
  • m.__str__() 是簡單描寫 monitor 現在狀態, 這能被用來輸出陳述。

Note: 為了之後一致性,保留下列的方法,但是不推薦使用他們,將來有可能從. SimPy 被移除。

  • m.tally(y)記錄現時的 y 值和這個目前時間,now(). (千萬不要使用)   
  • m.accum(y [,t]) 記錄現時的 y 值和時間, t (如果缺少 t ,則為 now()), (千萬不要使用)

在這例子我們建立1個 Monitor ,估計從指數分布的 1000 筆資料的平均值和變異數變量。並製作數目 30 的條狀圖 (同時加上 underover 兩個數值):

from SimPy.Simulation import *
from random import expovariate

M = Monitor()

for i in range(1000):
y = expovariate(0.1)
M.observe(y)

print 'mean= ',M.mean(), 'var= ',M.var()
h = M.histogram(low=0.0, high=20, nbins=30)

在下列的例子,監控系統的數字,N, 來估計在系統之中的平均數 (這例子僅僅是片斷的程式碼):

M = Monitor()

... # upon an arrival of a job, increment N
# the time used is now()
N = N +1
M.observe(N)
...

... # upon a departure of a job
N = N -1
M.observe(N)

print 'mean= ',M.timeAverage()

感 謝

任何能夠改進這份文件的更正或建議,我們都會很感激。

附 錄

A1. SimPy 錯誤信息

建議性信息

如同 message=simulate(until=123) 所表示,這些信息將會被 simulate() 傳回來。

模擬如果正常結束, simulate() 傳回下列信息: 

  • SimPy: Normal exit. 這意味沒有任何錯誤發生失誤,並如同指定的 until 參數,執行模擬。

simulate() 傳回下列的信息,表示可以提早終止模擬,但是允許程式繼續執行。

  • SimPy: No more events at time x. 全部的過程在 message=simulate(endtime) 指定的 endtime 終止時間之前就已經全部完成。
  • SimPy: No activities scheduled. 當呼叫 simulate() 時,沒有啟動任何過程。

嚴重錯誤訊息

這些訊息被有關的 SimPy 嚴重例外所生成的時,他們會中止 SimPy 程式,嚴重的SimPy錯誤會傳到 sysout

  • Fatal SimPy error: activating function which is not a generator (contains no 'yield'). 過程嘗試去啟動( 或者再啟動 ) 的函式並非是 SimPy 過程 Python generator), SimPy 過程必須包含至一個 yield ... 陳述。
  • Fatal SimPy error: Simulation not initialized. SimPy程式呼叫 simulate() 之 前,必須先呼叫initialize()

監控錯誤訊息

  • SimPy: No observations for mean. 計算平均數之前,監控並沒有產生任何觀察值。

  • SimPy: No observations for sample variance. 計算樣本變異數之前,監控並沒有產生任何觀察值。

  • SimPy: No observations for timeAverage, 計算時間平均之前,監控並沒有產生任何觀察值。
  • SimPy: No elapsed time for timeAverage. 計算時間平均之前,模擬還未開始。

A2. SimPy過程狀況

任何時間,從模型製造者的觀點來看,SimPy過程,p, 可以是下列狀況之一個:

  • Active: 等候預定的事件,這情形模擬模型的活動狀況。時間在這狀態之下持續進行,過程狀態 p.active() 傳回 True
  • Passive: 還未啟動或者已經被終止。等候被另外的過程 (重新)啟動 ,這情形好比真實的世界還沒有完成的過程,等候被觸發然後繼續。不要更改模擬的時間。p.passive() 傳回 True
  • Terminated: 假如被引用,過程會執行完它所有的操作並且變成資料實體,p.terminated() 傳回 True。

最初(在 Proces 過程實體產生之前),過程傳回 passive

此外,SimPy 過程, p, 可以是下列 (sub)狀態之一:

  • Interrupted: 活動中的者被另外的過程所打斷,它能馬上回應這個打斷,這可以模擬它在確定完成時間之前,中斷執行中的動作,p.interrupted() 傳回 True。A
  • Queuing: 啟動中的操作元已已經向忙碌中的資源請求服務,在獲得資源等待 (passive) 被啟動,p.queuing(a_resource) 傳回 True

A3. SimPlot, Simpy 畫圖功能

SimPlot 提供簡單的方式畫出模擬結果的圖形。

A4. SimGUI,  Simpy 圖形使用介面

SimGUI 提供使用者與SimPy的程式使用介面,改變參數和檢查輸出。

A5. SimulationTrace, SimPy 追蹤功能

SimulationTrace 已發展給用戶洞察SimPy模擬程式執行的動力學。 它能幫助開發者測試和解釋SimPy模型 (e.g. 為了講授目的文件).

A6. SimulationStep, SimPy 事件步驟功能

SimulationStep 能協助除錯模型,利用他們從事件為基礎模型操作之中,所得到的事件輸出, (e.g. 為了畫圖的目的),等等。

它提供:

  • 執行可以在事件發生後由使用者指定執行步驟的模擬模型,
  • 重覆的呼叫,執行定時發生的模擬模型,
  • 在程式控制下開始出發和停止事件步驟方式。

A7. SimulationRT, 即時同步功能

SimulationRT 允許同步模擬時間和即時模擬,這功能能被用來實作例如互動式遊戲或者即時的模型描述。

Created: 2003-April-6