使用Python的SimPy進(jìn)行制造仿真
使用Python和SimPy創(chuàng)建一個(gè)吉他工廠仿真
仿真是一種基于模型的活動(dòng)。它通過對(duì)系統(tǒng)模型的試驗(yàn)達(dá)到分析與研究系統(tǒng)的目的。
仿真技術(shù)是再現(xiàn)系統(tǒng)動(dòng)態(tài)行為、分析系統(tǒng)配置與參數(shù)是否合理、預(yù)測(cè)瓶頸工序、判斷系統(tǒng)性能是否滿足規(guī)定要求、為制造系統(tǒng)的設(shè)計(jì)和運(yùn)行提供決策支持。
在本文中,我們將使用SimPy來建立一個(gè)吉他工廠,介紹一些非常精彩的東西,你可以將這些用到你自己的仿真案例中。
1.SimPy
首先,什么是SimPy?SimPy文檔中將其定義為:“SimPy是基于過程的離散事件的標(biāo)準(zhǔn)Python模擬框架”。如果您沒有安裝SimPy,用下面的代碼進(jìn)行SimPy的安裝:
pip install simpy2.吉他工廠
首先我們將從頭開始建立一個(gè)吉他工廠,從非常簡單的東西到更完善的仿真系統(tǒng)。在此示例中我們生產(chǎn)一種吉他,吉他的木質(zhì)主體分為兩部分:琴身和琴頸,這兩部分是分別生產(chǎn)的,但使用的都是相同類型的木材,然后將這些半成品送到給噴涂工序車間進(jìn)行噴涂。最后,將噴涂好的琴身、琴頸和電子元件組合在一起,從而完成一個(gè)吉他的生產(chǎn)。
我們先看看生產(chǎn)業(yè)務(wù)流程圖:

先解釋一下業(yè)務(wù)流程:
有2個(gè)主要容器(庫存):木材倉庫和電子元件倉庫。這些容器中有N單位數(shù)量木材/電子元件,這些原材料將在生產(chǎn)流程中使用。
琴身部件從木材倉庫取出1單位木材,生產(chǎn)成1個(gè)琴身,然后將其存儲(chǔ)在琴身倉庫中。琴頸也一樣,但是從1單位木材上得到2個(gè)琴頸。琴頸存儲(chǔ)在琴頸倉庫中,琴身和琴頸都在等待噴涂。
噴涂車間給琴身和琴頸上漆,然后將它們存儲(chǔ)在已噴涂的琴身倉庫(待組裝琴身倉庫)和已噴涂的琴頸倉庫(待組裝琴頸倉庫)中。
1個(gè)琴身和1個(gè)琴頸和1單位電子元件在組裝車間就組裝出來1個(gè)成品吉他,組裝完成后存放在成品倉庫中。
生產(chǎn)完成一定量的吉他成品,商店安排人來取貨。
當(dāng)木材或電子元件的原材料庫存低到一定水平時(shí),會(huì)聯(lián)系供應(yīng)商進(jìn)行原材料供貨。T天后,供應(yīng)商送貨到達(dá)工廠的倉庫,原材料庫存增加。
3.循序漸進(jìn)仿真
1)簡單例子
我們從易到難,先看看最簡單的業(yè)務(wù)模型,琴身和琴頸車間分別從木材倉庫取1單位木材,分別生產(chǎn)出1個(gè)琴身和2個(gè)琴頸,放在成品倉庫中(我們暫時(shí)稱之為產(chǎn)品倉庫),如下圖:

代碼如下:
import simpywood_capacity = 1000initial_wood = 500dispatch_capacity = 500class Guitar_Factory:def __init__(self, env):self.wood = simpy.Container(env, capacity = wood_capacity, init = initial_wood)self.dispatch = simpy.Container(env ,capacity = dispatch_capacity, init = 0)
我們開始導(dǎo)入SimPy,之后創(chuàng)建Guitar_Factory類,并添加兩個(gè)容器(倉庫),一個(gè)是木材倉庫,最大庫存為上面設(shè)置的1000,初始化庫存為500,另一個(gè)是成品倉庫,最大庫存為上面設(shè)置的500,初始化庫存為0。注意env參數(shù),這個(gè)是SimPy的一個(gè)環(huán)境,我們后面會(huì)說明。
現(xiàn)在我們創(chuàng)建琴身和琴頸的生產(chǎn)過程。
def body_maker(env, guitar_factory):while True:yield guitar_factory.wood.get(1)body_time = 1yield env.timeout(body_time)yield guitar_factory.dispatch.put(1)def neck_maker(env, guitar_factory):while True:yield guitar_factory.wood.get(1)neck_time = 1yield env.timeout(neck_time)yield guitar_factory.dispatch.put(2)
我們創(chuàng)建兩個(gè)生產(chǎn)過程,該函數(shù)有兩個(gè)參數(shù):SimPy環(huán)境和guitar_factory類(注意guitar_factory與是我們定義的Guitar_Factory不的實(shí)例化)。過程是這樣的:
在仿真運(yùn)行期間,車間從倉庫取出1單位木材
guitar_factory.wood.get(1)在車間加工,一段時(shí)間后
env.timeout(body_time),body_time定義為1個(gè)時(shí)間單位,將會(huì)產(chǎn)出一個(gè)琴身或琴頸,它模擬了車間的生產(chǎn)時(shí)間。在該時(shí)間單位(在我們的示例中為1)過去之后,車間會(huì)將產(chǎn)出放入dispatch的容器(倉庫)中。琴身車間將用1單位木材制成1個(gè)吉他琴身,而琴頸車間將用1單位木材制成2個(gè)琴頸。
hours = 8days = 5#定義仿真的時(shí)間長度total_time = hours * daysenv = simpy.Environment()guitar_factory = Guitar_Factory(env)body_maker_process = env.process(body_maker(env, guitar_factory))neck_maker_process = env.process(neck_maker(env, guitar_factory))print('仿真開始:')env.run(until = total_time)print(f'倉庫Dispatch中分別有%d 琴身和琴頸的庫存!' % guitar_factory.dispatch.level)print('仿真結(jié)束。')
最后,我們創(chuàng)建一個(gè)仿真環(huán)境,在這個(gè)環(huán)境中實(shí)例化一個(gè)吉他工廠guitar_factory = Guitar_Factory(env),
琴身和琴頸的生產(chǎn)過程會(huì)在env的環(huán)境中通過env.process來實(shí)例化,通過傳遞我們定義好的until=total_time給到env.run的運(yùn)行仿真環(huán)境函數(shù),將仿真運(yùn)行40小時(shí)(5天*8小時(shí))。
我們通過guitar_factory.dispatch.level獲取成品的庫存水平。
運(yùn)行結(jié)果如下:

2)增加一些工序
現(xiàn)在我們?cè)黾訃娡抗ば蚝徒M裝工序到我們的業(yè)務(wù)模型。為了達(dá)到目的,我們?cè)黾樱?/p>
噴涂前和噴涂后的容器(倉庫),和噴涂工序。
電子元件容器(倉庫)
組裝工序

electronic_capacity = 100initial_electronic = 100pre_paint_capacity = 100post_paint_capacity = 200class Guitar_Factory:def __init__(self, env):self.wood = simpy.Container(env, capacity = wood_capacity, init = initial_wood)self.electronic = simpy.Container(env, capacity = electronic_capacity, init = initial_electronic)self.pre_paint = simpy.Container(env, capacity = pre_paint_capacity, init = 0)self.post_paint = simpy.Container(env, capacity = post_paint_capacity, init = 0)self.dispatch = simpy.Container(env ,capacity = dispatch_capacity, init = 0)
以上內(nèi)容沒有什么新的內(nèi)容,我們?cè)黾恿穗娮釉娡壳昂蛧娡亢蟮膫}庫,并設(shè)定他們的最大庫存水平和初始化庫存水平。
接下來我們定義噴涂和組裝工序。
def painter(env, guitar_factory):while True:yield guitar_factory.pre_paint.get(10)paint_time = 4yield env.timeout(paint_time)yield guitar_factory.post_paint.put(10)def assembler(env, guitar_factory):while True:yield guitar_factory.post_paint.get(2)yield guitar_factory.electronic.get(1)assembling_time = 1yield env.timeout(assembling_time)yield guitar_factory.dispatch.put(1)
正如我們?cè)谏a(chǎn)琴身和琴頸的一樣,我們創(chuàng)建噴涂和組裝的工序,噴涂需要4小時(shí),每次能同時(shí)噴涂10個(gè),放到一個(gè)名為
post_paint的容器(倉庫)中 guitar_factory.post_paint.put(10)。
組裝工序是使用1個(gè)噴涂后的琴身和1個(gè)噴涂后的琴頸,這里我們不區(qū)分琴頸和琴身,直接提取兩個(gè)庫存
guitar_factory.post_paint.get(2)(這里稍后需要優(yōu)化),再加上一份電子元件,經(jīng)過1個(gè)小時(shí)的組裝就得到一個(gè)成品。
然后我們添加一些打印輸出,創(chuàng)建環(huán)境并運(yùn)行仿真。
env = simpy.Environment()guitar_factory = Guitar_Factory(env)body_maker_process = env.process(body_maker(env, guitar_factory))neck_maker_process = env.process(neck_maker(env, guitar_factory))painter_process = env.process(painter(env, guitar_factory))assembler_process = env.process(assembler(env, guitar_factory))print(f'仿真開始:')env.run(until = total_time)print(f'噴涂前有%d 琴身和琴頸準(zhǔn)備噴涂' % guitar_factory.pre_paint.level)print(f'噴涂后有 %d 琴身和琴頸準(zhǔn)備組裝' % guitar_factory.post_paint.level)print(f'有 %d 吉他成品!' % guitar_factory.dispatch.level)print(f'----------------------------------')print(f'仿真完成。')

3)庫存預(yù)警、供應(yīng)鏈供貨
現(xiàn)在我們添加一些非常酷的東西,到目前為止,我們已經(jīng)為每一個(gè)工序設(shè)定了固定的時(shí)間,比如我們組裝車間需要一個(gè)小時(shí)來組織吉他。用這個(gè)參數(shù),我們生產(chǎn)一個(gè)吉他始終都是1個(gè)小時(shí),不會(huì)有任何的偏差。
實(shí)際上,工序生產(chǎn)產(chǎn)品的時(shí)間都是在一個(gè)平均時(shí)間上下波動(dòng)的,我們假設(shè)時(shí)間服從正態(tài)分布(實(shí)際上你應(yīng)該通過實(shí)際的統(tǒng)計(jì)數(shù)據(jù)得到加工時(shí)間的分布特征來生成隨機(jī)數(shù)。)

另外我們定義每種工序的數(shù)量(你可以理解為工序員工數(shù)量或者工序設(shè)備數(shù)量)。我們從導(dǎo)入隨機(jī)庫和定義參數(shù)開始。
import randomnum_body = 2mean_body = 1std_body = 0.1num_neck = 1mean_neck = 1std_neck = 0.2num_paint = 1mean_paint = 4std_paint = 0.3num_ensam = 4mean_ensam = 1std_ensam = 0.2
num_body設(shè)置了琴身的車間數(shù)量,設(shè)置為2,mean_body設(shè)置了生存一個(gè)琴身平均需要的時(shí)間,std_body設(shè)置了生存琴身的時(shí)間的標(biāo)準(zhǔn)差,我們修改我們的代碼:
def body_maker(env, guitar_factory):while True:yield guitar_factory.wood.get(1)body_time = random.gauss(mean_body, std_body)yield env.timeout(body_time)yield guitar_factory.pre_paint.put(1)def neck_maker(env, guitar_factory):while True:yield guitar_factory.wood.get(1)neck_time = random.gauss(mean_neck, std_neck)yield env.timeout(neck_time)yield guitar_factory.pre_paint.put(2)def painter(env, guitar_factory):while True:yield guitar_factory.pre_paint.get(10)paint_time = random.gauss(mean_paint, std_paint)yield env.timeout(paint_time)yield guitar_factory.post_paint.put(10)def assembler(env, guitar_factory):while True:yield guitar_factory.post_paint.get(1)yield guitar_factory.electronic.get(1)assembling_time = max(random.gauss(mean_ensam, std_ensam), 1)yield env.timeout(assembling_time)yield guitar_factory.dispatch.put(1)之前的例子timeout的參數(shù)我們總是設(shè)定為一個(gè)固定值,現(xiàn)在我們將這幾個(gè)工序的生產(chǎn)時(shí)間定義為一個(gè)隨機(jī)數(shù)。random.gauss(mean_body, std_body)
之前的例子timeout的參數(shù)我們總是設(shè)定為一個(gè)固定值,現(xiàn)在我們將這幾個(gè)工序的生產(chǎn)時(shí)間定義為一個(gè)隨機(jī)數(shù)。
random.gauss(mean_body, std_body)意思是生成一個(gè)平均數(shù)為mean_body,標(biāo)準(zhǔn)差為std_body的正態(tài)分布隨機(jī)數(shù)(正態(tài)分布也叫高斯分布,gauss)。
另外注意,我們的組裝時(shí)間隨機(jī)數(shù)取為1到1之間的最大值。換句話說,我們說組裝吉他的時(shí)間永遠(yuǎn)不會(huì)少于一小時(shí)。很多情況下都需要這種設(shè)定,不然有可能生成的隨機(jī)數(shù)可能是負(fù)數(shù)(仿真過程將會(huì)出錯(cuò))。
我們必須通過創(chuàng)建新功能來更改工序數(shù)量,該功能允許我們創(chuàng)建多個(gè)工序。
def body_maker_gen(env, guitar_factory):for i in range(num_body):env.process(body_maker(env, guitar_factory))yield env.timeout(0)body_gen = env.process(body_maker_gen(env, guitar_factory))
當(dāng)然,這里另外還有
neck_maker_gen, paint_maker_gen和assembler_maker_gen
三個(gè)工序有類似的代碼。
通過for循環(huán)我們創(chuàng)建2個(gè)琴身工序(我們定義了num_body=2),因此,我們有2個(gè)琴身工序、1個(gè)琴頸生產(chǎn)工序,1個(gè)噴涂工序和4個(gè)裝配工序。
現(xiàn)在我們將創(chuàng)建庫存監(jiān)控和聯(lián)系供應(yīng)商的函數(shù)。我們不斷監(jiān)控原材料的庫存量(level),如果當(dāng)前庫存量低于我們定義的水平,它將致電供應(yīng)商進(jìn)行送貨。經(jīng)過一定的供應(yīng)周期后,一定量的原材料將到達(dá)我們?cè)牧蟼}庫。
首先我們先定義我們的庫存預(yù)警水平。
wood_critial_stock = (((8/mean_body) * num_body +(8/mean_neck) * num_neck) * 3)electronic_critical_stock = (8/mean_ensam) * num_ensam * 2
預(yù)警庫存的定義,取決于創(chuàng)建琴身或琴頸的平均時(shí)間,琴身和琴頸的制造工序數(shù)量。當(dāng)然這里我們也可以直接設(shè)定某一個(gè)數(shù)值。
木材供應(yīng)周期為2天。
我們?cè)谖覀兊腉uitar_Factory類定義里面添加一個(gè)預(yù)警操作過程。
class Guitar_Factory:def __init__(self, env):self.wood = simpy.Container(env, capacity = wood_capacity, init = initial_wood)self.wood_control = env.process(self.wood_stock_control(env))self.electronic = simpy.Container(env, capacity = electronic_capacity, init = initial_electronic)self.electronic_control = env.process(self.electronic_stock_control(env))self.pre_paint = simpy.Container(env, capacity = pre_paint_capacity, init = 0)self.post_paint = simpy.Container(env, capacity = post_paint_capacity, init = 0)self.dispatch = simpy.Container(env ,capacity = dispatch_capacity, init = 0)def wood_stock_control(self, env):yield env.timeout(0)while True:if self.wood.level <= wood_critial_stock:print(f'在第{0}日 第{1}小時(shí),木材庫存 ({2})低于預(yù)警庫存水平下 '.format(int(env.now/8), env.now % 8,self.wood.level))print('聯(lián)系供應(yīng)商')print('----------------------------------')yield env.timeout(16)print('在第{0}天 第{1}小時(shí),木材送達(dá)'.format(int(env.now/8), env.now % 8))yield self.wood.put(300)print('當(dāng)前庫存是:{0}'.format(self.wood.level))print('----------------------------------')yield env.timeout(8)else:yield env.timeout(1)
我們?cè)贕uitar_Factory類中的__init__函數(shù)中增加wood_stock_control和electronic_stock_control兩個(gè)過程。我們看看wood_stock_control的過程是怎么樣工作的,上述代碼沒有electronic_stock_control過程,但原理是一樣的,你可以自己創(chuàng)建,也可以下載我的代碼。
首先
yield env.timeout(0),意味著木材監(jiān)控進(jìn)程在仿真開始時(shí)就啟動(dòng),后面的yield env.timeout(16)和yield env.timeout(8)表示如果預(yù)警產(chǎn)生供貨請(qǐng)求后,16小時(shí)后送貨到達(dá),然后再等待8小時(shí)后,我們恢復(fù)庫存監(jiān)控。while True表示該過程將在模擬運(yùn)行的所有時(shí)間內(nèi)執(zhí)行。然后,它將檢查庫存水平是否等于或小于先前定義的臨界水平。如果庫存大于該水平,下一個(gè)時(shí)間單位再監(jiān)控
yield env.timeout(1)。當(dāng)庫存水平等于或低于臨界水平時(shí),將執(zhí)行一些打印輸出時(shí)間和當(dāng)前庫存量,并且聯(lián)系供應(yīng)商送貨。
2天(16小時(shí))后,木材原材料到達(dá),木材原材料增加300到我們的倉庫中。yield self.wood.put(300)。
最后,將打印新庫存水平,并且警報(bào)將關(guān)閉1天(
yield env.timeout(8))。
我們將仿真運(yùn)行5天,結(jié)果如下:

4)獨(dú)立倉庫和商場(chǎng)配送
制作完琴身和琴頸后,我們將琴身和琴頸看作同一種東西,將它們存儲(chǔ)在同一個(gè)倉庫中(等待噴涂),這有點(diǎn)不合理。現(xiàn)在,我們將為琴身和琴頸分別實(shí)現(xiàn)單獨(dú)的容器(倉庫),我們可以對(duì)其進(jìn)行適當(dāng)處理。
流程圖如下:

guitars_made = 0body_pre_paint_capacity = 60neck_pre_paint_capacity = 60body_post_paint_capacity = 120neck_post_paint_capacity = 120class Guitar_Factory:def __init__(self, env):self.wood = simpy.Container(env, capacity = wood_capacity, init = initial_wood)self.wood_control = env.process(self.wood_stock_control(env))self.electronic = simpy.Container(env, capacity = electronic_capacity, init = initial_electronic)self.electronic_control = env.process(self.electronic_stock_control(env))self.body_pre_paint = simpy.Container(env, capacity = body_pre_paint_capacity, init = 0)self.neck_pre_paint = simpy.Container(env, capacity = neck_pre_paint_capacity, init = 0)self.body_post_paint = simpy.Container(env, capacity = body_post_paint_capacity, init = 0)self.neck_post_paint = simpy.Container(env, capacity = neck_post_paint_capacity, init = 0)self.dispatch = simpy.Container(env ,capacity = dispatch_capacity, init = 0)self.dispatch_control = env.process(self.dispatch_guitars_control(env))
我們現(xiàn)在已經(jīng)知道如何制作一個(gè)容器(倉庫)了。注意這里新增一個(gè)guitars_made變量和dispatch_control方法。當(dāng)然,我們需要修改body_maker和neck_maker兩個(gè)函數(shù)(工序),把生產(chǎn)出來的琴身和琴頸分開獨(dú)立倉庫存放:
def body_maker(env, guitar_factory):while True:yield guitar_factory.wood.get(1)body_time = random.gauss(mean_body, std_body)yield env.timeout(body_time)yield guitar_factory.body_pre_paint.put(1)
現(xiàn)在,我們將琴身存儲(chǔ)在body_pre_paint容器中(倉庫)。neck_maker函數(shù)也一樣處理。painter函數(shù)(工序)的提取半成品原料來源的倉庫(容器)也需要做相應(yīng)的修改,讓painter工序分別在琴身倉庫和琴頸倉庫提取材料:
def painter(env, guitar_factory):while True:yield guitar_factory.body_pre_paint.get(5)yield guitar_factory.neck_pre_paint.get(5)paint_time = random.gauss(mean_paint, std_paint)yield env.timeout(paint_time)yield guitar_factory.body_post_paint.put(5)yield guitar_factory.neck_post_paint.put(5)
噴涂好的琴身和琴頸,分別存放在body_post_paint和neck_post_paint容器(倉庫)中。
現(xiàn)在,我們像在電子元件或木材上一樣建立一個(gè)控制過程。這個(gè)過程會(huì)跟蹤吉他成品的庫存水平,并通知商店過來取貨。
def dispatch_guitars_control(self, env):global guitars_madeyield env.timeout(0)while True:if self.dispatch.level >= 50:print('成品庫存為:{0}, 在第{1}日 第{2}小時(shí) 聯(lián)系了商場(chǎng)取貨'.format(self.dispatch.level, int(env.now/8), env.now % 8))print('----------------------------------')yield env.timeout(4)print('在第{0}日 第{1}小時(shí),商場(chǎng)取走{2}吉他'.format(int(env.now/8), env.now % 8,self.dispatch.level))guitars_made += self.dispatch.levelyield self.dispatch.get(self.dispatch.level)print('----------------------------------')yield env.timeout(8)else:yield env.timeout(1)
我們創(chuàng)建了一個(gè)名為guitars_made全局變量,記錄我們的總產(chǎn)量。
如果成品庫存水平等于或高于50,我們會(huì)通知商店過來取貨。4小時(shí)后yield env.timeout(4),他們到達(dá)倉庫并拿走所有可用的吉他yield self.dispatch.get(self.dispatch.level)。
當(dāng)他們?nèi)∽呒麜r(shí),我們用語句guitars_made + = self.dispatch.level將吉他的數(shù)量累加到guitars_made變量,用于記錄我們總共出貨了多少吉他。
然后,控制過程將暫停8個(gè)單位時(shí)間yield env.timeout(8),當(dāng)然也可以不暫停,不暫停的話,這里也不會(huì)觸發(fā),因?yàn)槌善穾齑嫠绞遣蛔愕模蛘弋?dāng)天不會(huì)再來取貨。
組裝工序也需要修改為分別從兩個(gè)待組裝的倉庫中提取半成品。
def assembler(env, guitar_factory):while True:yield guitar_factory.body_post_paint.get(1)yield guitar_factory.neck_post_paint.get(1)yield guitar_factory.electronic.get(1)assembling_time = max(random.gauss(mean_ensam, std_ensam), 1)yield env.timeout(assembling_time)yield guitar_factory.dispatch.put(1)
最后,添加一些打印信息并運(yùn)行仿真程序:
print('當(dāng)前等待噴涂的琴身數(shù)量:{0} 和琴頸數(shù)量:{1}'.format(guitar_factory.body_pre_paint.level, guitar_factory.neck_pre_paint.level))print('當(dāng)前等待組裝的琴身數(shù)量:{0} 和琴頸數(shù)量:{1}'.format(guitar_factory.body_post_paint.level, guitar_factory.neck_post_paint.level))print(f'當(dāng)前成品庫存量:%d ' % guitar_factory.dispatch.level)print(f'----------------------------------')print('此周期的吉他總生產(chǎn)數(shù)量: {0}'.format(guitars_made + guitar_factory.dispatch.level))print(f'----------------------------------')print(f'仿真完成!')

5)監(jiān)控
我們希望監(jiān)控每一個(gè)時(shí)間點(diǎn)上,仿真系統(tǒng)各個(gè)節(jié)點(diǎn)的狀態(tài),比如我們想記錄每個(gè)時(shí)間點(diǎn)的待組裝的琴身的庫存量并實(shí)時(shí)作圖。
我們?cè)黾右粋€(gè)監(jiān)控過程函數(shù),用來監(jiān)控我們的等待組裝的琴身和琴頸的半成品庫存,看最后一行是我們新增的監(jiān)控過程env_status。
class Guitar_Factory:def __init__(self, env):self.wood = simpy.Container(env, capacity = wood_capacity, init = initial_wood)self.wood_control = env.process(self.wood_stock_control(env))self.electronic = simpy.Container(env, capacity = electronic_capacity, init = initial_electronic)self.electronic_control = env.process(self.electronic_stock_control(env))self.body_pre_paint = simpy.Container(env, capacity = body_pre_paint_capacity, init = 0)self.neck_pre_paint = simpy.Container(env, capacity = neck_pre_paint_capacity, init = 0)self.body_post_paint = simpy.Container(env, capacity = body_post_paint_capacity, init = 0)self.neck_post_paint = simpy.Container(env, capacity = neck_post_paint_capacity, init = 0)self.dispatch = simpy.Container(env ,capacity = dispatch_capacity, init = 0)self.dispatch_control = env.process(self.dispatch_guitars_control(env))self.env_status_monitor = env.process(self.env_status(env))
我們看看env_status是如何實(shí)現(xiàn)的。
def env_status(self, env):global statusstatus = pd.DataFrame(columns = ["datetime", "dispatch_level",'wood','electronic','body_pre_paint','neck_pre_paint','body_post_paint','neck_post_paint'])status[["datetime", "dispatch_level",'wood','electronic','body_pre_paint','neck_pre_paint','body_post_paint','neck_post_paint']] = status[["datetime", "dispatch_level",'wood','electronic','body_pre_paint','neck_pre_paint','body_post_paint','neck_post_paint']].astype(int)while True:im = plt.plot(status['datetime'], status['neck_pre_paint'],color='#4D9221')ims.append(im)plt.title('neck_pre_paint')plt.pause(0.001)print('{0}在第{1}日 第{2}小時(shí),成品庫存量:{3}'.format(env.now,int(env.now/8), env.now % 8,self.dispatch.level))tmp = {'datetime':env.now,'dispatch_level':self.dispatch.level,'wood':self.wood.level,'electronic':self.electronic.level,'body_pre_paint':self.body_pre_paint.level,'neck_pre_paint':self.neck_pre_paint.level,'body_post_paint':self.body_post_paint.level,'neck_post_paint':self.neck_post_paint.level}status = status.append([tmp])yield env.timeout(1)
我們先定義一個(gè)全局變量status,是一個(gè)pandas的dataframe類型,并且定義好它的結(jié)構(gòu),第一列是時(shí)間,后面各列是各個(gè)節(jié)點(diǎn)(容器))的庫存,并把庫存列定義為int整型。
這里我們只用body_pre_paint待組裝的琴身庫存舉例。
我們通過stock_list變量組裝好一個(gè)字典,然后把它追加到status變量中。看起來是這樣子的:

然后我們通過matplotlib模塊把它用動(dòng)態(tài)圖表現(xiàn)出來。
記得程序之前需要導(dǎo)入這個(gè)模塊,并且用plt.ion()用做動(dòng)態(tài)圖。
import matplotlib.pyplot as plt
plt.ion()
在監(jiān)控函數(shù)中我們用下面的代碼來每一個(gè)時(shí)間間隔畫一次圖片。
plt.plot(status['datetime'], status['neck_pre_paint'],color='#4D9221')
得到neck_pre_paint的庫存變化圖,如下:

要監(jiān)控其他庫存,只需要修改stock_list,并追加到status中,然后plot作圖即可。你也可以對(duì)status進(jìn)行統(tǒng)計(jì)各個(gè)節(jié)點(diǎn)的平均庫存。

根據(jù)上文的系統(tǒng)配置,我們很簡單就發(fā)現(xiàn)了等待噴涂的琴頸庫存和等待噴涂的琴身的庫存不匹配,有點(diǎn)偏高,所以我們可以調(diào)整琴頸生產(chǎn)的一些參數(shù),來得到更好的庫存水平。
比如修改這個(gè)

或者修改這個(gè)

至此,我們完成了一個(gè)比較完整的仿真!希望您喜歡,并且可以從中獲得有用的東西。
本文完整代碼,請(qǐng)?jiān)诠娞?hào)內(nèi)回復(fù)“仿真”進(jìn)行下載。
