(サムネイル画像:PexelsによるPixabayからの画像を編集)
今回は artisoc Cloud でシュガー・モデルの続きを作っていきます。
前回の記事はこちらです。
1.エージェント用のプログラムを書く
今回はこの場面から始めます。
まずは、creature エージェントを動かすプログラムを書きましょう。
1₋1.エージェントを移動させる
(1) 左のモデルツリーの「creature」をクリックすると、プログラミング画面が creature という別のページになります。
このページでエージェント専用のプログラムを書くことができます。
ちなみに、前回使っていたページは Universe 用のプログラミング画面で、左のモデルツリーの「Universe」をクリックすることで切り替えることができます。
Universe 用とエージェント用のプログラミングの違いや、各プログラムの実行順については以下の記事を参考にしてください。
(2) エージェントを移動させる関数はいくつかありますが、今回は field をマス目状に設定しているので、forward_direction_sqgrid という関数を使います。
1~4行目の
def agt_init(self):
pass
def agt_step(self):
はそのままで、5行目の pass を消して、
def agt_step(self):
#単純移動
self.forward_direction_sqgrid(2 * int(rand() * 4),1)
として下さい。
エージェント自身に関する関数は、多くの場合、関数の前に「self.」をつけて使います。
かっこの中の前半は0~3の整数乱数を2倍した値、つまり0,2,4,6のどれかをランダムにとるという意味です。
これはエージェントが進む向きを数字に当てはめたものであり、
- 原点が左下のとき 0:右、1:右上、2:上、3:左上、4:左、5:左下、6:下、7:右下
- 原点が左上のとき 0:右、1:右下、2:下、3:左下、4:左、5:左上、6:上、7:右上
というルールなので、今回は原点が左下で、かつ上下左右の方向の移動のみ考えることになります。
かっこの後半は移動距離をあらわしており、今回は1マス移動することにします。
これで、単純な移動のプログラミングは完成です。
出力画面で実行してみましょう。
1-2.複雑な移動にしてみる
次はアリに視野をもたせ、その分だけ移動できるようにしましょう。
Universe に変数「view」を追加し、コントロールパネルで設定できるようにします。
コントロールパネルでの名称や入力の種類は自由に設定して構いませんが、対象は view を、整数でとるようにしましょう。
ひとまず、値は2としておきます。
これで view という値をつかうことができるようになったので、先程の「#単純移動」の箇所を for 文を用いて、
#単純移動
for i in range(int(rand() * Universe.view) + 1):
self.forward_direction_sqgrid(2 * int(rand() * 4),1)
このように書き換えます。
view=2 の場合、これで4方向移動を2回繰り返すことができるので、上下左右には2マス、斜めにも1マス移動できるようになりました。
ただし、上→下などと移動した場合、もとに位置に戻ってくるので、まるで移動していないかのような挙動をする場合もあります。
2.モデルの要素を増やす
ここからは、シュガー・モデルの重要なパラメータを組み込んだり、アリの増え方のルールを具体的にプログラミングしていきます。
シュガー・モデルの流れとしては、
- アリが餌(シュガー)を求めて移動する
- アリが餌を食べると体力が上昇する
- 体力が一定以上になると子供を生み、0未満になると死亡する
- 一定期間で餌は復活するので、この状態でのアリの増殖具合を観測する
となります。
2-1.定数や変数を追加する
これから必要になる定数や変数をまとめて追加しておきます。
(1) モデルツリーの Universe から「変数を追加」を選択し、「app」(アリの食欲:appetite)、「food_value」(1マスの餌の量)、「re_food」(餌の復活ステップ数)を追加します。
(2) 追加した3つの変数をすべてコントロールパネルで調整できるようにします。
app, food_value は実数で、re_food は整数で設定しましょう。
入力方法はなんでも構いませんが、直接入力が無難です。
数値は実験の目的により変化しますが、とりあえず app=1.0, food_value=2.0, re_food=4 としておきます。
(3) モデルツリーの field から「空間変数を追加」を選択し、「food」、「food_color」を追加します。
空間変数とは、1種類追加するだけで、空間上のすべてのマスごとに値を入力できるようになります。
food[x,y,layer] , food_color[x,y,layer]という形で使い、今回は layer は常に0でよいので、x座標とy座標を用いた2次元配列であり、これで各マスにある餌の量や出力時に利用する色を表現します。
これらはモデル中に変化する値なので、コントロールパネルで設定する必要はありません。
(4) モデルツリーの creature から変数「assets」(アリの体力)を追加します。
これは1種類追加するだけで、同じ種類のエージェントすべてに変数が追加されます。
よって、アリAの体力は3,アリBの体力は5といったことが表現できます。
2-2.field に餌を設置する
シミュレーションを始める前の初期設定として、field 上のすべてのマス目に food を設置していきます。
(1) 初期状態に関するプログラムは def univ_init(self): の続きに書きます。
food[x,y,0], food_color[x,y,0] は2次元配列なので、2重 for ループを用意します。
今回は50×50マスなので、50という数字を用いて for 文を書いても構いませんが、もしサイズを変えることがあった場合、プログラムも書き直しになってしまいます。
そこで、field の縦横のサイズを取得する関数 get_width_space, get_height_space を利用します。
def univ_init(self):
#creatureの生成
create_agt(Universe.field.creature, num=Universe.n0)
#creatureの初期配置
creatures = make_agtset(agttype=Universe.field.creature)
random_put_agtset_sqgrid(creatures, overlap=True)
#foodの設置
for i in range(get_width_space(Universe.field)):
for j in range(get_height_space(Universe.field)):
Universe.field.food[i,j,0] = Universe.food_value
Universe.field.food_color[i,j,0] = rgb(0,0,255)
肝心の food の値はコントロールパネルで定めた food_value の値にします。
また、餌がある場合は出力上で青色で表現できるよう rgb 関数で指定しておきます。
(2) 次に餌がある場合は、餌も出力上で見えるようにします。
出力画面の field の右のマークから出力マップを編集できます。
右下のマップ要素リストを「空間変数」に切り替えてから「+」を押します。
要素名は何でもよいので、「表示色」の中の「変数指定」を選択し、「food_color」を対象にします。
また、「透明度」の「固定値」を選択し、60ぐらいにしておきます。
あとは「OK」を押せば完了です。
これで、出力したマップ上に餌が青色で表示されるはずです。
しかも、透明度を与えているので、アリ・エージェントの赤色とも区別できます。
(3) ここまでの成果を一度確認しておきましょう。
出力画面で再生ボタンを押し、シミュレーションを実行します。
すべてのマスに餌があるので、背景全体が真っ青になっていれば完成です。
赤いアリエージェントはステップが進むごとに移動しますが、まだ餌を食べるというプログラムを書いていないので、餌の青色は全く変化しません。
3.アリの挙動を複雑化させる
いよいよアリ・エージェントに生き物っぽいプログラミングを実装していきます。
3-1.アリが餌を食べるようにする
まず、アリが餌を食べて体力を増やし、field 上の餌は消えるようにプログラムしましょう。
(1) ルール画面のモデルツリーから creature を選択し、エージェント用のプログラミング画面を開きます。
アリの初期設定として、最初の体力を決める必要があるので、def agt_init(self): の下の pass を消して、
def agt_init(self):
self.assets = rand() * (10 * Universe.app)
とします。
これで、アリ自身の assets(体力)は0~(appの10倍)の間のランダムな値になります。
(2) 実際に餌を食べるのはステップが進んだときなので、def agt_step(self): の続きを次のようにします。
def agt_step(self):
#単純移動
for i in range(int(rand() * Universe.view) + 1):
self.forward_direction_sqgrid(2 * int(rand() * 4),1)
#eat
self.assets = self.assets - Universe.app + Universe.field.food[self.x, self.y, 0]
Universe.field.food[self.x, self.y, 0] = 0
Universe.field.food_color[self.x, self.y, 0] = rgb(255,255,255)
#eat の1行目は、自分自身の assets は app の分減少し、移動後の場に餌があれば food の分だけ増加します。
app は食欲と表現しましたが、1ステップを生きていくための最低限度のエネルギーとも言えます。
2行目で食べてしまったその場の food の値を0にし、3行目で food_color も白色になるようにしています。
また、エージェント用のプログラムで座標を指定する場合、「self.x」とすると、エージェント自身がいるx座標を指定できます。
(3) ここまでの結果を実行してみましょう。
赤いアリエージェントが移動したところは餌が食べられるので、背景が白くなっていると思います。
3-2.エージェントの生成と消滅
ここからはエージェントの生成と消滅をプログラミングしていきます。
(1) 餌が食べらずに体力が減少し、0未満になるとそのアリは死ぬ(消滅)するとします。
def agt_step(self): の続きに kill_agt関数を用いて #death のプログラムを追加しましょう。
def agt_step(self):
#単純移動
for i in range(int(rand() * Universe.view) + 1):
self.forward_direction_sqgrid(2 * int(rand() * 4),1)
#eat
self.assets = self.assets - Universe.app + Universe.field.food[self.x, self.y, 0]
Universe.field.food[self.x, self.y, 0] = 0
Universe.field.food_color[self.x, self.y, 0] = rgb(255,255,255)
#death
if self.assets < 0:
kill_agt(self)
return
kill_agt関数で self を対象とすることで、自分自身を消すことができます。
最後の return は、この #death のif文が実行された場合は以降の命令をすべてスキップして def agt_step(self): 関数全体の処理を終了するという意味です(検証不十分のため、間違いがあれば訂正してください)。
また、del_agt関数でもエージェントを削除できるので、それぞれの挙動の違いに注意してください。
詳しくは参考に挙げている関数仕様のページで確認してください。
(2) 餌を食べたアリは体力を増やしていき、app の10倍を超えたらそのアリは子供を1匹生むとします。
app は1ステップを生きるために必要な最低限度のエネルギーとも考えられたので、エネルギーを十分に蓄えられた個体は子供を生めるという設定になっています。
def agt_step(self): の続きに create_agt関数を使って以下のように #birth をプログラミングします。
def agt_step(self):
#単純移動
for i in range(int(rand() * Universe.view) + 1):
self.forward_direction_sqgrid(2 * int(rand() * 4),1)
#eat
self.assets = self.assets - Universe.app + Universe.field.food[self.x, self.y, 0]
Universe.field.food[self.x, self.y, 0] = 0
Universe.field.food_color[self.x, self.y, 0] = rgb(255,255,255)
#death
if self.assets < 0:
kill_agt(self)
return
#birth
if self.assets > 10 * Universe.app:
child = create_agt(Universe.field.creature)
child.x = self.x
child.y = self.y
child.assets = self.assets / 2
self.assets = self.assets / 2
create_agt関数で生み出すのはこれまでと同種のアリ・エージェントなので、Universe.field.creature を指定します。
ここで child は新しく生まれたアリ・エージェント1体のことを指し、child の位置は親と同じ座標に、assets は親の半分を受け継ぐとします。
そして親自身の assets も半分にしておきます。
(3) ここまで出来たら、再び実行画面で挙動の確認をしておきましょう。
4.餌の復活
今回は最後に、餌が一定期間ごとに復活するようにします。
アリと砂糖では餌が復活するというイメージは持ちにくいかもしれませんが、草食動物と牧草をイメージすれば、冬の間に枯れてしまった草木も夏には復活するというのも違和感はないかと思います。
(1) Universe のプログラミング画面で、def univ_step_end(self): の下の pass を消し、以下のようにします。
def univ_step_end(self):
#(re_food)ステップごとにfoodの復活
if count_step() % Universe.re_food == 0:
for i in range(get_width_space(Universe.field)):
for j in range(get_height_space(Universe.field)):
Universe.field.food[i,j,0] = Universe.food_value
Universe.field.food_color[i,j,0] = rgb(0,0,255)
def univ_step_end(self): は各ステップの最後に実行されるプログラムです。
”一定期間置きに”は、現在のステップ数を count_step関数で取得し、それを re_food (今回は4)の値で割った余りを用いて if文で判定します。
次に2重 for ループですべてのマス目の(x,y)座標に対し、空間変数 food の値を 定数 food_value(今回は2)に戻します。
食べられてしまったマスの food は 0 だったはずですが、これで全マスで餌が復活したことになります。
最後に餌の復活に伴って、food_color も青色に戻しておきます。
(2) 今回はこれで完成です!
実行画面でプログラムを再生してみましょう。
ステップが進むごとに赤色のエージェントの数がどんどん増えていくと思います。
エージェントがいたマス目の青色は消えていればちゃんと餌を食べており、青色が4ステップごとに復活していれば餌の復活も問題ないでしょう。
まとめ
今回はシュガー・モデルで一番重要になる、エージェントの行動に関するプログラムを中心に進めていきました。
次回でいよいよ完成です!
次回はエージェントの数をカウントし、それをグラフ化するなど、ロジスティック方程式との対比などにも利用できるようにしていきます。