关于元胞自动机
元胞自動機(cellular automata,CA) 是一種時間、空間、狀態都離散,空間相互作用和時間因果關系為局部的網格動力學模型,具有模擬復雜系統時空演化過程的能力。
其實在去年暑假準備國賽的時候試圖過自學,但受限于,網上易懂且高效的文檔或者視頻教學少之又少,且個人學習能力及時間有限,搞得當時的我一頭霧水,于是當時的我放棄了學習。
最近準備數學建模比賽的假期中,建模隊友讓我去學習一下元胞自動機仿真。花了一些時間研究和思考,整理了一些東西。
元胞自動機在筆者看來,最費解的地方在于:
1.關于元胞具體的顏色如何設置
2.如何讓元胞自動機應用于具體地區(如森林大火在澳大利亞的實現,即導入澳大利亞的綠植數據)
3.如何讓狀態矩陣適應較為復雜的規則
近期筆者觀看了大量的教學視頻和論文文獻,根據以上問題做了一些整理,這個過程重新讓我認識了元胞自動機的運行過程,并且總結了一套可適用于絕大多數仿真過程的編程框架。 這里就不贅述元胞自動機的基本知識,直接結合以上三個問題幾個具體例子來談一下見解。 (本文均使用MATLAB來實現)
1.關于元胞具體的顏色如何設置
首先,在筆者的個人理解中,元胞隨時間變化的過程本質上是矩陣的變換。 狀態矩陣中的數值取于一個有限元素的集合(比如在森林火災元胞自動機中,我們令森林為0,陸地為1,起火點為3)。 而元胞自動機的“顏色”只是為了方便我們觀察變化規律而認為設置的“可視化”過程,我們令森林(0)的位置呈現綠色,陸地(1)的位置呈現黃色,起火點(3)的位置呈現紅色。
彩色圖像中,每個點的顏色受三原色向量[R,G,B]來控制,R,G,B的值浮動于0到1的區間內。 而色彩的兩個極端——黑色和白色的RGB向量為[0,0,0]和[1,1,1]。 也就是說,要想在讓矩陣中特定大小的數值顯現出我們需要的顏色,我們需要給其設置特定的RGB向量。 不難理解,如果一個m n大小的矩陣的各個點顯現出我們想看到的顏色,我們需要給定一個mn3的三維矩陣來控制其顏色,即R,G,B從一個向量延申為一個矩陣。
? ? ? ? ? ? ? ??
這里我們可以運用cat函數來組合其RGB矩陣
遇到具體實現時,我看到很多B站UP或者文檔中直接在最后的循環內去分別去變換RGB矩陣,這里我們可以選擇編寫一個顏色轉化函數,輸入狀態矩陣,將其轉化,這樣就可以在每次循環內直接變換其狀態矩陣,讓程序各個函數分工明確,方便我們更好地調試程序的邏輯。以下為森林火災的顏色變換函數:
function [R,G,B] = transformcolor(b1) [m,n]=size(b1); [R,G]=deal(ones(m,n)); B=zeros(m,n); R(b1==0)=0; B(b1==2)=1; G(b1==3)=0; end [R,G,B]=transformcolor(b1); ch=imagesc(cat(3,R,G,B));cat函數將三個R,G,B矩陣合成為一個高維矩陣,方便我們可視化當前狀態。?
MATLAB圖像的R,G,B顏色對照表可參考以下鏈接:
https://blog.csdn.net/ha_____ha/article/details/103683988
2.如何讓元胞自動機應用于具體地區
?其實,導入具體環境的數據可以是一個簡單圖像處理的過程。以森林大火為例,我們可以在互聯網上尋找到各個國家的植被分布圖,筆者在自己練習的時候尋找了美國的國家植被分布圖,如下:
一種簡單的方式便是將彩色圖灰化,即將圖像(mn3)?的三維RGB矩陣轉化為(mn)的二維矩陣。(與之前所說的RGB矩陣稍微有所不同,導入圖像時的RGB值均為0-255的數值,并不是0-1)
筆者選取了代表紅色的R矩陣(選取RGB矩陣中顏色差異最明顯的那個),用imshow觀測如下
此時,我們可以根據該灰度圖進行一個閾值選擇:綠色圈出的部分的值多數分布在0-160之間(森林部分),黃色箭頭指向的大陸部分的值多數分布在160-245之間,其余部分分布在245-255之間。而空白部分并不屬于美國的版圖,方便后期變換處理,筆者讓國界之外的部分設置為2。
這里使用循環對整個矩陣進行區域劃分:
a=imread("E:\USEfulEEE\Mathmodel\CA\China\America.jpg"); %讀取圖像 b3=a(:,:,3); b2=a(:,:,2); b1=a(:,:,1); %分別觀察RGB矩陣,選取值差異最明顯的那個 imshow(b1) %imshow(b2);imshow(b3) [m,n]=size(b1); for i=1:mfor j=1:nif b1(i,j)<=160b1(i,j)=0; %綠色(森林)為0elseif b1(i,j)>160&&b1(i,j)<245b1(i,j)=1; %陸地(可生長為森林的地區)為1elseb1(i,j)=2; %邊界為2endend end [R,G,B]=transformcolor(b1); %狀態矩陣的顏色變換 ch0=imagesc(cat(3,R,G,B)); %可視化區域劃分過的美國植被圖?區域劃分過的矩陣即為我們后期用于做變換的狀態矩陣,該過程相當于狀態的初始化。
?3.如何讓狀態矩陣適應較為復雜的規則
思考狀態變換的計算機實現的過程是一個較為復雜的邏輯的梳理過程。這里筆者也用一個單獨封裝的函數來實現每一次的變換過程。
森林火災元胞機的變化規則為:
1.森林周圍8個鄰居若有1處為起火狀態,該元胞在下一狀態就有很大的概率連帶起火
2.起火狀態在下一個狀態會熄滅,轉化為陸地
3.每個森林元胞有很小的概率被閃電劈中,并起火
4.每個陸地元胞有很小的概率生長出新的森林
5.美國版圖以外的元胞不受任何元胞的影響,一直保持自身狀態
我們在每一個新狀態的開始,狀態轉移函數會捕捉舊狀態的森林、陸地、起火、版圖以外的狀態
function new = generatenew(old) [m,n]=size(old); treeold=old==0; %old中森林的位置 landold=old==1; %old中陸地的位置 fireold=old==3; %old中起火的位置 lightning=0.000002; %起火概率 growth=0.001; %新樹生長的概率有了舊狀態,我們要根據規則和舊狀態去生成新的狀態,即新狀態哪些位置為森林,起火,陸地和版圖以外。
筆者根據五大規則列寫了幾個重要的關系式:
1.新狀態森林的位置=舊狀態森林的位置-新狀態起火的位置+長出新森林的位置
2.新狀態陸地的位置=舊狀態陸地的位置-長出新森林的位置+舊狀態起火的位置
3.新狀態起火的位置=閃電劈中的位置+舊狀態起火連帶的起火位置
不難看出,舊狀態幾種元胞的總量和新狀態幾種元胞的總量是永遠守恒的。
countfire=zeros(m,n); countfire(2:m-1,2:n-1)=fireold(1:m-2,2:n-1)+fireold(3:m,2:n-1)+fireold(2:m-1,1:n-2)+fireold(2:m-1,3:n);[r,r1]=deal(rand(m,n)); grow=r.*landold; %grow在可能生長的地方有值 light=r.*treeold; %light在可能起火的地方有值 liandai=(countfire>=1).*r1; liandai=(liandai>=0.35)&treeold;grownew=(grow>=1-growth); %新長出樹的位置為1,其余為0 firenew=(light>=1-lightning)|liandai; %新起火點為閃電劈中的位置+舊起火點連帶的位置 land=landold-grownew+fireold; %陸地的位置為老陸地+火焰燒盡的位置 tree=treeold+grownew -firenew;new=2*ones(m,n); %新地初始化全是邊框 new(tree==1)=0; new(land==1)=1; new(firenew==1)=3; end實現了一次狀態的轉移,我們在主程序里面加入一個for循環或者while循環,來觀察該火災系統持續的變化過程。
PS:注意并行性,我們不能使用循環一個一個改動元胞空間內的元胞狀態,會造成前面的改動影響后面的運行。而利用MATLAB矩陣的優勢進行改動可以保證速度。(我也試過用循環來依次改動每一個元胞,這樣運行的時間成本的確非常高)
hold on; axis equal axis tight axis off for i=1:100b1=generatenew(b1);[R,G,B]=transformcolor(b1);ch0=imagesc(cat(3,R,G,B));pause(0.0001) end變化過程如下演示(轉化gif過后有些模糊):
元胞自動機例二——沙化防護林
筆者在bilibili上看到一個比較有趣的元胞自動機仿真,關于防護林的密度、寬度與防止荒漠化能力的關系研究,即仿真防護林的防護效果。UP主對其做了這樣一個規則描述:
鏈接如下:https://www.bilibili.com/video/BV1nK4y1p7BD
?按照之前講的模板,我進行了編程。
首先將該區域初始化,一個300300的區域,100-150的區域為防護林,其左邊為沙漠(不可被人所使用),右邊為空地(可被人所使用),為方便處理,我們也設置元胞自動機的邊界。
在此我們令空地為0,沙漠為1,森林為2,邊界為3。
森林區域設置可改變的種植密度,用變量percentage來表示。不難看出,該變量越大,綠色的部分越密。
封裝一個如下的顏色匹配函數:
function [R,G,B] = transformcolor(b1) [m,n]=size(b1); [R,G]=deal(ones(m,n)); B=zeros(m,n); R(b1==0)=0; B(b1==2)=1; G(b1==3)=0; end然后根據狀態改變的規則,列出以下式子:
1.新狀態沙漠的位置=舊狀態沙漠的位置-沙漠轉空地的位置+空地轉沙漠的位置
2.新狀態空地的位置=舊狀態空地的位置-空地轉沙漠的位置+沙漠轉空地的位置+森林轉空地的位置
3.新狀態森林的位置=舊狀態森林的位置-森林轉空地的位置
狀態轉移函數如下:
function outlinenew = transferarray(outlineold,m,n) spaceold=outlineold==0; desertold=outlineold==1; forestold=outlineold==2; [count1,count2]=deal(zeros(m,n)); c1=desertold(1:m-2,2:n-1)+desertold(3:m,2:n-1)+desertold(2:m-1,1:n-2)+desertold(2:m-1,3:n); count1(2:m-1,2:n-1)=c1; %count1為每個點周圍的沙漠數量 c2=forestold(1:m-2,2:n-1)+forestold(3:m,2:n-1)+forestold(2:m-1,1:n-2)+forestold(2:m-1,3:n); count2(2:m-1,2:n-1)=c2; %count2為每個點周圍的森林數量 r=rand(m,n); r1=r>0.7; %%%%%空地轉沙漠%%%%%% desertadd=spaceold & (count1>=1) & r1 ; %%%%%沙漠轉空地%%%%%% desertreduce=desertold & (count2>=3) &r1; %%%%%森林轉空地%%%%%% spacenew=forestold & (count1>=3) & r1; %%%%%整體變化過程%%%%%% outlinenew=3*ones(m,n); outlinenew(desertold+desertadd-desertreduce==1)=1; outlinenew(forestold-spacenew==1)=2; outlinenew(spaceold+spacenew-desertadd+desertreduce==1)=0; end主函數如下:
clear;clc; percentage=0.45; %可調節森林密度 in=zeros(300); in(:,1:99)=1; r=rand(300,51); r(r>=1-percentage)=2; r(r<1-percentage)=0; in(:,100:150)=r; outline=3*ones(302); outline(2:301,2:301)=in;[R,G,B]=transformcolor(outline); ch=imagesc(cat(3,R,G,B)); [m,n]=size(outline); hold on for i=1:300outline=transferarray(outline,m,n);[R,G,B]=transformcolor(outline);ch=imagesc(cat(3,R,G,B));pause(0.08) end運行效果:
1.森林密度小于50%(設定為35%):
可以看出,沙漠迅速突破防護林,侵蝕了人類的可利用土地。
?2.森林密度大于50%(設定為65%):
可以看出,防護林的保護效果很好。
這段文章記錄了一個閑人如何度過他無聊的春節,在想的過程中我體會到了很多,也給并不很自信的我帶來了一些小小的成就感和自信心。
強推連大數學建模講座的元胞自動機教程:
https://www.bilibili.com/video/BV1fV411q7SA?from=search&seid=6908856970843696115&spm_id_from=333.337.0.0
元胞自動機(模擬楓葉腐蝕)_嗶哩嗶哩_bilibili
要繼續加油。
總結
- 上一篇: shell中sed -i特殊字符
- 下一篇: 30-- 返回倒数第 k 个节点