元胞自动机入门-森林火灾模型详解

在我大四下学期时,毕业设计让我提前接触了美硕研究方向的内容,其中模拟流体的格子-玻尔兹曼方法,利用了网格来模拟流体微粒的方法,让我想到了当初学习数模之时遇到的元胞自动机。

当下很多数模的教材,当讲到一些科学方法时,往往都会直接给出理论计算部分,这样往往容易产生劝退之意。喵姐才疏学浅,但也想以自己的表达方式,以前人的代码基础来谈谈元胞自动机的应用方法。

谈及元胞自动机(Cellular Automata),同学们第一印象或许就是许许多多的小格子,然后这些小格子们以某种函数关系相互相爱相杀,将图像推演下去。

我这么说也不知道有没有说中大家,但这么说其实也差不多,下面我会通过一些例子,简单介绍下这些格子们(cells)是如何“相爱相杀”的。

长文预警

多年以前,世间曾有一片森林,后来我对它用了二向箔,这片森林就成了二维的存在。

在MATLAB里这片森林就是个二维矩阵,1000×1000的位置,每个位置就是一个cell。

并不是说,3D的就不能被元胞自动机模拟了,3D的理论上也是可以的。

不过CA一般多用于二维层面上的模拟,并被认为是离散的动力学模型。并且,网格也不一定非得是四四方方的方块形式,也可以是三角形,多边形等。

有天闲暇,我用MATLAB打开这片森林,想看看cell的数值,里面全是0和2。那么0代表空地,2代表一棵树。

那么假如某棵树着火了,那么在cell里的数值,就应当是1,代表这棵树着火了,并且在下一个时刻,这个cell的数值将变成0,代表树烧没了。

那么凭什么让这棵树附近的树儿们引火上身呢?这里我先介绍一下什么是VonNeumann Neighborhood和Moore Neighborhood。如果是应用VonNeumann邻居,那么则只考虑这棵树前后左右的状态。

若是Moore邻居,则考虑周边八个格子的影响。

你仍然可以考虑周边多个格子的影响,整一些什么Sister Miao Neighborhood什么的,都可以。

在森林火灾模型中,规则就是只要4个邻居cells有一个是1,并且自己是2,那么下个时间节点自己将变成1,再下一个时间节点自己变成0。

森林火灾模型元胞的规则,做到这里,基本上这就完事了,怎么样,是不是很简单?


那么回到我们的绿绿森林,网上现有的源代码里面还设定了0.000005的雷击与0.01的自然生长,即2有0.000005的概率变成1,0有0.01的概率变成2。并且还巧妙地设置了边界连接,那我们就暂且考虑一下这些因素吧,让喵姐在下文详细拆解代码的部分。

Line:1-11

clf

clear all 

n=100; %注意line6,生成n×n的零矩阵,就是所谓的lattice,可供我们恣意纵横的棋盘。

Plightning = .0000005; %产生雷击的概率

Pgrowth = .01; %空地长出树木的概率

z=zeros(n,n); %在line3处设置了n,如果你想自定义“棋盘”的话,大可以删除line3,然后直接改这里,这样的话底下的cell规则也要修正一下。

veg=z; %复制一层棋盘,留z作底板

sum=z; %再复制一层z,用作计算该cell周边着火树木的数量

imh = image(cat(3,z,veg,z)); %cat(3,z,veg,z)会生成n×n×3的矩阵,由image生成图像。

axis equal 

axis tight 


Image(C)会让数组C产生图像,可以用下面这些代码直观地了解一下。

C = [0 2 4 6; 8 10 12 14; 16 18 20 22];

image(C)

colorbar

Line:13

sum = (veg(1:n,[n 1:n-1])==1) + (veg(1:n,[2:n 1])==1) + (veg([n 1:n-1], 1:n)==1) + (veg([2:n 1],1:n)==1) 

这行代码计算了每个cell的上下左右四个格子中1的数目。

要知道,sum也是一个n×n的矩阵,veg(1:n,[n 1:n-1])中,1:n表示1-100。

[n 1:n-1]可能萌新同学不太理解,且容喵姐在此絮叨一下,[n]很简单,就是1×1的矩阵,[1 2 3]呢?是1×3的矩阵,[1:4]则为[1 2 3 4],是个1×4的矩阵,那么[1 2 3 1:4]是什么呢?

答案是[1 2 3 1 2 3 4]。

当你看到这里,想必就明白了[n 1:n-1]是一个1×n的矩阵。

而veg(1:n,[n 1:n-1])则是n×n的矩阵。

这行代码萌新理解起来可能比较难,但理解后应当就和多年前大二时期,那个黄昏见晚的下午,坐在大学寝室椅子上的喵姐,之前还一脸懵逼,结果一下子恍然大悟吧。

以这样的形式来计算sum,是考虑到了边界的连接,即火烧到棋盘右侧边界,便会从左侧边界继续烧过来。上下边界同理。

那么同学们准备好恍然大悟了吗?

喵姐真想看看你们恍然大悟的样子,就觉得心有灵犀,大家都好聪明,一点就透。

首先,看矩阵[1:n],这个一目了然,对吧。

[1:n]=[1:n-1 n],这个等式对不对?对!

那假若我这样呢?[n 1:n-1],有什么区别?是不是把n提到左边来了,这是什么意思?

就是veg(1:n,[n 1:n-1])把每行最右边边界那个cell的值提到了最左边第一个,1:n的意思就是第1至n行。

那第二个cell的值呢?答案是来源于左边一个格子。

以此类推。

那么veg(1:n,[n 1:n-1])的意思就很明朗了,该cell值变成左侧一格cell值,注意在这里,是变成左侧一格值,不是加上这个值。

(veg(1:n,[n 1:n-1])==1)则变成了每个cell,若左侧一格cell值为1,则该cell变成1,不是则为0。

好的,理解了这一点,再看(veg(1:n,[2:n 1])==1),这是把最左侧cell值提到了最右侧去,然后如果是1就保留, 即每个cell右侧一格cell值提到自己身上,如果是1就保留,其他值就归零。

(veg([n 1:n-1], 1:n)==1),当1:n在括号后侧时,则表示1-n列,此时表示底部边界最后一个cell值提到最顶上第一个,其他每个cell得到头顶上一个cell的值,并且如果这些值为1则保留,为其他值则变成0。

直观一点,可以通过这些简短的代码测试一下:

a=[1 2;

    1 2;

    1 2;

    1 2;

    1 2;

    1 2;

    3 3;

    1 2;

    1 2;

    1 2];

veg=a;

b=veg([10 1:9], 1:2)


(veg([2:n 1],1:n)==1) 表达得到下面一格cell的值,为1则保留,其他值则变成0。

那么

sum = (veg(1:n,[n 1:n-1])==1) + (veg(1:n,[2:n 1])==1) + (veg([n 1:n-1], 1:n)==1) + (veg([2:n 1],1:n)==1) ; 则表示4个n×n矩阵相加,这样就有了上下左右四个cell中着火的数目数量。

如果你删除了line3,在line6处设置了自己想要的尺寸,如x(行,即上下方向长度)×y(列,即左右方向宽度),此处也要改成

sum = (veg(1:x,[y 1:y-1])==1) + (veg(1:x,[2:y 1])==1) + (veg([x 1:x-1], 1:y)==1) + (veg([2:x 1],1:y)==1) ;

Line:14

 veg = 2*(veg==2)-((veg==2)&(sum>0|(rand(n,n)

先看2*((veg==0) & rand(n,n)

此处,rand可以生成随机数,rand(n,n)则为表示n×n里的每个cell生成随机数,rand(n,n)


那么肯定有同学要问了,不是说好了要凭空长出来树吗?要是原来有树了咋办?

所以说,除了rand(n,n)

((veg==2)&(sum>0|(rand(n,n)

且看veg==2,很好理解,产生个n×n的矩阵,里面树木地区cells,就是里面数值为2的cells变成1,其他cells数值为0。

再看(sum>0|(rand(n,n)0表示该cell周边有火,(rand(n,n)

要是雷劈空地那还好说,倘若是雷劈到树了呢?

没错,((veg==2)&(sum>0|(rand(n,n)

好了,既然知道了哪些树宝宝要火,下个时间点就得让她们火(即2变成1),如下代码即可实现。

2*(veg==2)-((veg==2)&(sum>0|(rand(n,n)

多了2*(veg==2),这会得到整片森林的树,则为n×n的0和2矩阵,即有树处为2,其他处为0。在上面我们已经得到了要火的树宝宝们位置,且在矩阵里以1的形式被标记好了,那么减去这个部分,整片森林中要火的树宝宝瞬间燃烧,即2-1=1。怎么样?元胞自动机就是如同2-1=1一样简单。

考虑到有烧有生,将烧毁部分与生长部分加起来,则得到每一轮演变的完整过程。

veg = 2*(veg==2)-((veg==2)&(sum>0|(rand(n,n)

我们是不是忘了cell值为1的情况?不,没有,在2*(veg==2)时,上一轮cell为1的树宝宝们直接被0了。

Line:12-17

for i=1:30000

sum = (veg(1:n,[n 1:n-1])==1) + (veg(1:n,[2:n 1])==1) + (veg([n 1:n-1], 1:n)==1) + (veg([2:n 1],1:n)==1) ;

veg = 2*(veg==2)-((veg==2)&(sum>0|(rand(n,n)

set(imh, 'cdata', cat(3,(veg==1),(veg==2),z) ) %这里cdata的意思是生成图像的同时,不替换原有图像。

drawnow

End


到此,本森林火灾模型已经建立完毕。

运行结果:

树木生长时,它看起来就像本公众号那可爱的二维码,迫不及待地等着新同学们来扫码关注一样,不是吗?

改变“棋盘”尺寸的模拟结果:





回到开头,那个被二向箔打击的森林,俯瞰如下图。

新建一个m文件,把bmp图像放到同一文件夹中。

line: 1-4

forest=imread('WoYuanLiangNi.bmp');%导入图像数据至MATLAB

thresh = graythresh(forest);%自动确定二值化阈值;

forgiveyou = im2bw(forest,thresh);%对图像自动二值化

imshow(forgiveyou)%看一下二值化后长啥样

点开forgiveyou数据,发现空白处为1,黑色处为0,所以我要做的就是让空白处全部变成0,黑色处全部变成2。

line1-8:

clc

clear

forest=imread('WoYuanLiangNi.bmp');

thresh = graythresh(forest); 

forgiveyou = im2bw(forest,thresh);      

%imshow(forgiveyou) 

Iforgiveyou = zeros(400,500)+ 2*(forgiveyou==0); 

imshow(Iforgiveyou) 

事后我点进去Iforgiveyou里面看了下,的确是变成了0和2的矩阵,400×500,是因为我bmp图片的像素就是400×500。

这里我们去掉雷击与自然生长,仅仅在图像中间设定一个燃烧的树。

line:1-21

clc

clear

forest=imread('WoYuanLiangNi.bmp');

thresh = graythresh(forest); 

forgiveyou = im2bw(forest,thresh);      

%imshow(forgiveyou)

Iforgiveyou = zeros(400,500)+ 2*(forgiveyou==0);

imshow(Iforgiveyou)

z=zeros(400,500); 

x=400;

y=500;

sum=z; 

veg=Iforgiveyou;

veg(225,445)=1; %这儿~设定一个燃烧的树

imh = image(cat(3,z,veg,z));

for i=1:1000

 sum = (veg(1:x,[y 1:y-1])==1) + (veg(1:x,[2:y 1])==1) + (veg([x 1:x-1], 1:y)==1) + (veg([2:x 1],1:y)==1) ;

 veg = 2*(veg==2)-((veg==2)&(sum>0)) ; 

 set(imh, 'cdata', cat(3,(veg==1),(veg==2),z) ) 

 drawnow 

end

模拟结果:(并没有理想地烧完)

到这里,森林火灾模型的元胞自动机入门已经结束了,希望各位同学能够有所启发。在设置自己想要的元胞自动机时,需要考虑到各个格子之间的相互影响,写好局部的规则,这往往是很烧头脑的。

如今我们在大学学习或者步入社会,不再像初高中时代填鸭式获得知识。在埃及军区没网的夜晚,我时常抬头看向深邃的夜空,星星清晰明亮,总会让我陷入沉思,感叹前几个世纪开普勒,卡文迪许,牛顿等人,同样是看向这片夜空,他们天才般地得出了行星运动,万有引力等计算公式,被记载于物理课本中,而我却只能看到繁星点点,四处是人间理想,当我们惊叹于前人的惊人的创造时,会发现人类科学的历史就是被这些如同闪耀群星般的伟人推动的。科学历史星河上牛顿的万有引力,爱因斯坦的E=mc的平方耀眼无比,亦或是点点流星如元胞自动机,和其他FEM,FVM,FBM等科学方法,偶然间自己也会莫名有所冲动,想发现一个进步的科学方法,在人类科学的历史星河上,泛出一点属于自己的光芒。

涵盖大学的各个方面,数模入门到轻松拿奖

萌新如何成长成为学霸,升学是保研还是出国

英语该怎么学,其他竞赛怎么办,资料该怎么找...

你可能感兴趣的:(元胞自动机入门-森林火灾模型详解)