Chapter5 - 从羊-草生态系统深入turtle与plot画图
本章将介绍一个全新的NetLogo多主体模型,它是由羊和草两个物种构成的简单的生态系统。
羊-草生态系统模拟了自然界和人类社会都存在的最基本的生存逻辑:个体生存依赖一定的资源。这些资源可以是食物,也可以是财富、声望、权力、关系等。资源可以从环境中获得,它可以给个体带来好处(维持生存、达到行动目标),而失去资源将使个体处于不利的境地,不论是因为缺少食物而饿死,还是因为声誉损耗殆尽而无法在某地继续生存。
羊-草生态系统是关于系统内部食物、财富、声望、权力、关系等资源变化的基本模型,它以“羊”代表“个体”,以“草”代表“资源”,通过它我们可以研究资源分布、资源存量与个体生存及繁衍间的动态关系。掌握了羊-草生态系统模型,我们就可以将其扩展到其他资源和个体关系领域
5.1 羊-草生态系统规则
- 这是一个由羊(turtle)和草(patch)两个物种构成的小型生态系统。
- 羊的内部有一个能量值。吃掉草可以增加能量值。每一个周期都在消耗能量。能量值小于或等于0,羊就会死掉。
- 羊能够繁殖。当能量累积到一定水平,就会繁殖。繁殖需要消耗能量。新出生的羊会天然具备一定的能量。
- 草可以自发地从地里长出来。
5.2 初始化羊-草生态系统
下面用NetLogo实现这个基本的程序,首先初始化生态系统。
在“界面”添加“setup”按钮。由于羊的内部都有一个能量值,因此我们需要自定义一个turtle的属性—— energy。跟第4章讲的patches-own语法类似,使用turtles-own命令,方括号内是变量名energy:turtles-own[energy]
接下来,清空之前的所有状态,如下所示:
然后动态地向生态系统添加草。本次模拟设置20%的草,80%的空地。
- 首先使用ask patches对所有patch进行循环,
- random-float命令产生一个0
1之间的随机小数n之间的随机小数****random-float n**
**产生一个0 - 这个数值如果小于0.2,就把当前patch设置成绿色。
set pcolor green
=pcolor = green
- 因此总体运行效果就是近20%的patch变成绿色,这样草的初始化就完成了。代码如下:
接下来初始化系统中的羊,使用create-turtles命令创建一只羊,并且把它的初始能量设置成100,否则这只羊没有能量用于移动。
以上这部分就是初始化功能,完成后我们可以运行一下。每次单击“setup”按钮,都会随机产生20%的绿草,羊就位于这个世界的中心位置。初始化的生态系统完整的初始化程序如下:
5.3 添加to go程序
接下来添加“go”按钮的程序,即每一个模拟周期要完成的功能。根据前述生态系统的规则,每一个模拟周期都要完成如下功能:
- 草要自然生长
- 每只羊要不断地移动
- 羊在能量积累到一定值时繁育后代
- 如果羊的能量消耗尽,就会死亡。
- 我们用子函数(也称子模块)的方式实现这几个功能,在“go”按钮对应的代码中添加如下语句:执行程序时,它就会调用相应子函数,从而实现整体功能。接下来我们看看每一个功能模块如何操作。
1
2
3
4
5
6
7
8
9to go
add_food ;;添加食物子函数
ask turtles[
turtle_move ;;turtle移动子函数
turtle_breed ;;turtle繁殖子函数
turtle_die ;;turtle死亡子函数
]
tick
end
5.3.1 add_food
为了实现添加草的功能,我们添加了一个自定义模块,该模块跟to setup、to go代码类似,用to作为关键词,后面跟要定义的模块名称,最后用end结束,这样用户就可以定义自己的函数了。add_food函数要实现的功能就是在每个周期都添加一定量的草。可以添加如下代码:
这里用到了一个具有强大功能的函数——n-of。它的一般格式为:**n-of size agentset**
。**n-of 10 patches**
的作用就是从patches集合中随机挑选10个patch,形成了一个新的patches集合。
有了集合以后,再对集合中的每一个元素进行循环,将其颜色设置成绿色,无论这个patch当前是绿色的还是黑色的。
该函数的作用相当于下了一场“食物雨”,每一个模拟周期都会运行一遍add_food函数,都会有10个单位的patch添加上草。
完成了第一步add_food的操作,接下来要做的就是循环访问现在系统中所有的turtle,并且每一个turtle的一生都伴随着3件事——移动、繁殖和死亡。
5.3.2 turtle_move
关于第二个功能,首先我们来看羊的移动需要哪些操作,同样用一个子模块的方式定义移动相关代码,如下所示:
它要实现的功能包括如下几点。
- 如果当前patch是绿色的,即上面有草,那么这只羊会吃掉草,羊的能量也相应增加。这里设置羊的energy增加10,并将当前patch的颜色设置为黑色,表示草被吃掉了。这就是第一部分代码所完成的功能。
- 为了使羊的移动看起来更自然,我们让羊在每个周期以0.2的概率随机转换方向,其他时间都匀速直线前进
- 羊的移动功能。每个周期都消耗1个单位的能量,向前移动一步。这三部分代码都可以通过前面讲过的知识完成。如何设置变量的值、如何产生随机数、如何设置turtle的运动方向、用fd 1完成移动(这里fd是forward的缩写),这些语句组合在一起就实现了turtle_move这个函数。
5.3.3 turtle_breed
接下来用turtle_breed来完成繁殖这部分功能,代码如下所示
在这部分代码中,首先检测这只羊的能量水平是否足够繁育后代,这里规定当它的能量值大于500时才能够繁育后代。繁育后代涉及两件事情,
- 是自己的能量值减少。这部分代码也体现了为人父母的辛苦,养育后代要消耗自身大量能量。
- 是新生儿的出生。这里用到一个新命令——hatch。hatch的英文含义是孵化,非常形象。方括号内的这部分内容就是针对新出生的这只羊的。这只新出生的羊要完成两个操作,
- 第一是往前走一步——fd 1,新出生的羊的指向heading是随机取值的,往前走一步就可以跟它的母体分开。
- 第二个操作是给新出生的羊一个初始能量,否则它一出生就死掉了。此处把它的能量值设置成100。这样就完成了繁殖功能。
5.3.4 turtle_die
最后一个功能是turtle_die,判断一只羊的能量值小于或等于0,它就会死掉。在NetLogo中,我们可以用die这个单词作为命令来杀死turtle。
以上就是羊-草生态系统模型所需要的代码,运行一下看看它的效果。单击“setup”按钮,然后单击“go”按钮,开始可能大家会觉得画面非常乱,稍微把速度调慢一点儿,这时你就会看到有一些用小箭头表示的羊在环境里随机游走,并且可以吃掉草
我们可以重新运行程序,开始的时候只有一只羊,很快它就会繁殖出更多的羊,大家可以看到它的繁殖过程。只要吃掉的草足够多,它就可以进行繁殖。
5.4 追踪某一个具体的turtle或者patch的行为
- 右击turtle,可以对turtle进行inspect、watch和follow操作
- inspect:弹出记录了turtle属性的窗口
- watch:指定的turtle周围出现聚焦的圆圈
- follow:指定turtle出现在屏幕中心位置
- 右击patch,可以对patch进行inspect操作
5.5 变量的主体
- NetLogo中的每一个变量都有隶属关系
- 如果不是在ask turtles这个循环里调用turtle_move子函数,它的调用主体就不是turtle了,而是一个全局的调用主体obsever,obsever没有pcolor、energy属性,程序就会提示错误。
5.6 添加绘图框
- 在NetLogo“界面”加号旁边的下拉框中选中“图”(plot),选中时鼠标指针变成一个十字,然后在空白处单击,就会出现一个弹框
- 设置“名称”为Population(种群);设置它的“X轴标记”为Time,显示时间;“Y轴标记”设置为Population,显示种群数量;X和Y的最小值和最大值可以设置,也可以不设置;勾选“自动调整尺度”后,如果曲线的最大值超过坐标的最大值,绘图框将自动调节坐标最大值;“显示图例”就是在绘图框右侧位置标出每一个片条对应的曲线;“绘图笔”对应图中曲线。
- 第一条曲线绘制系统中羊群数量随时间变化的趋势。我们可以修改绘图笔的颜色为黑色,名称设为sheep。这里请尽量用英文来设置,因为有时曲线名称会对应函数来进行调用,写中文的话可能会出错。
- “绘图笔更新命令”表示实现这条曲线需要的代码,在每一次绘制这条曲线的新点的时候,需要激活这部分代码来实现绘图功能,默认代码plot count turtles刚好满足我们的需求。plot语句的作用就是绘制图形,count turtles就是统计turtle的数量。
- 第二条曲线绘制草总量的变化情况。添加一个绘图笔,把它的颜色改为绿色,名称改成grass。这时绘图命令就需要我们手动填写了
plot count patches with [pcolor = green]
这条命令表示绘制曲线,统计当前pcolor属性是绿色的patch数量。单击“确定”,绘图框就设定好了。 - 重新运行程序,你会发现运行得很好,但是右侧新加入的绘图框没有任何反应。这是因为在NetLogo当前版本中,只有设定了tick,绘图框才会生效。这点不难理解,不设置tick时,模拟世界有一个时钟,绘图本身的更新也有一个时钟,我们用tick来做模拟世界和绘图框的时间同步。跟之前的程序设定一样,在setup代码中添加reset-ticks进行重置,然后在to go代码中添加tick进行计时。这时再运行程序,就画出了漂亮的曲线
5.7 小结
- 介绍了pcolor、energy等每一个属性变量都有一个相应的调用主体,在实现子模块时要特别注意。
- 介绍了操作NetLogo界面追踪某一个具体的turtle或者patch的方法。这对于模型调试,以及理解turtle的行为是否正确,起到了重要作用。
- 介绍了如何添加plot绘图框。绘图框的使用非常方便,通过简单的设置就可以完成。但是要记得添加tick代码,确保模拟世界和绘图框的时间同步。
- 介绍了hatch和die两个关键字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58turtles-own[energy]
to setup
clear-all
reset-ticks
ask patches[
if (random-float 1) < 0.2[
set pcolor green
]
]
create-turtles 1[
set energy 100
]
end
to go
tick
addFood ;; 添加食物子函数
ask turtles[
turtleMove
turtleBreed ;;turtle繁殖子函数
turtleDie ;;turtle死亡子函数
]
end
to addFood ;;每一个模拟周期都会运行一遍add_food函数,都会有10个单位的patch添加上草
;; n-of 10 patches的作用就是从patches集合中随机挑选10个patch
;; 形成了一个新的patches集合
ask n-of 10 patches[
set pcolor green
]
end
to turtleMove
if pcolor = green [
set energy energy + 10
set pcolor black
]
if random-float 1 < 0.2 [
set heading (random 360) ;; 在每个周期以0.2的概率随机转换方向,其他时间都匀速直线前进。
]
set energy energy - 1 ;; 每个周期都消耗1个单位的能量,向前移动一步。
forward 1 ;;
end
to turtleBreed
if energy > 500 [
set energy energy - 500
;; hatch。hatch的英文含义是孵化,非常形象。方括号内的这部分内容就是针对新出生的这只羊的。
hatch 1 [
forward 1 ;; 新出生的羊的指向heading是随机取值的,往前走一步就可以跟它的母体分开
set energy 100 ;; 给新出生的羊一个初始能量,否则它一出生就死掉了。此处把它的能量值设置成100。
]
]
end
to turtleDie
if energy <= 0[
die ;;给新出生的羊一个初始能量,否则它一出生就死掉了。此处把它的能量值设置成100。
]
end