1. エージェントを様々に動かす

ここでは羊を追いかける狼をモデル化を通して、空間の上でエージェントを様々に動かす技法を学びましょう。

第2部で作成する狼と羊モデルは、捕食 - 被食関係のロトカ・ヴォルテラの方程式に基づいたモデルとなっています。この方程式はロトカとヴォルテラという学者が1920年代に独立に提唱したと言われています。
モデル作成に入る前にロトカ・ヴォルテラの方程式について簡単に説明します。

被食者は、自律的に増えていきます。捕食者がいなければ幾何級数的に増えます。捕食者がいるとき、被食者が多ければ沢山食べられてしまうし、捕食者が多くてもやはり沢山食べられてしまいます。
捕食者は、被食者がいなければ、食べ物がないので次第に減っていきます。被食者がいれば、それを食べて増えていきます。 被食者が多ければ多いほど、沢山増えていきます。
被食者と捕食者にこのような関係があるとき、捕食者と被食者の両方がある程度生きていると、どちらも増減を繰り返します。 捕食者が増えると被食者が減り、やがて捕食者も減っていきます。すると被食者が増えるようになり、やがて捕食者も増えるようになります。
このように、ロトカ・ヴォルテラのモデルは、捕食者の個体数と被食者の個体数とのマクロな関係に注目し、個体数の振動を導き出しました。
詳しい説明はMASコミュニティのページも参考にしてください。

第2部で作成するモデルはごく簡易的にしたものですが、最後の発展問題まで取り組めば生態系における個体数の振動を観察できるようになっていますので、是非最後まで取り組んでみてください。 なお、チュートリアルに沿って作成したサンプルモデルのリンクは第2部の末尾に掲載していますが、まずは自力でトライしてみましょう。

モデル作成にあたって新しい技法がたくさん登場しますが、すべてのルール表記を覚えるというよりは、自分のモデル作成の際に第2部で学ぶ動かし方をレパートリーとして用いられるようにすることを目指しましょう。
順序としては以下のようになります。

  1. 速さや方向を変えながら動くエージェント

  2. ループしない空間での動き

  3. 目的地を目指すエージェント

  4. 他のエージェントを追いかける / 消す

また、第2部ではルールを様々に変化させて羊や狼の動きを変えていきます。
ルールを変える前のモデルを残しておきたい場合は「継承して新規作成」し、別名で保存してください。

1.1. 準備

まずは、新しいモデルを作成します。
モデル名は「狼と羊」などとしておきましょう。

ここまでのチュートリアルを参考に、ツリーに空間、エージェント、変数を加えていき、モデルの基本構造をつくっていきます。
Universeの下に空間「plain」(空間の大きさは50×50、「ループする」のまま)をつくり、そこにエージェント種別「wolf」と「sheep」を追加します。wolfの下には1ステップあたりの移動速度を表す変数「speed」をつくっておきます。
また、マップ出力設定でwolf, sheepが出力されるように設定しておきましょう。この時、エージェント表示色を変えておきましょう。固定色の右側の数字を設定するか、その横の色見本をクリックすれば実際の色を見ながら指定することができます。見本ではwolfを赤、sheepを青にします。

../../_images/tutorial2-1-1.png

以上で準備は完了です。

1.2. 速さや方向を変えながら動くエージェント

まずは復習がてらエージェントがステップごとに速度を変えながら動くルールを書いてみましょう。
ここでは狼に10匹登場してもらいます。Universeのルールエディタを開き、下のようにルールを書きましょう。

def univ_init(self):
    create_agt(Universe.plain.wolf, num=10)

今度は狼が1ステップ0.1の加速度でspeedを0.0から3.0まで徐々に加速しつつ、突進していくルールを書いていきます。エージェントの初期位置とdirectionはagt_initでランダムに決めておきます。wolfのルールは次のようになります。

def agt_init(self):
    self.x = rand() * 50
    self.y = rand() * 50
    self.direction = rand() * 360
    self.speed = 0
    
def agt_step(self):
	# speedが3以下だったら0.1加速
	if self.speed <= 3:
		self.speed += 0.1
	self.forward(self.speed)

ここで新しい表現が登場しました。 self.speed += 0.1部分です。これはself.speed = self.speed + 0.1と同義です。
プログラミングでは自分自身に足したり引いたりして代入することがとても多いので、同じ変数を二度書かなくても良いように+=という演算子が用意されています。足し算以外にも下記のような書き換えができます。(本当はもっとたくさんあります。気になる方は「Python 代入演算子」と検索してみましょう。)

a += b        # a = a + b に同じ
a -= b        # a = a - b に同じ
a *= b        # a = a * b に同じ
a /= b        # a = a / b に同じ

-=*=はこの後登場するので覚えておきましょう。

次に、左右30度の範囲でランダムに進む方向を変えるルールを書きます。ここではturn関数を使います。例えば下記のように使うことができます。

self.turn(30) # 左回りに30度向きを変える(左下原点のとき)

今回は先ほど書いたforward(self.speed)の直前に、方向を変えるルールを挿入します。

self.turn(rand() * 60 - 30) # 左右30度の範囲でランダムに方向を変える

ここで、turn()の引数(rand() * 60 - 30)に注目します。rand() * 60は0以上60未満の乱数です。したがって、rand() * 60 - 30は−30以上+30未満の乱数になります。
一般的に、rand() * 2N − Nという表現は、±Nの範囲の乱数値です。turn()と組み合わせると、左右N度の範囲でランダムに方向を変える便利な技法になります。

ここまでルールが書けたら一度保存をして実行してみましょう。
10頭の赤い狼が縦横無尽に空間を動き回れば成功です。

1.3. ループなしの空間

それでは、空間のプロパティで「ループする」のチェックを外すとどうなるでしょうか。空間plainのプロパティを開き、「ループする」のチェックを外し、ループしない空間にしたモデルを実行してみましょう。

../../_images/tutorial2-1-1.gif

空間に境界が設定されたことで、上下左右の端点に至ったwolfはこれ以上前進できなくなります。今回のルールでは、ステップごとにwolfの進む方向が変化するようにしているので、しばらくすると再び前進を始める狼も出てきますが、一般的にはこのようにループしない空間では、エージェントに空間の境界を認識させ、端点に至った場合には別のルールを実行させる必要が生じます。

まずは、自分が空間の端にいるかどうかを認識させる方法について学びます。 実は、forward関数の機能はエージェントを前進させるだけではありません。エージェントを前進させたうえで、指定された距離を進めた場合には-1を、進めなかった場合には進めなかった距離を返す関数なのです。
例えばforward(5)として前進させたけれど空間の端にぶつかって実際には2.15しか進めなかった場合、forward(5)は2.85を返します。

このことを利用してエージェントが空間の端にいる場合は別の動きをするルールを書くことができます。
今回は狼が空間の端に来たら反転(180度回転)する動きを書いてみます。wolfのルールのforward(self.speed)を消し、以下に書き換えてみてください。

# 前進するが,空間の端なら反対を向く
if self.forward(self.speed) != -1: # 端にぶつかった場合
	self.turn(180)

実行して、狼が壁で行き止まらずに動き続ける様子を確かめましょう。

../../_images/turorial2-1-1-2.gif

1.4. 目的地をめざす

続いては特定の目的地や標的を目指すエージェントの移動を考えましょう。
まずは、10頭の狼が草原の中央(x=25, y=25)を目的地として直進する動きをルールに書いていきます。

ここでは新しくget_direction関数が登場します。これは地点1と地点2の角度を計算する関数で、次のように書きます。

get_direction(地点1のx座標, 地点1のy座標, 地点2のx座標, 地点2のy座標, 対象となる空間)

この関数を利用してwolfのdirectionに、wolfと目的地の角度を代入します。agt_stepのルールを下記のように書き替えましょう。

def agt_step(self):
	# 目的地の方向に向く
	self.direction = get_direction(self.x, self.y, 25, 25, Universe.plain)
	self.forward(0.5)

このルールはagt_stepに書くことで、目的地を通り越した狼も反転して再び目的地の方向に体を向けます。
ルールが書けたら実行してみましょう。

../../_images/tutorial2-1-2.gif

1.5. エージェントを追いかける / 消す

ここまでエージェントの様々な動かし方を学びました。
ここからは草原という一つの空間で狼と羊の両方が動くモデルを作成していきます。

まず、羊を50匹登場させましょう。羊は±60度の範囲で向きを変えながら毎ステップ1ずつ進むとします。
univ_initとsheepのagt_init、agt_stepに下記のように書き加えておきます。

def univ_init(self):
    create_agt(Universe.plain.sheep, num=50)
def agt_init(self):
    self.x = rand() * 50
    self.y = rand() * 50
    self.direction = rand() * 360
def agt_step(self):
	self.turn(rand() * 120 - 60)
	# 壁にぶつかったら体の向きを90度変える
	if self.forward(1) != -1:
		self.turn(90)

今回の狼の基本的な動きは次の通りです。周囲5の範囲内に羊を見つけるとそれを標的にして体の向きを変え、加速しながら直進していきます。標的に接近した場合、狼は移動をやめ、羊を食べてしまいます。
狼の標的はステップごとに決めるとし、周囲に複数の羊がいる場合はランダムに選んだ1匹を標的とします。

このモデルでポイントとなる部分は、標的の方向に体を向けるという点と、羊を食べる(エージェントを消す)という点です。
まず、標的の方向に体を向けるというルールの書き方から解説していきます。

標的の羊を決めるには周囲のエージェントからランダムに一匹選部必要があります。この時に便利なのがrandchoice関数です。これはエージェント集合からランダムに1つのエージェントを取り出すという関数です。以下のようにして使います。

# エージェント集合peopleからランダムに1つのエージェントを取り出し変数oneに格納する
one = randchoice(people)

ここで登場する関数はturn_agt関数です。エージェントの向きを変える関数として、第2部で登場したget_directionもありますが、エージェントの方向を向く場合はturn_agtを使うと簡単です。例えば、次のように書くことでエージェントoneの方向を向くことができます。

self.turn_agt(one)

次に、エージェントを消すという関数についてです。ここではdel_agt関数を使います。 エージェントoneを消す場合は次のように書きます。

del_agt(one)

また、羊に接近したら捕食するというルールの記述のためには羊と狼の距離を測る必要があります。2つのエージェントの距離を取得するための関数はmeasure_agt_distance関数です。
例えばエージェントtaroとhanakoの距離を取得し、distanceに代入するルールは以下のように書きます。

distance = measure_agt_distance(taro, hanako)

新しく登場する関数を学んだところで実際にルールを書いていきます。wolfのルール、agt_stepの中身を次のように書き換えてみましょう。

def agt_step(self):
	sheep_set = self.make_agtset_around_own(5, False, agttype=Universe.plain.sheep)
	num = count_agtset(sheep_set)
	if num == 0: # 標的がいなければうろうろする 
		self.turn(rand() * 60 - 30)
		self.speed = 0.5
	else: # 標的がいれば
		one = randchoice(sheep_set)
		self.turn_agt(one) # 体を標的の方向に向ける
		# 標的がすぐ近くなら、立ち止まって捕食する
		if measure_agt_distance(self, one) < 1:
			self.speed = 0
			del_agt(one)
		# 離れていれば限界速度まで加速
		elif self.speed <= 3:
			self.speed += 0.1
	if self.forward(self.speed) != -1:
		self.turn(90)

それでは、保存して実行してみましょう。狼が羊を捕食する様子が観察できたでしょうか。

../../_images/tutorial2-1-3.gif