关于元胞自动机

元胞自动机(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 eq?%5Ctimesn大小的矩阵的各个点显现出我们想看到的颜色,我们需要给定一个meq?%5Ctimesneq?%5Ctimes3的三维矩阵来控制其颜色,即R,G,B从一个向量延申为一个矩阵。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI-Wuh-WumeeahOWwveWktCM=,size_11,color_FFFFFF,t_70,g_se,x_16                watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI-Wuh-WumeeahOWwveWktCM=,size_13,color_FFFFFF,t_70,g_se,x_16

这里我们可以运用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.如何让元胞自动机应用于具体地区

 其实,导入具体环境的数据可以是一个简单图像处理的过程。以森林大火为例,我们可以在互联网上寻找到各个国家的植被分布图,笔者在自己练习的时候寻找了美国的国家植被分布图,如下:

566f9c84adab40e0bb4d343ec315ab20.png

 

 

一种简单的方式便是将彩色图灰化,即将图像(meq?%5Ctimesneq?%5Ctimes3) 的三维RGB矩阵转化为(meq?%5Ctimesn)的二维矩阵。(与之前所说的RGB矩阵稍微有所不同,导入图像时的RGB值均为0-255的数值,并不是0-1)

笔者选取了代表红色的R矩阵(选取RGB矩阵中颜色差异最明显的那个),用imshow观测如下

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI-Wuh-WumeeahOWwveWktCM=,size_20,color_FFFFFF,t_70,g_se,x_16

 

 

此时,我们可以根据该灰度图进行一个阈值选择:绿色圈出的部分的值多数分布在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:m
    for j=1:n
        if b1(i,j)<=160
            b1(i,j)=0;   %绿色(森林)为0
        elseif b1(i,j)>160&&b1(i,j)<245
            b1(i,j)=1;   %陆地(可生长为森林的地区)为1
        else
            b1(i,j)=2;   %边界为2
        end
    end
end
[R,G,B]=transformcolor(b1);   %状态矩阵的颜色变换
ch0=imagesc(cat(3,R,G,B));    %可视化区域划分过的美国植被图

 区域划分过的矩阵即为我们后期用于做变换的状态矩阵,该过程相当于状态的初始化。

a46c85804d194d0696e20a64165119e2.gif

 

 

 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:100
    b1=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

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI-Wuh-WumeeahOWwveWktCM=,size_20,color_FFFFFF,t_70,g_se,x_16

 按照之前讲的模板,我进行了编程。

首先将该区域初始化,一个300eq?%5Ctimes300的区域,100-150的区域为防护林,其左边为沙漠(不可被人所使用),右边为空地(可被人所使用),为方便处理,我们也设置元胞自动机的边界。

在此我们令空地为0,沙漠为1,森林为2,边界为3。

森林区域设置可改变的种植密度,用变量percentage来表示。不难看出,该变量越大,绿色的部分越密。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI-Wuh-WumeeahOWwveWktCM=,size_20,color_FFFFFF,t_70,g_se,x_16

封装一个如下的颜色匹配函数:

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:300
    outline=transferarray(outline,m,n);
    [R,G,B]=transformcolor(outline);
    ch=imagesc(cat(3,R,G,B));
    pause(0.08)
end

运行效果:

1.森林密度小于50%(设定为35%):

7bc33f654916449481dca8bdbfe97b17.gif

可以看出,沙漠迅速突破防护林,侵蚀了人类的可利用土地。

 2.森林密度大于50%(设定为65%):

 

a9bd14f2a94643579c437841d11f0230.gif

 

可以看出,防护林的保护效果很好。

这段文章记录了一个闲人如何度过他无聊的春节。

强推连大数学建模讲座的元胞自动机教程:

https://www.bilibili.com/video/BV1fV411q7SA?from=search&seid=6908856970843696115&spm_id_from=333.337.0.0

元胞自动机(模拟枫叶腐蚀)_哔哩哔哩_bilibili

要继续加油。

 

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

#冷咖啡离开了杯垫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值