Juliaで群れシミュレーション"Boids"

## 概説
julia言語で簡易的なBoidsシミュレーションを行いました。

![Boids3.gif](https://qiita-image-store.s3.amazonaws.com/0/253018/c08beac3-956e-10cb-27ea-9eb498c67d57.gif)

## はじめに

### Boidsとは
Boid(s)とは、"bird(鳥)"+"-oid(~のようなもの)"の造語であり、飛行する鳥の群れをシミュレートするモデルです。人工生命研究者であるCraig Reynoldsが考案しました。このモデルでは、boid1匹1匹がたった3つの相互作用ルールに従って飛行しているだけにもかかわらず、boidの群れ(boids)はあたかも高度な知性を持った1つの生き物のような挙動を示します。その3つのルールを以下に示します。

1. Collision Avoidance: 群れの仲間との衝突を避ける
2. Velocity Matching: 群れの仲間と速度を合わせようとする
3. Flock Centering: 群れの仲間と近づいたままでいようとする

それぞれのルールについて詳しく見ていきます。
### 定義
$xy$平面に$N$匹のboidが存在します。Boidは位置ベクトル$\boldsymbol{p}$と速度ベクトル$\boldsymbol{v}$をそれぞれ持っています（初期値はランダムです）。$i$番目のboidの位置ベクトルと速度ベクトルを各々$\boldsymbol{p}[i]$,$\boldsymbol{v}[i]$と表します。
![boid_init.jpg](https://qiita-image-store.s3.amazonaws.com/0/253018/8079ae17-cf13-0ef1-094a-450ed5ad96b5.jpeg)
$i$番目のboidに対してルール1-3に基づいて計算して得られた変化量をそれぞれ$\boldsymbol{v_1}[i]$,$\boldsymbol{v_2}[i]$,$\boldsymbol{v_3}[i]$とおきます。現在の速度$\boldsymbol{v}[i]$に$\omega_1 \boldsymbol{v_1}[i]+\omega_2\boldsymbol{v_2}[i]+\omega_3\boldsymbol{v_3}[i]$を加算します（$\omega_1$,$\omega_2$,$\omega_3$は重みパラメータ）。


### ルール1 Collision Avoidance

![boid_rule1.jpg](https://qiita-image-store.s3.amazonaws.com/0/253018/a9357a69-c883-3791-dc4e-6e24a56f1bf9.jpeg)
Boidは仲間に近づきすぎたときに衝突を回避しようとします。$i$番目のboidから見て一定の距離内に仲間（$j$番目のboid）がいるとき、仲間$j$がいる方向と反対方向に移動しようとします。一定の距離内に複数の仲間がいる場合は合成されたベクトルに従って移動します。

```julia
function rule1(param,var)
    for i in 1:param.N
        for j in 1:param.N
            if i==j;continue;end
            dist=norm(var.p[j,:].-var.p[i,:])
            #ある仲間jとの距離が一定の距離より短ければ
            if dist < param.distThresh
                var.v1[i,:].-=(var.p[j,:].-var.p[i,:])
            end
        end
    end
end
```

### ルール2 Velocity Matching
![boid_rule3.jpg](https://qiita-image-store.s3.amazonaws.com/0/253018/0338281a-b333-f4a1-5469-c32c1b5857ea.jpeg)

Boidは自身の速度を群れ全体の速度に合わせようとします。$i$番目のboidにおけるルール2の計算結果$\boldsymbol{v_2}[i]$は、群れ全体（自身を除く）の平均速度と自身の速度との差です。

```math
\frac{\sum_{i\neq j}^{N} \boldsymbol{v}[j]}{N-1}-\boldsymbol{v}[i]=\boldsymbol{v_2}[i]
```
```julia
function rule2(param,var)
    for i in 1:param.N
        for j in 1:param.N
            if i==j;continue;end
            var.v2[i,:].+=var.v[j,:]
        end
    end
    var.v2.=var.v2./(param.N-1)
    var.v2.=var.v2.-var.v
end
```
### ルール3 Flock Centering

![boid_rule2.jpg](https://qiita-image-store.s3.amazonaws.com/0/253018/167dd43a-c279-e0e9-2d6a-4942d7d0ccda.jpeg)

Boidは群れの中心に向かおうとします。$i$番目のboidにおけるルール3の計算結果$\boldsymbol{v_3}[i]$は、群れ全体（自身を除く）の中心位置と自身の位置との差です。

```math
\frac{\sum_{i\neq j}^{N} \boldsymbol{p}[j]}{N-1}-\boldsymbol{p}[i]=\boldsymbol{v_3}[i]
```

```julia
function rule3(param,var)
    for i in 1:param.N
        for j in 1:param.N
            if i==j;continue;end
            var.v3[i,:].+=var.p[j,:]
        end
    end
    var.v3.=var.v3./(param.N-1)
    var.v3.=var.v3.-var.p
end
```
## 付加事項
boidをより本物らしくするためにいくつかの処理を追加します。これらは必須ではありません。

### 速度制限
本物の鳥は生き物ですから飛行速度には限界があります。また飛行速度がゼロになってしまうのも群れの移動という観点からはあまりうれしくありません。そこでboidの最高速度と最低速度を決めておき、boidの速度がその速度範囲を超えた場合は範囲内に収まるように変化させます。単純な`clamp(速度)`だと速度がガクンと変わって不自然なので現在速度とclampした値の比をかけて連続的に変化するようにします。

### 境界判定
世界の大きさの最小値と最大値を決めておきます。Boidの現在位置が世界の外側であり速度方向が世界の外側をむいているとき、速度方向が反対になるようにします。

## 計算

```julia
using Plots
using LinearAlgebra
using Random
Random.seed!(2018)
gr(size=(300,300),titlefont=font("sans-serif",9))


module Boids
    struct Param
        d::Int64 #次元
        N::Int64 #Boidの数
        distThresh::Float64 #ルール1の閾値
        maxSpeed::Float64 #速度の最大値
        minSpeed::Float64 #速度の最小値
        maxWorld::Float64 #世界の最大値
        minWorld::Float64 #世界の最小値
        
        #R::Float64
        ω1::Float64 #rule1の重み
        ω2::Float64 #rule2の重み
        ω3::Float64 #rule3の重み
    end
    mutable struct Variables
        p::Array{Float64,2} #Boidsの位置
        v::Array{Float64,2} #Boidsの速度
        v1::Array{Float64,2} #rule1の出力結果
        v2::Array{Float64,2} #rule2の出力結果
        v3::Array{Float64,2} #rule3の出力結果
    end
end

using .Boids
d=2 
N=50
distThresh=10.0

ω1=0.1
ω2=0.4
ω3=1/300
       
maxSpeed=4.0
minSpeed=1.0

minWorld=0
maxWorld=150

worldSize=abs(maxWorld-minWorld)

param1=Boids.Param(d,N,distThresh,maxSpeed,minSpeed,maxWorld,minWorld,ω1,ω2,ω3)

p=rand(N,d).*worldSize
v=(rand(N,d).-0.5)

v1=zeros(N,d)
v2=zeros(N,d)
v3=zeros(N,d)

var1=Boids.Variables(p,v,v1,v2,v3)

#出力結果格納変数の初期化
function clear_vec(var)
    var.v1.=0.0
    var.v2.=0.0
    var.v3.=0.0
end    

#rule1 Collision avoidance
#仲間を避ける
function rule1(param,var)
    for i in 1:param.N
        for j in 1:param.N
            if i==j;continue;end
            dist=norm(var.p[j,:].-var.p[i,:])
            #ある仲間jとの距離が一定の距離より短ければ
            if dist < param.distThresh
                var.v1[i,:].-=(var.p[j,:].-var.p[i,:])
            end
        end
    end
end

#rule2 Velocity Matching
#群れの平均速度に合わせる        
function rule2(param,var)
    for i in 1:param.N
        for j in 1:param.N
            if i==j;continue;end
            var.v2[i,:].+=var.v[j,:]
        end
    end
    var.v2.=var.v2./(param.N-1)
    var.v2.=var.v2.-var.v
end

#rule3 Flock Centering
#群れの中心に向かう
function rule3(param,var)
    for i in 1:param.N
        for j in 1:param.N
            if i==j;continue;end
            var.v3[i,:].+=var.p[j,:]
        end
    end
    var.v3.=var.v3./(param.N-1)
    var.v3.=var.v3.-var.p
end

#速度制限
function limit_vel(param,var)
    for i in 1:param.N
        speed=norm(var.v[i,:])
        clamped=clamp(speed,param.minSpeed,param.maxSpeed)
        var.v[i,:].*=(clamped/speed)
    end
end

#境界判定
function check_boundary(param,var)
    for i in 1:param.N
        for n in 1:param.d
            if ((var.p[i,n]<param.minWorld && var.v[i,n]<0) || (var.p[i,n]>param.maxWorld && var.v[i,n]>0))
                var.v[i,n]*=-1.0
            end
        end
    end
end

function update(param,var)
    clear_vec(var)
    rule1(param,var) 
    rule2(param,var)   
    rule3(param,var)
    var.v.+=param.ω1*var.v1+param.ω2*var.v2+param.ω3*var.v3
    limit_vel(param,var)
    var.p.+=var.v
    check_boundary(param,var)
end
const dt=1.0

@time anim = @animate for m in 0:300
    t=round(m*dt,digits=2)
    quiver(var1.p[:,1],var1.p[:,2],
        quiver=(var1.v[:,1].*5.0,var1.v[:,2].*5.0),
        xlab="x",xlims=(minWorld-10,maxWorld+10),
        ylab="y",ylims=(minWorld-10,maxWorld+10),
        leg=false,
        title="Boids,t = $t")
    plot!(var1.p[:,1],var1.p[:,2],seriestype=:scatter,markersize=2)
    update(param1,var1)
    
end
@time gif(anim,"tmp/Boids4.gif",fps=10)
```
## 出力
 9.440260 seconds (71.71 M allocations: 2.217 GiB, 6.52% gc time)
 1.541061 seconds (675 allocations: 38.266 KiB)

概説に示したgif動画が出力されます。

<!--
簡単のため、視野角度やカットオフ距離による制限はなしとします。つまり、Boidたちは360度全方位どんなに遠くにいる仲間も「見る」ことができます。
-->

## 感想
* `quiver`はベクトル場のような格子点から矢印が生えているような図を描くのに使われているのをよく見ます。今回は邪道的に使って移動する矢印を描画するのに使ってます（大きさを分かりやすくするために5.0をかけています）。
* コードの分かりやすさを優先してルールごとにforループを回しています。これを一緒くたにしてやればもっと計算時間を短くできそうな気がします。
* 加速度もいれるともっとリアルになると思います。


## 参考文献
* [C. W. Reynolds, "Flocks, Herds, and Schools: A Distributed Behavioral Model", Computer Graphics, 21(4), 1987, pp. 25-34.] 
(http://www.cs.toronto.edu/~dt/siggraph97-course/cwr87/)
* ["Boids Background and Update" by Craig Reynolds](https://www.red3d.com/cwr/boids/)
* [@takashi "インタラクティブプログラミング - 群れ（boid）を作る"](https://qiita.com/takashi/items/9d684a6e3742a15e8726)
* [Yutaka Kachi, "Boid"](https://www.catch.jp/program/processing.js/boid/boid_102.pde)
* [ワンダープラネットデベロッパーブログ 2015年3月13日号](http://developer.wonderpla.net/entry/blog/engineer/Unity_BoidsSimulation/)
* [JavaScript, HTML5で『群れ』をシミュレーションするレシピ (ボイド)](http://coderecipe.jp/recipe/gRrj53OPQF/)