在我遇到 SimPy 包的其中一位創(chuàng)始人 Klaus Miller 時(shí),從他那里知道了這個(gè)包。Miller 博士閱讀過幾篇提出使用 Python 2.2+ 生成器實(shí)現(xiàn)半?yún)f(xié)同例程和“輕便”線程的技術(shù)的 可愛的 Python專欄文章。特別是(使我很高興的是),他發(fā)現(xiàn)在用 Python 實(shí)現(xiàn) Simula-67 樣式模擬時(shí),這些技術(shù)很有用。
結(jié)果表明 Tony Vignaux 和 Chang Chui 以前曾創(chuàng)建了另一個(gè) Python 庫(kù),它在概念上更接近于 Simscript,而且該庫(kù)使用了標(biāo)準(zhǔn)線程技術(shù),而不是我的半?yún)f(xié)同例程技術(shù)。該小組在一起研究時(shí),認(rèn)為基于生成器的樣式更有效得多,并且最近在 SourceForge 上發(fā)起了使用 GPL 的項(xiàng)目,稱為 SimPy(請(qǐng)參閱 參考資料,獲得 SimPy 主頁(yè)的鏈接),目前處于 beta 測(cè)試版狀態(tài)。Vignaux 教授希望他在惠靈頓維多利亞大學(xué)(University of Victoria)的將來大學(xué)教學(xué)中使用統(tǒng)一的 SimPy 包;我相信該庫(kù)也非常適合應(yīng)用到各類實(shí)用問題中。
我承認(rèn)在近期的通信交流和調(diào)查研究之前,我對(duì)編程領(lǐng)域的模擬方面沒有任何基礎(chǔ)知識(shí)。我猜想本專欄文章的大部分讀者也和我一樣,對(duì)這方面的知識(shí)知之甚少。盡管有人會(huì)認(rèn)為這種樣式編程的方式有些新奇,但在理解資源有限的實(shí)際系統(tǒng)的行為時(shí),模擬是很有用的。不管您感興趣的是有限帶寬網(wǎng)絡(luò)、汽車交通行為、市場(chǎng)和商業(yè)性優(yōu)化、生物/進(jìn)化的交互作用還是其它“隨機(jī)”系統(tǒng),SimPy 對(duì)這樣的建模都提供了簡(jiǎn)單的 Python 工具。
隨機(jī)的定義
與“連接”相類似,它是那些 最適合形容其作業(yè)的詞匯之一 - 再也找不到更適合的了:
隨機(jī)(stochastic),源自希臘語 stokhastikos(形容詞)
1)推測(cè)的、與推測(cè)相關(guān)的或者具有推測(cè)特點(diǎn)的;好推測(cè)的。
2)在統(tǒng)計(jì)學(xué)上:涉及或包含一個(gè)隨機(jī)變量或多個(gè)隨機(jī)變量,或涉及偶然性或概率。
來源:Dictionary.com
在本專欄文章中,我將一直使用食品雜貨店內(nèi)具有多條通道的付款區(qū)域這個(gè)相當(dāng)簡(jiǎn)單的示例。通過使用所演示的模擬,我們可以根據(jù)對(duì)掃描器技術(shù)、購(gòu)物者習(xí)慣、人員配備需求等進(jìn)行的各種更改所產(chǎn)生的經(jīng)濟(jì)上和等待時(shí)間上的含義提出問題。這個(gè)建模的優(yōu)點(diǎn)是在您對(duì)所做的更改產(chǎn)生的含義有清晰的想法時(shí),它讓您能提前制定策略。很明顯,大多數(shù)讀者不會(huì)專門經(jīng)營(yíng)一家食品雜貨店,但這些技術(shù)可以廣泛地應(yīng)用于各類系統(tǒng)中。
模擬的概念
SimPy 庫(kù)只提供了三個(gè)抽象/父類,并且它們對(duì)應(yīng)于模擬的三個(gè)基本概念。有許多其它常規(guī)函數(shù)和常量用于控制模擬的運(yùn)行,但重要的概念都與這些類結(jié)合在一起。
模擬中的核心概念是 進(jìn)程。一個(gè)進(jìn)程只是一個(gè)對(duì)象,它完成某些任務(wù),隨后在它準(zhǔn)備完成下一個(gè)任務(wù)之前有時(shí)會(huì)等待一會(huì)兒。在 SimPy 中,您還可以“鈍化”進(jìn)程,這意味著在一個(gè)進(jìn)程完成一個(gè)任務(wù)后,只有當(dāng)其它進(jìn)程要求該進(jìn)程完成其它任務(wù)時(shí),它才會(huì)去做。把進(jìn)程當(dāng)作嘗試完成一個(gè)目標(biāo),常常是很有用的。在編寫進(jìn)程時(shí),通常把它編寫成可以在其中執(zhí)行多個(gè)操作的循環(huán)。在每個(gè)操作之間,可以插入 Python“yield”語句,它讓模擬調(diào)度程序在返回控制之前執(zhí)行每個(gè)等待進(jìn)程的操作。
進(jìn)程執(zhí)行的許多操作取決于 資源的使用。資源只是在可用性方面受到限制。在生物學(xué)模型中,資源可能是食物供應(yīng);在網(wǎng)絡(luò)模型中,資源可以是路由器或有限帶寬通道;在我們的市場(chǎng)模擬中,資源是付款通道。資源執(zhí)行的唯一任務(wù)是在任何給定的時(shí)間內(nèi)將它的使用限于一個(gè)特定的進(jìn)程上。在 SimPy 編程模型下,進(jìn)程單獨(dú)決定它要保留資源的時(shí)間有多長(zhǎng),資源本身是被動(dòng)的。在實(shí)際系統(tǒng)中,SimPy 模型可能適合概念性方案,也可能不適合;很容易想象到資源在本質(zhì)上會(huì)限制其利用率(例如,如果服務(wù)器計(jì)算機(jī)在必需的時(shí)間幀內(nèi)沒有獲得滿意的響應(yīng),則它會(huì)中斷連接)。但作為編程問題,進(jìn)程或資源是否是“主動(dòng)”方就不是特別重要(只要確保您理解了您的意圖)。
最后一個(gè) SimPy 類是 監(jiān)控程序。實(shí)際上監(jiān)控程序不是很重要,只不過它很方便。監(jiān)控程序所做的全部任務(wù)就是記錄向它報(bào)告的事件,并保存有關(guān)這些事件的統(tǒng)計(jì)信息(平均值、計(jì)數(shù)、方差等)。該庫(kù)提供的 Monitor 類對(duì)記錄模擬措施是個(gè)有用的工具,但您也可以通過您想使用的其它任何技術(shù)來記錄事件。事實(shí)上,我的示例使 Monitor 子類化,以提供某些(稍微)增強(qiáng)的能力。
設(shè)置商店:對(duì)模擬編程
在我所撰寫的大部分文章中,我都會(huì)馬上給出樣本應(yīng)用程序,但在本例中,我認(rèn)為帶您經(jīng)歷食品雜貨店應(yīng)用程序的每個(gè)步驟會(huì)更有用。如果您愿意的話,可以把每個(gè)部分剪貼在一起;SimPy 創(chuàng)造者們將在將來的發(fā)行版中包含我的示例。
SimPy 模擬中的第一步是幾個(gè)常規(guī)的導(dǎo)入(import)語句:
清單 1. 導(dǎo)入 SimPy 庫(kù)
#!/usr/bin/env python from __future__ import generators from SimPy import Simulation from SimPy.Simulation import hold, request, release, now from SimPy.Monitor import Monitor import random from math import sqrt
有些 SimPy 附帶的示例使用 import * 樣式,但我更喜歡使我填充的名稱空間更清晰。對(duì)于 Python 2.2(SimPy 所需的最低版本),將需要如指出的那樣,導(dǎo)入生成器特性。對(duì)于 Python 2.3 以后的版本,不需要這樣做。
對(duì)于我的應(yīng)用程序,我定義了幾個(gè)運(yùn)行時(shí)常量,它們描述了在特定的模擬運(yùn)行期間我感興趣的幾個(gè)方案。在我更改方案時(shí),我必須在主腳本內(nèi)編輯這些常量。要是這個(gè)應(yīng)用程序的內(nèi)容更充實(shí),那么我就可能用命令行選項(xiàng)、環(huán)境變量或配置文件來配置這些參數(shù)。但就目前而言,這個(gè)樣式已經(jīng)足夠了:
清單 2. 配置模擬參數(shù)
AISLES = 5 # Number of open aisles ITEMTIME = 0.1 # Time to ring up one item AVGITEMS = 20 # Average number of items purchased CLOSING = 60*12 # Minutes from store open to store close AVGCUST = 1500 # Average number of daily customers RUNS = 10 # Number of times to run the simulation
我們的模擬需要完成的主要任務(wù)是定義一個(gè)或多個(gè)進(jìn)程。對(duì)于模擬食品雜貨店,我們感興趣的進(jìn)程是在通道處付款的顧客。
清單 3. 定義顧客的操作
class Customer(Simulation.Process): def __init__(self): Simulation.Process.__init__(self) # Randomly pick how many items this customer is buying self.items = 1 + int(random.expovariate(1.0/AVGITEMS)) def checkout(self): start = now() # Customer decides to check out yield request, self, checkout_aisle at_checkout = now() # Customer gets to front of line waittime.tally(at_checkout-start) yield hold, self, self.items*ITEMTIME leaving = now() # Customer completes purchase checkouttime.tally(leaving-at_checkout) yield release, self, checkout_aisle
每位顧客已經(jīng)決定采購(gòu)一定數(shù)量的商品。(我們的模擬不涉及從食品雜貨店通道上選擇商品;顧客只是推著他們的手推車到達(dá)付款處。)我不能確定這里的指數(shù)變量分布確實(shí)是一個(gè)精確的模型。在其低端處我感覺是對(duì)的,但我感到對(duì)實(shí)際購(gòu)物者究竟采購(gòu)了多少商品的最高極限有點(diǎn)失實(shí)。在任何情況下,您可以看到如果可以使用更好的模型信息,則調(diào)整我們的模擬是多么簡(jiǎn)單。
顧客采取的操作是我們所關(guān)注的。顧客的“執(zhí)行方法”就是 .checkout() 。這個(gè)進(jìn)程方法通常被命名為 .run() 或 .execute() ,但在我的示例中, .checkout() 似乎是最可描述的。您可以對(duì)它起任何您希望的名稱。 Customer 對(duì)象所采取的實(shí)際 操作僅僅是檢查幾個(gè)點(diǎn)上的模擬時(shí)間,并將持續(xù)時(shí)間記錄到 waittime 和 checkouttime 監(jiān)控程序中。但在這些操作之間是至關(guān)重要的 yield 語句。在第一種情況中,顧客請(qǐng)求資源(付款通道)。只有當(dāng)顧客獲得了所需的資源之后,他們才能做其它操作。一旦來到付款通道,顧客實(shí)際上就在付款了 ― 所花時(shí)間與所購(gòu)商品的數(shù)量成比例。最后,經(jīng)過付款處之后,顧客就釋放資源,以便其他顧客可以使用它。
上述代碼定義了 Customer 類的操作,但我們需要在運(yùn)行模擬之前,創(chuàng)建一些實(shí)際的顧客對(duì)象。我們 可以為一天中將要購(gòu)物的每位顧客生成顧客對(duì)象,并為每位顧客分配相應(yīng)的付款時(shí)間。但更簡(jiǎn)潔的方法是“在每位顧客到商店時(shí)”,讓工廠對(duì)象生成所需的顧客對(duì)象。實(shí)際上模擬并不會(huì)同時(shí)對(duì)一天內(nèi)將要購(gòu)物的所有顧客感興趣,而是只對(duì)那些要同時(shí)爭(zhēng)用付款通道的顧客感興趣。注意: Customer_Factory 類本身是模擬的一部分 ― 它是一個(gè)進(jìn)程。盡管對(duì)于這個(gè)客戶工廠,您可能聯(lián)想到人造的機(jī)器工人(la Fritz Lang 的 Metropolis),但還是應(yīng)該只把它看作編程的便利工具;它并不直接對(duì)應(yīng)已建模域中的任何事物。
清單 4. 生成顧客流
class Customer_Factory(Simulation.Process): def run(self): while 1: c = Customer() Simulation.activate(c, c.checkout()) arrival = random.expovariate(float(AVGCUST)/CLOSING) yield hold, self, arrival
正如我前面提到的,我想收集一些當(dāng)前 SimPy Monitor 類沒有解決的統(tǒng)計(jì)信息。也就是,我并不僅僅對(duì)平均付款時(shí)間感興趣,而且還對(duì)給定方案中最糟糕情況感興趣。所以我創(chuàng)建了一個(gè)增強(qiáng)的監(jiān)控程序,它收集最小和最大的計(jì)數(shù)值。
用監(jiān)控程序監(jiān)視模擬
class Monitor2(Monitor): def __init__(self): Monitor.__init__(self) self.min, self.max = (int(2**31-1),0) def tally(self, x): Monitor.tally(self, x) self.min = min(self.min, x) self.max = max(self.max, x)
我們模擬的最后一步當(dāng)然是 運(yùn)行它。在大多數(shù)標(biāo)準(zhǔn)示例中,只運(yùn)行一次模擬。但對(duì)于我的食品雜貨店,我決定通過幾次模擬進(jìn)行循環(huán),每次對(duì)應(yīng)于某一天的業(yè)務(wù)。這看來是個(gè)好主意,因?yàn)橛行┙y(tǒng)計(jì)信息會(huì)隨每天的情況而有相當(dāng)大的不同(因?yàn)榈竭_(dá)的顧客人次以及所購(gòu)商品數(shù)采用隨機(jī)產(chǎn)生的不同值)。
清單 6. 每天運(yùn)行模擬
for run in range(RUNS): waittime = Monitor2() checkouttime = Monitor2() checkout_aisle = Simulation.Resource(AISLES) Simulation.initialize() cf = Customer_Factory() Simulation.activate(cf, cf.run(), 0.0) Simulation.simulate(until=CLOSING) #print "Customers:", checkouttime.count() print "Waiting time average: %.1f" % waittime.mean(), \ "(std dev %.1f, maximum %.1f)" % (sqrt(waittime.var()),waittime.max) #print "Checkout time average: %1f" % checkouttime.mean(), \ # "(standard deviation %.1f)" % sqrt(checkouttime.var()) print 'AISLES:', AISLES, ' ITEM TIME:', ITEMTIME
三人不歡:一些結(jié)果(以及它們意味著什么)
當(dāng)我最初考慮食品雜貨店模型時(shí),我認(rèn)為模擬可以解答幾個(gè)直接問題。例如,我想象店主可能會(huì)選擇購(gòu)買改進(jìn)的掃描儀(減少 ITEMTIME ),或者選擇雇傭更多職員(增加 AISLES )。我想只要在每個(gè)方案下運(yùn)行這個(gè)模擬(假設(shè)雇員和技術(shù)成本給定的情況下),并確定上面兩種選擇哪種更能減少成本。
只有運(yùn)行了模擬后,我才意識(shí)到可能會(huì)出現(xiàn)比預(yù)料的更有趣的事情。查看收集的所有數(shù)據(jù),我意識(shí)到我不知道要嘗試優(yōu)化的是什么。 什么。例如,減少 平均付款時(shí)間和減少 最差情況的時(shí)間,哪個(gè)更重要?哪些方面會(huì)提高總體顧客滿意度?另外,如何比較顧客在付款之前所用的等待時(shí)間以及掃描所購(gòu)商品所花的時(shí)間?以我個(gè)人的經(jīng)驗(yàn),我會(huì)在等待的隊(duì)列中感到不耐煩,但在掃描我的商品時(shí),我不會(huì)感到很麻煩(即使這會(huì)花一些時(shí)間)。
當(dāng)然,我沒有經(jīng)營(yíng)食品雜貨店,所以我不知道所有這些問題的答案。但這個(gè)模擬確實(shí)讓我準(zhǔn)確地決定什么是折衷方案;而且它很簡(jiǎn)單,足以稍作調(diào)整就可適用于許多行為(包括那些還未顯式地參數(shù)化的行為 ― 例如,“一整天中顧客 真的會(huì)一直不斷地來嗎?”)。
我只要演示最后一個(gè)示例,就可以說明該模型的價(jià)值。我在上面曾寫道復(fù)雜系統(tǒng)的行為難以概念化。我認(rèn)為這里的示例可以證明這一事實(shí)。在可用的通道從 6 條減少到 5 條(其它參數(shù)不變)時(shí),您認(rèn)為會(huì)出現(xiàn)什么情況?最初我想會(huì) 稍微增加最糟糕情況下的付款時(shí)間。而事實(shí)并非如此:
清單 7. 通道數(shù)變化前后運(yùn)行的兩個(gè)樣本
% python Market.py Waiting time average: 0.5 (std dev 0.9, maximum 4.5) Waiting time average: 0.3 (std dev 0.6, maximum 3.7) Waiting time average: 0.4 (std dev 0.8, maximum 5.6) Waiting time average: 0.4 (std dev 0.8, maximum 5.2) Waiting time average: 0.4 (std dev 0.8, maximum 5.8) Waiting time average: 0.3 (std dev 0.6, maximum 5.2) Waiting time average: 0.5 (std dev 1.1, maximum 5.2) Waiting time average: 0.5 (std dev 1.0, maximum 5.4) AISLES: 6 ITEM TIME: 0.1 % python Market.py Waiting time average: 2.1 (std dev 2.3, maximum 9.5) Waiting time average: 1.8 (std dev 2.3, maximum 10.9) Waiting time average: 1.3 (std dev 1.7, maximum 7.3) Waiting time average: 1.7 (std dev 2.1, maximum 9.5) Waiting time average: 4.2 (std dev 5.6, maximum 21.3) Waiting time average: 1.6 (std dev 2.6, maximum 12.0) Waiting time average: 1.3 (std dev 1.6, maximum 7.5) Waiting time average: 1.5 (std dev 2.1, maximum 11.2) AISLES: 5 ITEM TIME: 0.1
減少一條付款通道不是使平均等待時(shí)間增加 1/5 或類似的情況,而是使它增加了大約 4 倍。而且,最不幸的顧客(在這些特定的運(yùn)行期間)的等待時(shí)間從 6 分鐘增加到了 21 分鐘。如果我是經(jīng)理,我認(rèn)為了解這個(gè)極限情況對(duì)顧客滿意度而言是極其重要的。誰會(huì)早已知道這一點(diǎn)呢?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
