3. 相互作用を含むモデルを作成する

本節では前節までに作成したモデルをもとに、エージェント間の簡単な相互作用を含むモデルを作成します。

3.1. 作成モデルの概要

鳥がきれいな群れを作って飛び回っている様子を、皆さんも見たことがあると思います。鳥はどのようにして群れたまま飛び続けることができるのでしょうか。どこかにリーダーとなる鳥がいて、指示を出しているのでしょうか。

../../_images/tutorial1_3_1.png

実はリーダーとなる鳥はおらず、鳥の群れは単に個々の鳥が近くの鳥に飛び方を合わせるだけで形成されることが知られています。これを表現したのが、1987年にクレイグ・レイノルズ(Craig Raynolds)によって発表された「ボイドモデル」です。ボイド(Boid)というのは、鳥 (bird) に似たもの (-oid) を短くして造られた言葉です。ボイドモデルはもともとはコンピュータ・グラフィックス(CG)の世界から登場したもので、CGによる群衆行動の表現などに応用されています。

ここではごくごく単純なモデルを作成し、鳥が群れを作って飛ぶ様子を表現してみましょう。

ボイドモデルについては、こちらも参考にしてください。

3.2. 準備

まずは準備として、以下のようなモデルを作ります。

  • シミュレーション開始時に100体の鳥エージェントを生成する

  • それぞれの鳥エージェントはランダムな場所に発生し、ランダムに決められた方向に速度1でまっすぐ飛んでいく

前節で作成したモデルをもとに、ルールエディタを以下のように書き換えます。

Universeのルールエディタ

def univ_init(self):
    create_agt(Universe.oozora.tori, num=100)  # 100体をまとめて生成

toriのルールエディタ

def agt_init(self):
    
    # 初期位置をランダムに設定
    self.x = rand() * 50
    self.y = rand() * 50
    
    # 方向をランダムに設定
    self.direction = rand() * 360
        
def agt_step(self):

    # 前方に1進む
    self.forward(1)

実行してみて、下図のように鳥がランダムに飛び回っていればOKです。

../../_images/tutorial1_3_2.gif

3.3. 周囲の鳥に方向を合わせる

鳥が群れを作る現象を再現するため、鳥エージェントのルールに周囲の鳥を認識し飛び方を合わせるルールを追記します。以下のような流れになります。

  1. 周囲のエージェントを探し、エージェント集合型変数に格納する

  2. 周囲にエージェントがいる場合、そのうち1つのエージェントを選び、自分の飛ぶ向きをそのエージェントに合わせる

これだけのルールで、鳥エージェントが群れを形成します。エージェント集合型変数とは、エージェントの集合そのものを値として持つ変数のことです。

では実装してきましょう。これは鳥エージェントが毎ステップ行うルールですので、agt_stepに記述していきます。まず、周囲のエージェントを探してエージェント集合型変数に格納するためには、make_agtset_around_own関数を利用して以下のように記述します。

def agt_step(self):
    
    # 自分から距離2以内のエージェントをneighborsに格納(自分自身を含まない)
    neighbors = self.make_agtset_around_own(2, False)
    
    self.forward(1)

make_agtset_around_own関数は1つ目の引数に視野を、2つ目の引数に自分自身を含むかどうか(TrueかFalse)を取ります。このように書くことで、自分から2以内にいるエージェントの集合(自分自身を含まない集合)を変数neighborsに格納することができます。

続いて、「周囲にエージェントがいる場合、そのうち1つのエージェントを選び、自分の飛ぶ向きをそのエージェントに合わせる」というルールを記述します。

def agt_step(self):
    
    # 自分から距離2以内のエージェントをneighborsに格納(自分自身を含まない)
    neighbors = self.make_agtset_around_own(2, False)
    
    if count_agtset(neighbors) > 0:  # もし周囲に鳥がいれば
        one = randchoice(neighbors)  # ランダムに1羽を選ぶ
        self.direction = one.direction  # 自分の向きをその鳥に合わせる
    
    self.forward(1)

count_agtsetはエージェント集合の要素数を数える関数です。if count_agtset(neighbors) > 0:で、neighborsの要素数が0でないとき、つまり周囲に鳥がいるときに行う処理に入ります。one = randchoice(neighbors)でneighborsからランダムに1羽選びます。randchoiceはエージェント集合からランダムに1つのエージェントを取得する関数です。最後に、self.direction = one.directionで自分のdirectionに選んだエージェントのdirectionを代入します。つまり、そのエージェントに自分の向きを合わせています。

この状態で実行してみましょう。下図は鳥の数を50にして実行した様子ですが、鳥が群れを作っているのが分かります。周囲の鳥に方向を合わせるという個々のエージェントのルールの積み重ねにより、群れの形成というマクロな現象が再現できました。

../../_images/tutorial1_3_3.gif

3.4. 状況に応じてエージェントの色を変える

マップの出力を見やすくするために、以下のように状況に応じてエージェントの色を変えてみましょう。

  • 群れを形成している場合 → 赤色

  • 群を形成していない場合 → 青色

まずは、ツリー上で鳥エージェントに色を表す変数colorを追加します。

../../_images/tutorial1_3_4.png

続いて、マップの出力設定で変数colorの値に応じて鳥エージェントの色を変えるよう設定します。マップ右上の編集ボタンでマップ出力設定を開き、マップ要素リストの鳥の編集ボタンでマップ要素設定画面を開きます。「エージェント表示色」で「変数指定」を選択し、ドロップダウンで対象の変数にcolorを選択します。

../../_images/tutorial1_3_5.png

最後に、ルールエディタ上で変数colorの値を、周囲に鳥がいるときには赤色、いないときには青色に設定します。周囲に鳥がいないときの処理を書くために、if文にelse節を追加する必要があります。以下のように書いてみましょう。

def agt_step(self):
    
    neighbors = self.make_agtset_around_own(2, False)
    
    if count_agtset(neighbors) > 0:
        one = randchoice(neighbors) 
        self.direction = one.direction
        self.color = COLOR_RED  # 周囲に鳥がいるとき、colorを赤色に指定
    else:
        self.color = COLOR_BLUE  # 周囲に鳥がいないとき、colorを青色に指定
    
    self.forward(1)

COLOR_RED COLOR_BLUEはそれぞれ赤色、青色を表す定数です。self.color = COLOR_REDの行でcolor変数に赤色を代入しています。この行は周囲に鳥がいる場合(neighborsの要素数が0より大きい)の処理の中にありますので、これで群れを形成するときに鳥が赤色になります。else以下で、まったく同じように今度はcolor変数に青色を設定しています。これで、群れを形成していないときに鳥が青色になります。

この状態で実行してみましょう。群れを作っているときとそうでないときで、鳥の色が変化していることが分かります。このように状況に応じて色を変えることで、出力をより見やすくすることが可能です。

../../_images/tutorial1_3_6.gif

色定数は以下のようなものが用意されています。

  • COLOR_RED - 赤色

  • COLOR_GREEN - 緑色

  • COLOR_BLUE - 青色

  • COLOR_YELLOW - 黄色

  • COLOR_CYAN - 水色

  • COLOR_MAGENTA - 紫色

  • COLOR_BLACK - 黒

  • COLOR_WHITE - 白

また、rgb関数を用いれば、色のRGB値を指定して好きな色を作ることも可能です。

self.color = rgb(255, 165, 0)  # オレンジ色

3.5. コンソール出力機能

print関数を用いると、コンソール画面に文字列を表示することができます。

print("Hello!")  # コンソール画面に「Hello」と表示される

hensuu = 3
print(hensuu) # コンソール画面にhensuuの値(3)が表示される

この機能は、シミュレーションの状況を把握するのに便利です。

練習として、群れを作った場合は「[自分のID番号]は群れを作っています」とコンソール画面に表示してみましょう。群れを作った場合の処理に、以下のように追記します。

def agt_step(self):
    
    neighbors = self.make_agtset_around_own(2, False)
    
    if count_agtset(neighbors) > 0:
        one = randchoice(neighbors) 
        self.direction = one.direction
        self.color = COLOR_RED
        print(str(self.id) + "は群れを作っています")  # 周囲に鳥がいるとき、コンソール画面に表示
    else:
        self.color = COLOR_BLUE
    
    self.forward(1)

数値と文字列を一緒に表示する場合は、str関数で数字を文字列に変換し、データ型を文字列に揃えたうえで「+」で繋ぐことに注意してください。print(self.id + "は群れを作っています")と書くとエラーになってしまいます。

これを実行すると、以下のように群れを形成するたびにコンソール画面に表示されます。このように、シミュレーション内で起きていることをリアルタイムで把握することができます。

../../_images/tutorial1_3_7.gif

3.6. 発展問題

これで、初級編としては終了です。余力のある人は、以下の発展問題をやってみましょう。

  1. 周囲の鳥に向きを完全に合わせるのでなく、±5°程度のゆらぎをつける

    • ヒント:self.direction += 10 * rand() - 5でdirectionに-5~5のランダムな値を足すことができます。+=は変数に値を加える操作を表す記号です。

  2. 鳥の速度の初期値をランダムにし、向きだけでなく速度も周囲の鳥に合わせる

    • ヒント:directionで行っていることと同じことをspeedでも行います。速度にゆらぎをつけてもいいでしょう。

  3. 鳥の認識できる範囲をコントロールパネルで操作できるようにする

    • ヒント:視野の広さを表す変数をUniverseに追加し、make_agtset_around_own関数でそれを参照します。