一、简单叙述
这个模型也是netlogo自带的模型,是模拟鸟群行为的,位置在biology下,叫做flocking
样子如下图所示
模型假设每只鸟的视野范围是以自身为圆心,vision为半径的一个圆
有3条转角规则
1、在视野范围内,有鸟和他靠的太近,他就会主动远离那只鸟(separate原则)
如果没有鸟和它很近,就会执行对齐(align原则)和靠近(cohere原则)
2、它会尽量模仿视野范围内其他鸟的飞行方向,得到他们的平均方向,往那个方向偏一定角度(align原则)
3、它会根据视野内其他鸟的位置,得到一个平均方向,往那个方向偏一定角度(cohere原则)
这些规则在此模型中都只是调整鸟的运动方向,调整完之后往前走一个单位
所有鸟都是平等的,没有什么leader
模型原来的代码有点绕来绕去的,我这里调整了一下,再稍作分析
二、代码分析
1.鸟拥有的属性
在建模过程中,最重要的就是想清楚全局变量怎么设置
主体应该有哪些额外属性
这里每只鸟有两个属性
第一个属性是flockmates,表示在视野范围内,除自己以外所有的邻居鸟
第二个属性是nearest-neighbor,表示离这只鸟最近的邻居
;;1.鸟拥有的属性
turtles-own[
flockmates ;;视野内的鸟
nearest-neighbor ;;最近的邻居
]
2.初始化按钮 setup
这里主要的工作就是创建鸟,另外就是国际惯例,清空界面,重置时间步
;;2.初始化按钮 setup 代码
to setup
clear-all ;;清空界面
create-turtles birdNum[ ;;创建鸟群,设置每只鸟的大小,颜色,位置,还有自带的属性
set size 7
set color yellow - 2 + random 5 ;;棕色鸟,颜色稍有差异
setxy random-xcor random-ycor ;;每只鸟随机分布
set flockmates no-turtles ;;刚开始没有邻居 集合需要初始化,最近邻一只鸟貌似就不用初始化? nobody和空集合意义不同
]
reset-ticks ;;重置时间步
end
3.循环迭代按钮 go
每一个时间步,都要调整每只鸟的方向,调整完之后,每只鸟往前1个单位
这里由于我们设置成按时间步更新,直接fd 1貌似问题也不大,至少我看不出来区别
所以整个程序的重点就是怎么转向
;;3. go按钮
to go
ask turtles[
adjust-heading ;;给每只鸟调整一下方向
]
repeat 5[ ;;每只鸟向前移动1个单位 5*0.2,为了更流畅
ask turtles[
fd 0.2
]
display
]
tick
end
4.adjust-heading 调整方向函数
首先按照视野范围,找到范围内的邻居鸟
只要有一只鸟的话,就找到最近的那只
最近那只靠的太近的话,就远离
不然就执行对齐和靠近
;;4.adjust-heading 调整方向函数
to adjust-heading
set flockmates other turtles in-radius vision ;;按照视野vision,找到这只鸟的邻居
if any? flockmates[ ;;如果有一只鸟的话
set nearest-neighbor min-one-of flockmates [distance myself] ;;找到最近的邻居
ifelse distance nearest-neighbor < minimum-separation[
;;separate远离
let neighbor-heading [heading] of nearest-neighbor ;;得到邻居的方向
change-heading (subtract-headings heading neighbor-heading) max-separate-turn
]
[
;;align 对齐
change-heading (subtract-headings average-flockmate-heading heading) max-align-turn
;;cohere 靠近
change-heading (subtract-headings average-heading-towards-flockmates heading) max-cohere-turn
]
]
end
源代码这里都采用函数封装的形式,把远离,对齐,靠近三个行为封装成函数
感觉有点多余,这里就拆开来更加直观,反正3个行为都是以某种方式调整了方向
change-heading函数传入两个值,一个是两种方向的差值,一个是偏转最大值,在主界面可以调整
所有偏转都有最大值的限制,不能乱来
5.change-heading 函数
这个函数用最大偏转角度max-turn来规范turn这个转角
turn里面包含了转角方向(正负情况)和转角大小
;;5.change-heading 在最大偏角范围内调整角度
to change-heading [turn max-turn]
ifelse abs turn > max-turn[
ifelse turn > 0[
rt max-turn
]
[
lt max-turn
]
]
[
rt turn ;;正数向右转,不然向左转,turn里已经包含正负号了可以直接rt
]
end
6.average-flockmate-heading
这是一个reporter函数,可以返回一个值
先看代码
;;6.average-flockmate-heading
to-report average-flockmate-heading
let x-component sum[dx] of flockmates
let y-component sum[dy] of flockmates
ifelse x-component = 0 and y-component = 0 ;;总体方向相互抵消,不产生影响
[
report heading
]
[
report atan x-component y-component
]
end
这里的dx,和dy是这样定义的
某只鸟,朝现在的方向,向前走一个单位,和刚在坐标比较,得到一个横坐标差值就是dx,纵坐标差值就是dy
由一只鸟的dx和dy可以使用函数 atan,得到当前的方向
atan dx dy
计算所有邻居鸟的dx和dy,再通过atan函数得到所有鸟的平均方向
有人可能会问,每只鸟作为主体都有heading属性,代表着方向,为什么不把所有方向加起来,平均一下
那么如果一只鸟方向是1度,一只鸟方向是359度,应该得到的平均方向是?
应该不是 (1+356)/2 = 180度吧,应该是0度。
方向是一个矢量,我们在暴力平均heading的时候,把那个方向条件给弄没了
但我们有一个逻辑链条
由当前方向heading向前一个单位,得到dx和dy,这两个值本身就是自带的属性
而dx和dy是有方向性的矢量,能够很好的表达方向,自由地加减
又有atan函数可以把 dx 和 dy 反向表示成一个方向
所以可以通过累加每只鸟的dx和dy,得到邻居鸟的平均方向
7.average-heading-towards-flockmates
先上代码
;;7.average-heading-towards-flockmates
to-report average-heading-towards-flockmates
let x-component mean [sin (towards myself + 180)] of flockmates
let y-component mean [cos (towards myself + 180)] of flockmates
ifelse x-component = 0 and y-component = 0[
report heading
]
[
report atan x-component y-component
]
end
先来看看普通坐标系和方向坐标系的不同
普通坐标系东面是0度西面是180度,是顺时针的
而方向坐标系如下所示,是逆时针的
下图右侧假设一个物体做圆周运动,在四个位置就应该是相应的度数
所以rt 20,这个指令是给heading加20
lt 20,则是给heading减去20
在访问每只邻居鸟的时候,调用 towards myself得到的方向就是
邻居鸟朝向自身的方向,下图红色球表示当前鸟,蓝色球表示邻居鸟
这个方向加上180度,就是自身指向邻居鸟的方向
现在我们只有一个方向,没有dx,dy,因为这是一个逻辑上的方向,不是任何主体的方向
所以我们现在要拿到dx和dy
假设我们现在的方向是方向坐标系下的90°,其实就是直角坐标系下的0度,那么我们向前一个单位,dx = 1,dy = 0
我们用方向坐标系下的90°做三角运算,得到
sin 90° = 1 cos 90° = 0
所以我们使用sin(方向坐标系角度)来表示dx
用cos(方向坐标系角度)来表示dy
再来实证一下
方向坐标系下的角度 | 直角坐标系下的角度 |
0° | 90° |
90° | 0° |
180° | 270° |
270° | 180° |
首先建立一个函数关系 设左边的度数为x1,右边的度数为x2
在2π为周期的函数中有 2π + 90° - x1 等价于 x2
有sin(2π + 90° - x1 ) = sin (x2)
而sin(2π + 90° - x1 ) 的图像如下
所以sin(2π + 90° - x1 ) 等价于cos(x1)
同理
cos(2π + 90° - x1 ) = cos (x2)
cos(2π + 90° - x1 ) 等价于 sin(x1)
所以sin(x1) = cos (x2)
但是这里为什么要用mean取平均值,有点问题,讲道理用sum应该也行?源程序用的的mean,我用了一波sum也看不出什么问题
三、全部代码
;;1.鸟拥有的属性
turtles-own[
flockmates ;;视野内的鸟
nearest-neighbor ;;最近的邻居
]
;;2.初始化按钮 setup 代码
to setup
clear-all ;;清空界面
create-turtles birdNum[ ;;创建鸟群,设置每只鸟的大小,颜色,位置,还有自带的属性
set size 7
set color yellow - 2 + random 5 ;;棕色鸟,颜色稍有差异
setxy random-xcor random-ycor ;;每只鸟随机分布
set flockmates no-turtles ;;刚开始没有邻居 集合需要初始化,最近邻一只鸟貌似就不用初始化? nobody和空集合意义不同
]
reset-ticks ;;重置时间步
end
;;3. go按钮
to go
ask turtles[
adjust-heading ;;给每只鸟调整一下方向
]
repeat 5[ ;;每只鸟向前移动1个单位 5*0.2,为了更流畅
ask turtles[
fd 0.2
]
display
]
tick
end
;;4.adjust-heading 调整方向函数
to adjust-heading
set flockmates other turtles in-radius vision ;;按照视野vision,找到这只鸟的邻居
if any? flockmates[ ;;如果有一只鸟的话
set nearest-neighbor min-one-of flockmates [distance myself] ;;找到最近的邻居
ifelse distance nearest-neighbor < minimum-separation[
;;separate远离
let neighbor-heading [heading] of nearest-neighbor ;;得到邻居的方向
change-heading (subtract-headings heading neighbor-heading) max-separate-turn
]
[
;;align 对齐
change-heading (subtract-headings average-flockmate-heading heading) max-align-turn
;;cohere 靠近
change-heading (subtract-headings average-heading-towards-flockmates heading) max-cohere-turn
]
]
end
;;5.change-heading 在最大偏角范围内调整角度
to change-heading [turn max-turn]
ifelse abs turn > max-turn[
ifelse turn > 0[
rt max-turn
]
[
lt max-turn
]
]
[
rt turn ;;正数向右转,不然向左转,turn里已经包含正负号了可以直接rt
]
end
;;6.average-flockmate-heading
to-report average-flockmate-heading
let x-component sum[dx] of flockmates
let y-component sum[dy] of flockmates
ifelse x-component = 0 and y-component = 0 ;;总体方向相互抵消,不产生影响
[
report heading
]
[
report atan x-component y-component
]
end
;;7.average-heading-towards-flockmates
to-report average-heading-towards-flockmates
let x-component mean [sin (towards myself + 180)] of flockmates
let y-component mean [cos (towards myself + 180)] of flockmates
ifelse x-component = 0 and y-component = 0[
report heading
]
[
report atan x-component y-component
]
end
四、小结
最后那个三角转换应该还有更合理的解释,为什么用mean不用sum,这里不深究了
模型中主要是用一定的逻辑改变鸟的方向,速度都恒定不变都是每个时间步向前一个单位
应该还有更好的表达方式