上一節(jié)我們介紹了元胞自動機的基礎知識,這一節(jié)我們就來看看agentpy是如何實現(xiàn)森林火災的模型的。大部分的流程與上一節(jié)的生命游戲是一樣的,只是有一些細節(jié)不太一樣。建模過程:ABM的建模過程如下所示: 大致上,也可以對應到元胞自動機的四大要素,但是為了不混淆,也為了不讓大家產生負面的學習遷移,所以我們這里直接用ABM的建模規(guī)則來進行說明。agent結構 首先需要設計一個合理的agent結構,因為我們這里的模型非常簡單,也就是代表樹木的有三種狀態(tài),0代表存活的樹木,1代表燃燒中的樹木,2代表燒完了的白地。環(huán)境結構 用于存儲環(huán)境狀態(tài)的數(shù)據(jù)結構,也就是元胞里面的空間網(wǎng)絡,ABM可以支持各種復雜類型的空間結構,包括三角網(wǎng)、矩形網(wǎng)、蜂窩網(wǎng)、網(wǎng)絡模型、多邊形區(qū)域等,但是我們這里的空間結構比較簡單,就是一個二維的網(wǎng)格,每個格網(wǎng)代表一個位置。這里與上一節(jié)生命游戲的空間網(wǎng)格定義方式是一樣的。——說到空間這個東西,雖然是space,不是spatial,但是我們做GIS的就沒有怕過誰……任意不規(guī)則的地理建模對我們來說都是一盤菜而已,不過這里不用那么麻煩,用agentpy自帶的Grid就可以了。個體的行為規(guī)則 abm里面的agent結構 + 個體行為規(guī)則,實際上就等于了元胞里面的狀態(tài)集合。也就是個體有幾種型態(tài)可以變化,生命游戲里面是兩種:0和1,代表死亡和存活。而森林火災的模型是三種:0代表樹木,1代表燃燒,2代表燒完了的白地。與生命游戲不同,生命游戲的死亡和存活是可以相互轉換的,而森林火災的模型中,燃燒的樹木是不會恢復的,也就是說在這里的規(guī)則是單向的。個體與環(huán)境的交互規(guī)則 這個是abm特有的核心,也是與元胞最大的不同,元胞的個體和空間是融為一體的,而abm的個體和空間是分開的,環(huán)境是個體的承載物,也是個體的行為約束,個體通過認知和因對認證之后的行為,來與環(huán)境進行交互。森林火災的模型中,個體與環(huán)境的交互規(guī)則是:燒完之后,把代表環(huán)境的網(wǎng)格賦值為2.環(huán)境的行動規(guī)則 環(huán)境的行動規(guī)則指的是環(huán)境的狀態(tài)如何隨著時間的推移而變化。一般情況下,環(huán)境會和個體相互進行影響,不過我們這里沒有用到這個規(guī)則,直接略過。 個體與個體之間的互動規(guī)則 這個就是元胞里面的演化規(guī)則,只是可以更加詳細和復雜一些,元胞里面不考慮環(huán)境、個體的問題,二者是合一的,所以環(huán)境的變化就是個體的變化,而這里把個體與環(huán)境獨立開了,所以定義的就是個體與個體之間的影響。另外,元胞里面,每個網(wǎng)格或者說細胞是沒有狀態(tài)的,隨生隨死,沒有記憶,沒有狀態(tài),但是在abm里面,個體本身是可以有狀態(tài)的,特別是在一些更復雜的模擬中,例如生態(tài)模型里面,捕食者可以通過交流,對獵物進行圍獵。森林火災的模型中,個體與個體之間的互動規(guī)則就是:對于每棵正在燃燒的樹,檢查它的鄰居是否有樹可以燃燒,如果有的話,讓他燒起來。題外話:簡單建模情況下,個體與個體之間互動,一般是依賴于環(huán)境約束的,也就是空間統(tǒng)計學里面的空間關系概念化,比如我們現(xiàn)在這個例子。但是復雜情況下就另說了,因為個體與個體之間的聯(lián)系有可能不用通過環(huán)境,特別是真實仿真的情況下,例如設計了廣播機制。以上六種要素就是abm建模的主要步驟,可以看出,元胞的四大要素,在abm里面都有對應的體現(xiàn)。 import agentpy as ap
# 下面幾個包用于可視化 import matplotlib.pyplot as plt import seaborn as sns import IPython
# 解決繪圖中的中文亂碼問題 plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False class ForestModel(ap.Model): # 初始化設定,里面的具體參數(shù)都是通過外部傳遞進來的 def setup(self): # 創(chuàng)建agent # 根據(jù)給定的樹密度和網(wǎng)格大小計算出需要創(chuàng)建的樹的數(shù)量,并將結果轉換為整數(shù) n_trees = int(self.p['Tree density'] * (self.p.size**2)) trees = self.agents = ap.AgentList(self, n_trees) # 創(chuàng)建網(wǎng)格,也就是我們的森林,每個網(wǎng)格上可以有樹也可以沒有樹 self.forest = ap.Grid(self, [self.p.size]*2, track_empty=True) # 將樹添加到森林中,其中樹的位置是隨機的,并且空的網(wǎng)格也會被樹占據(jù) # 這一塊是與前面生命游戲中最大的不同, # 生命游戲中沒有獨立agent,也可以看成所有的網(wǎng)格都是agent, # 也就是運算單元與網(wǎng)格一一對應的 # 而在森林火災模型中,樹是agent,森林是網(wǎng)格,森林只是一個承載物 self.forest.add_agents(trees, random=True, empty=True) # 初始將所有樹的初始狀態(tài)設置為 0,表示它們都是活著的 # 在本模型中,三種狀態(tài):0 表示樹是活著的,1 表示樹正在燃燒,2 表示樹已經(jīng)燃燒完畢 self.agents.condition = 0 # 選擇網(wǎng)格左側的兩列樹作為著火點的樹,它們將首先被點燃 # 選擇兩列的目的是防止第一列出現(xiàn)沒有樹木的情況,導致模擬無法進行 unfortunate_trees = self.forest.agents[0:self.p.size, 0:2] unfortunate_trees.condition = 1 # 每個時間步長的具體操作,也就是模擬的每一步 def step(self): # 選擇所有正在燃燒的樹 burning_trees = self.agents.select(self.agents.condition == 1)
# 對于每棵正在燃燒的樹,檢查它的鄰居是否有樹可以燃燒 for tree in burning_trees: for neighbor in self.forest.neighbors(tree): # 如果鄰居是樹,并且它還沒有燃燒,那么將它設置為正在燃燒的狀態(tài) if neighbor.condition == 0: neighbor.condition = 1 # 將正在燃燒的樹設置為已經(jīng)燃燒完畢的狀態(tài) tree.condition = 2 # 停止條件:如果沒有樹正在燃燒,那么模擬就結束了 if len(burning_trees) == 0: self.stop() def end(self): # 在模擬結束時,打印出已經(jīng)燃燒完畢的樹的數(shù)量和總樹的數(shù)量 burned_trees = len(self.agents.select(self.agents.condition == 2)) self.report('Percentage of burned trees', burned_trees / len(self.agents)) # 定義各項參數(shù)
parameters = { 'Tree density': 0.4, # 定義森林的密度 'size': 50, # 定義森林的大小 'steps': 100, # 定義模擬的步數(shù)(每一步代表一個時間單位,如果沒有滿足條件,到達這個時間就會停止) } # 可視化的過程
def animation_plot(model, ax): # 繪制森林的狀態(tài),定義不同樹木狀態(tài)下的顏色 attr_grid = model.forest.attr_grid('condition') color_dict = {0:'#7FC97F', 1:'#d62c2c', 2:'#e5e5e5', None:'#d5e5d5'} ap.gridplot(attr_grid, ax=ax, color_dict=color_dict, convert=True) ax.set_title(f"森林火災模擬\n" f"模擬步數(shù): {model.t}, 樹木存活數(shù)量: " f"{len(model.agents.select(model.agents.condition == 0))}")
fig, ax = plt.subplots() model = ForestModel(parameters) # 生成動畫和html的控件 animation = ap.animate(model, fig, ax, animation_plot) IPython.display.HTML(animation.to_jshtml(fps=15)) # 設置參數(shù) parameters = { # 森林的密度,設置為一個0.2到0.7之間的范圍 'Tree density': ap.Range(0.2, 0.7), 'size': 100 } # 生成30組參數(shù)組合 sample = ap.Sample(parameters, n=30) # 批處理運行,迭代運行40次,也就是整體會運行1200個模擬 exp = ap.Experiment(ForestModel, sample, iterations=40) results = exp.run() # 把執(zhí)行結果保存到文件中 results.save() results = ap.DataDict.load('ForestModel') # 繪制出森林燒毀的百分比的變化趨勢 sns.set_theme() ax = sns.lineplot( data=results.arrange_reporters(), x='Tree density', y='Percentage of burned trees' ) fontdict = {'family': 'SimHei', 'size': 12} ax.set_xlabel('森林密度', fontdict=fontdict) ax.set_ylabel('森林燒毀的百分比', fontdict=fontdict)
我們手動設置幾個參數(shù)來運行一下,例如我先把森林的密度設置為0.4,森林的大小設置為50,模擬的步數(shù)設置為100,運行結果如下:在密度為0.4的情況下,森林燒毀的百分比是474 / 950 * 100% = 49.89%然后設置密度為0.6,大小和步數(shù)不變,運行結果如下:在密度為0.6的情況下,森林燒毀的百分比是 3 / 1445 * 100% = 0.21%后面通過30組,每組40次的迭代,一共運行了1200次,得到了最終的結果:發(fā)現(xiàn)只要密度超過50%,那么損毀的比例就接近100%。 可見,如果沒有外界因素干預的話,森林大火基本上是一發(fā)不可收拾的……每次運行的結果可能不同,但是多次的運行結果是有一定的規(guī)律的,也就是森林的密度越大,森林燒毀的百分比越高,這是符合我們的預期的。記得用jupyter。
|