Repast Simphonyは、オープンソースのエージェント・シミュレーション用のツールです。Java、C++、Pythonのそれぞれのツールがありますが、このチュートリアルでは、Javaのツールを用いて説明をします。
このチュートリアルでは、以下の環境を想定します。
なお、githubからダウンロードするWindows版のRepast SimphonyはEclipseとともにダウンロードされます。
ダウンロードしたexeファイルをダブルクリックしてインストールします。
例として、消費者が商品を探して、自分の基準にあった商品を購入するという動きを実現してみましょう。具体的には以下のような状況を実現してみます。
消費者は、商品を探索し、購入しようとしている。消費者は、商品の二つの属性(例:デザイン、価格)を商品選択基準としており、自身が持っている評価値と、両者の属性を総合して得られる個々の商品の属性値を比較し、その属性値が自分の評価値以上の商品を購入する。商品は市場に疎らに(ランダムに)存在しており、消費者は探索範囲内にある、条件を満たした商品を購入するが、一回に購入できる商品は一つであるとし、複数条件を満足するものがある場合には、最も条件が良いものを選択するものとする。消費者はこの商品を一定間隔で繰り返し購入する。 |
---|
上記の状況を評価するのに、以下のようなモデルを考えてみます。
選択済みエージェントを利用する理由は、エージェントの数を計測することで、購入数を得ることができるためです。
エージェントの属性と振る舞いは以下のように考えることとします。
属性 | 振る舞い | |
---|---|---|
消費者エージェント | 評価値(固定値) 仮想市場での位置(グリッド中央) |
商品探索と購入 |
商品エージェント | 属性1(1~10ランダム) 属性2(1~10ランダム) 仮想市場での位置(グリッド内ランダム) |
|
選択済み商品エージェント | 仮想市場での位置(グリッド内ランダム) |
消費者の商品選択ですが、ここでは、単純に、2つの商品属性値の和と、評価値を比較し、属性値の和の方が大きい場合に、購入することとします。
それでは実際にシミュレーションを行ってみましょう。
Repastを立ち上げて、ナビゲーションペインで右クリックして、Newから、Otherを選択し、Repast Simphonyから、Repast Simphony Projectを選択します。
Nextをクリックし、Project名を入力する。ここでは、Mktng00と言う名前のプロジェクトを作成します。
続いてNextを押下し、最後にFinishを押下して、プロジェクトを作成します。
まず、商品エージェントを作成します。ナビゲーション部分のMktng00フォルダ内にsrcフォルダがあり、そこにmktng00フォルダ(パッケージ)があるので、それをマウスで選択し、クラスを作成するボタンを押下します。ここでは、Product.javaという名前のクラスを作ります。
商品エージェントは、仮想空間に存在し、2つの製品属性を持っているエージェントとして作成します。仮想空間としては、Gridというクラスを使用します。
商品の属性は消費者が評価する際に取り出す必要があるので、値を取り出すようにメソッドを用意します。また、必要なライブラリは、Eclipseが推奨してくれるRepast Simphonyものをimportします。
package mktng00;
import repast.simphony.space.grid.Grid;
public class Product {
private Grid<Object> grid;
private int att1; // 製品属性1
private int att2; // 製品属性2
public Product(Grid<Object> grid, int att1, int att2){
this.grid = grid;
this.att1=att1;
this.att2=att2;
}
// 属性1
public int getAtt1() {
return att1;
}
public void setAtt1(int att1) {
this.att1 = att1;
}
// 属性2
public int getAtt2() {
return att2;
}
public void setAtt2(int att2) {
this.att2 = att2;
}
}
次に、消費者が商品を評価して、属性を評価して、選択をしてしまった後の商品用のエージェントを作成します。これは、先の商品エージェントと同じですが、属性を処理したり、取り出したりする必要がないので、コンストラクタには、仮想空間に存在するために必要なクラスのみを定義します。選択済み商品エージェント用のクラスをSelectedProduct.javaとします。このようなエージェントを用意しておくことで、商品がいくつ売れたのかをエージェント数を数えることで知ることができます。
package mktng00;
import repast.simphony.space.grid.Grid;
public class SelectedProduct {
private Grid<Object> grid;
public SelectedProduct(Grid<Object> grid){
this.grid = grid;
}
}
次に、消費者エージェントを作成します。ここでは、Consumer.javaという名前にしました。この消費者エージェントも、仮想空間に存在するエージェントです。仮想空間は、商品エージェントと同じです。また、自分の評価値用の属性値を用意しておきます。
package mktng00;
import java.util.List;
import repast.simphony.context.Context;
import repast.simphony.engine.schedule.ScheduledMethod;
import repast.simphony.query.space.grid.GridCell;
import repast.simphony.query.space.grid.GridCellNgh;
import repast.simphony.space.grid.Grid;
import repast.simphony.space.grid.GridPoint;
import repast.simphony.util.ContextUtils;
public class Consumer {
private Grid<Object> grid;
private int preference; // 評価基準
public Consumer(Grid<Object> grid, int preference){
this.grid = grid;
this.preference = preference;
}
// 評価基準
public int getPref() {
return preference;
}
public void putPref(int preference) {
this.preference = preference;
}
// 以下にメソッドを消費者の行動に利用するメソッドを追加
}
今回使用しているRepastでは、Tickという単位で時間を動かしてエージェントを動作させます。動作のさせ方は、いくつかあるようですが、ここで は、ScheduledMethodというアノテーションを使用します。ここでは、evaluateというメソッドを作成し、消費者の周りに存在している 商品エージェントの属性を評価するという処理を記述します。
@ScheduledMethod(start = 1, interval = 5)
public void evaluate(){
// 自身のいるグリッド上の場所を取得
GridPoint pt = grid.getLocation(this);
// 自分の周囲にある商品(Productエージェント)を探す
// GridCellNghで自セルの周りのセルを探索対象とする。
// 3,3は自分のセルから上下に3セルずつを意味する。
GridCellNgh<Product> nghCreator
=new GridCellNgh<Product>(grid, pt,Product.class,3,3);
// 自セルの周りの指定された範囲セルにあるProductエージェントをListに格納する。
List<GridCell<Product>> gridCells=nghCreator.getNeighborhood(true);
// 属性評価の和が最も高いProductを選択する
GridPoint prefProd = null;
int tmp=-1; // 比較評価用の仮変数
int tmpHigh=-1; // 比較評価用の仮変数
// 周りのセルを格納したListから一つずつProductエージェントを取り出して評価する
for(GridCell<Product>cell:gridCells){
// 一つずつ属性値を取り出す
for(Product prd:cell.items()){
tmp=prd.getAtt1()+prd.getAtt2();
}
// 取り出した属性値とそれまでの最高の属性値を比較して、高ければ、それを選択対象にする
if(tmp>tmpHigh){
prefProd = cell.getPoint();
tmpHigh=tmp;
}
}
// 選択対象があれば(tmpHigh≥prefermce)ならば、
// changeAgentでそのセルにあるProductエージェントをSelectedProductに変更するメソッド(changeAgent)を呼ぶ
if(tmpHigh>=preference) {
changeAgent(prefProd);
}
}
ScheduledMethodはパラメータとして、開始時間(start)と、時間間隔(interval)をとることができますが、ここでは、開始時間を1、時間間隔を5としておきます。
メソッドの中の最初のインスタンスとして、GridPointというクラスのインスタンスを定義します。これは、このエージェントが存在しているグリッド上のセルの変数です。
次に、自分が存在しているセルの周りのセルに存在している商品エージェントを取得するために、GridCellNghクラスのインスタンスを定義します。 このクラスのパラメータとしては、グリッド変数、グリッド上の位置、対象となるクラス、そして、中心からのセル距離を設定します。上のコードにある、 3,3がセル距離で、中心から、上下左右に3セル分の範囲、つまり、7×7の49セルが対象になるという意味です。
次に、Productのインスタンスが存在するセルのListクラスを定義します。これは、先に定義したクラスに、getNeightborhoodメソッド(引数:true)を適用することで作成することができます。
さて、ここまでで、List変数である、gridCellsに、商品が存在しているセルの情報を格納することができました。次に、この中から最も属性値の和が高いものを見つける処理を記述します。まず、該当するセルの値を入れるGridPointクラスのインスタンスをprefProdとして定義、属性値 の和の比較をするための整数型の変数をtmp、tmpHighとして定義します。
以降、最初のfor文は、gridCells分だけ繰り返し処理をするという意味です。二つ目のfor文は、最初のfor文の各cellにある商品を取り 出して処理をするという意味で、ここでは、商品の2つの属性値をgetメソッドで取り出して、tmpという変数に入力をしています。
その後のif文では、tmpの値をそれまでの最大の値と比較して、それまで最大よりも大きな値だったら、新しい値に置き換えて、そのセルを消費者が選択するセルとするという処理をします。
最後に、tmpHighの値が消費者の評価基準値以上だったら、その商品を消費者が選択するというメソッドである、changeAgentを呼ぶという処理を実行します。
ここまでが、evaluateという、時間ごとに実行されるメソッドですが、最後に、消費者が選んだ商品を選択済みにするというメソッド(changeAgent)の説明をします。
changeAgentメソッドでは、選択されたセルにあるProductエージェントを消滅させて、代わりに選択済み商品エージェントを配置することが必要になります。
public void changeAgent(GridPoint pt){
// 指定のグリッドにいるオブジェクトを取り出す
Object obj=grid.getObjectAt(pt.getX(),pt.getY());
// コンテキストからobjを除去する
Context<Object> context = ContextUtils.getContext(obj);
context.remove(obj);
// 同じ場所にSelectedProductを配置する
SelectedProduct selected=new SelectedProduct(grid);
context.add(selected);
grid.moveTo(selected, pt.getX(),pt.getY());
}
ここでは、最初に、メソッドの引数としてあたえられたGridPointインスタンスから、対象の商品があるセルにあるObjectをobjとして定義します。続いて、そのobjを消去する処理を記述します。
続いて、新たに、SelectedProductクラスのインスタンスを生成し、もともと商品インスタンスがあったグリッドに、新しいSelectedProductクラスのインスタンスを配置します。
以上で、エージェントシミュレーションに必要となるクラスのコーディングは終了です。以降、シミュレーション起動時の初期値の設定を行うクラスの説明をします。
初期化を行うクラスは、RepastのContextBuilderというクラスを拡張して作成します。手順は以下のようになります。
図 ContextBuilderの作成(2)
すると、以下のようなスタブが現れます。
ここで、ContextBuilder<T>
となっているところのTをObjectに変更します。また、MktngBuilder00のところにマウスを持っていき、そこで、右クリックをして、Source > Override/Implement methodsを選択します。
新たに開いたウィンドウでOKを押下すると、それまであったエラーはなくなります。
ContextBuilderは、その名の通り、Contextを作ります(Buildします)。Contextは、エージェントの集まりです。そして、そのエージェントに関連して、Projectionという構造も取ります。この例では、Gridがprojectionになります。では、実際にContextとProjectionをコーディングしてみましょう。
ここでも、必要に応じてライブラリをimportする必要がありますが、同名のクラスがありますので、正しくコードを入力してもエラーが消えない場合には、importするライブラリを変更してみてください。(例えば、WrapAroundBordersは、repast.simphony.continuous.WrapAroundBordersではなく、repast.simphony.space.grid.WrapAroundBordersを使う、等です)
public class Mktng00Builder implements ContextBuilder<Object> {
@Override
public Context build(Context<Object> context) {
context.setId("Mktng00"); // コンテキスト名。context.xmlと同一である必要がある。
// Gridの生成。ここでは、11×11のグリッドの空間を作成。WrapAroundBorderは空間の端が反対の端とつながっている空間
GridFactory gridFactory=GridFactoryFinder.createGridFactory(null);
Grid<Object>grid=gridFactory.createGrid("grid", context,
new GridBuilderParameters<Object>(new WrapAroundBorders(),
new SimpleGridAdder<Object>(),
true,11,11));
// 1つの消費者エージェントを仮想空間の中央に作成、基準値は7とする
Consumer cons1=new Consumer(grid, 7);
context.add(cons1);
grid.moveTo(cons1, 5,5);
// 10個の商品エージェント作成
int prodCount=10;
// 2つの商品属性を1~10の間でランダムに付与
for(int i=0;i<prodCount;i++){
// 作成した商品エージェントをランダムに配置
boolean posDuplicate = true;
while(posDuplicate){
int x = RandomHelper.nextIntFromTo(1, 10);
int y = RandomHelper.nextIntFromTo(1, 10);
int tmpCnt=0;
for(Object o: grid.getObjectsAt(x,y))tmpCnt++; // 移動予定先にエージェントが配置済みかチェック
if(tmpCnt==0){
// セルが空ならその場所に置く
int att1=RandomHelper.nextIntFromTo(1, 10);
int att2=RandomHelper.nextIntFromTo(1, 10);
Product prod = new Product(grid, att1, att2);
context.add(prod);
grid.moveTo(prod, x, y);
posDuplicate = false;
}
}
}
RunEnvironment.getInstance().endAt(51); // シミュレーションの停止条件(Tick値)
return context;
}
}
まず、ContextにIDを付与します。これは、Mktng00.rs内のcontext.xmlの中のIDと同一なものにする必要があります。そして、GridFactoryクラスを使ってGridを作成します。ここでは、エッジがつながった、縦横11マスのグリッドをgridという名前で作成しています。
以降のコードでは、1つの消費者エージェントを仮想空間の中央に配置するとともに、10個の商品エージェントを、仮想空間にランダムに配置するという処理を行っています。なお、消費者エージェントについては、基準値を7としています。また、商品エージェントにおいては、2つの商品属性を1~10の間でランダムに取るような処理も行っています。同じ場所に商品が配置されないように、セルに既存のエージェントがあるか確認してから配置するコードも入れてあります。
また、このままではシミュレーションが永遠に終わらないので、シミュレーションのクロック(Tick)の上限を定めておきます。ここでは、51までTickが動いたら、終了することとします。returnの上の行がそれに該当します。
シミュレーションを動かす前に、先ほど少し言及した、Context.xmlを作成します。このContext.xmlはシミュレーションを動かす際に、どのような画面を作るかを設定するためのものです。Context.xmlはMktng00.rsフォルダーの中にあるので、フォルダーを開いて、context.xmlをダブルクリックして、ファイルを開きます。
ここに、先ほど説明したように、Gridのprojectionを追加しておきます。
<context id="Mktng00" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://repast.org/scenario/context">
<projection type="grid" id="grid"/>
</context>
以上でプログラムは完成です。
では、実際にプログラムを動かしてみましょう。Runの三角ボタンをクリックして、Mktng00 Modelを選択します。
しばらくすると、下のような画面が開きます。ここから、表示するディスプレイなどの設定を行っていきます。
図 プログラムの実行(2)
まず、Scenario TreeのData Loaderノードを右クリックして、Set Data Loaderを選択します。そこで、開くSelect Data Source Typeウィンドウにおいて、Custom ContextBuiler Implementationを選択して、Nextを押下します。次の画面で、コンボボックスにMktng00.Mktng00Builderが表示されているはずですから、そのままNextを押下して、最後の画面でFinishを押下します。
次に、Displayボタンを右クリックして、Add displayを選択します。新たに開いたウィンドウでは、ウィンドウ名をGrid Displayとして、gridだけを左の窓から、右の窓に移し、Nextを押下します。
次の画面では、Consumer、Product、SelectedProductを左の窓から、右の窓に移し、Nextを押下します。次の画面で、先ほど選んだ三種類のエージェントの画面上でのスタイルを選択します。スタイルを変更するためには、変更したいエージェントが選ばれた状態で、画面右の四角ボタンを押下します。
ここでは、
としました。
残りは特に設定する必要がないので、Nextを数回押下して、最後にFinishを押下することで、シミュレータの設定は終わりです。この状態を保存しておきます。保存するには、メニューでFile>Saveで保存するか、ウィンドウのフロッピーディスクマークを押下することで実行できます。
では、実際に、シミュレーションを動かしてみましょう。動かすためには以下の3つのボタンを利用します。
初期化ボタンを押下すると以下のような画面が表示されます。
ここで、連続動作ボタンを押下すると、中心の赤い四角の周りの青色の丸が一つ黄色に変わるはずです。これは、消費者から、上下左右に3マス以内にある商品のうち、商品属性の値の和が最も大きく、基準を超えたものが、一つ消費者に選択された、ということを意味します
その後、範囲内の青い丸のうち、基準値を超えているものが黄色になり、それ以上は何も変化しなくなります。
この状態で、シミュレーションを初期化すると、商品の配置が換わり、また、全部の商品が非選択の状態になります。
以上で、Repast Simphonyの簡単な使い方の説明は終了します。ここでは、消費者は中央に固定したままでしたが、消費者をグリッド上で動かしたり、あるいは、属性データによって、選択式にしたりすることも、プログラムを変更することで実現できます。また、消費者の数を増やして、より市場環境に近づけたりすることも可能です。
しかし、このシミュレーションは、確率的な動きのうち、一回だけの結果ですから、実際には、複数回実施し、期待値を求めるなどする必要があります。いわゆる、モンテカルロシミュレーションです。Repast Simphonyではバッチ処理をすることで、モンテカルロシミュレーションも実現できますが、そのやり方については、別のチュートリアルで説明します。
このシミュレーションの場合には、エージェント数が少ないので、見て、いくつのProductが選択されたか確認できるのですが、エージェント数が多くなるとなかなか数えるのが難しくなります。エージェントの数は、Data Setという単位で数えることができますので、ここでは、そのやり方も説明します。
まず、実行画面の「Data Sets」を右クリックして、「Add Data Set」を選択します。開いたが面では、「Data Set Id」と「Data Set Type」が選択できますが、デフォルトのままで大丈夫ですので、そのまま、「Next」ボタンを押下します。
次の画面で記録したいデータを選ぶのですが、ここでは「Method Data Source」タブを選択します。その後、「Add」ボタンを押下し、画面上方の表に行を追加します。ここでは、Productエージェントと、SelectedProdエージェントを数えようと思いますので、それぞれ、名前(Source Name)とエージェント(Agent Type)を設定します。Agent Typeは、該当のセルでダブルクリックすることで候補が表示されるので、該当のものを選択します。「Aggregate Operation」の値は、「Count」としておきます。
これらを設定したのち「Next」を選び次の画面に移行します。次の画面はデフォルトのままでよいので、そのまま「Finish」を押下して、終了します。実行画面の「Scenario Tree」の「Data Sets」のところに、追加したData Setが表示されていると思います。
次に、エージェント数の推移をグラフ化する部分の説明をします。グラフの設定は「Charts」から行います。先ほど同様に右クリックし、表示されるメニューから「Add Time Series Chart」を選択します。開いた画面ではNameと、Data Set が選択できます。Data Set からは、先ほど作成した「A Data Set」を選択します。名前を変えた人は、自分がつけたデータセット名を選択します。
「Next」を押下すると、表示したいデータ項目の選択画面が表示されます。ここでは、「Product」と「SelectedProd」の両方を選択しておきます。
「Next」を押下すると、グラフの設定画面が表示されますので、「Title」と「Y-axis」を書き入れます。ここでは、「Product-SelectedProd」「No. of agents」と書いておきます。
最後に「Finish」を押下すると、先ほど同様に、実行画面の「Scenario Tree」の「Charts」のところに、追加したChartが表示されていると思います。
この状態で実行してみましょう。結果表示部に「Product-SelectedProd」というタブが表示されていると思います。
シミュレーションを動かしてみると、この例の場合には、図のように3つのProductが選択されましたので、Productエージェントが3つ減り、SelectedProdが3つ増えています。
Grid表示でも同じ結果となっています。
Chartで描画したデータは当然シミュレーターの中にありますので、そのデータを取り出すこともできます。データの取り出しは、Scenario Treeの「Text Sinks」から行います。
これまでと同様に、コンソールの「Text Sinks」を右クリックし、「Add File Sink」を選択します。開いた画面では、Nameの設定と、Data Set IDの選択ができます。ここでは、Data Set IDとして、先ほど作成したデータセットを選択します。
すると、その下に、Data Setで作成したデータ項目が表示されますので、取得したいデータ項目を選択肢、左側から、右側に移します。
「Next」を押下すると、保存するファイル名と区切り(デリミタ)を選択する画面になりますが、ここはデフォルトのままで問題ありません。「Insert Current Time into File Name」はチェックしたままにしておきましょう。そうしないと、ファイルが上書きされていしまいます。ここで「Finish」を押下すれば、設定は終了です。
この状態で、設定を保存しするのを忘れないようにしましょう。
では、実際に動作させてみます。データは、Repast Simphonyのこのプロジェクトのフォルダー内に保存されます。今回は、2回動作させてので異なる時間が入ったファイルが2つ保存されているのが確認できます。
今回は、テキストファイルで保存したので、そのままエディタで開いてみると、ちゃんと値が保存されいて右ことが確認でいます。
参考文献:北中英明、複雑系マーケティング入門、共立出版
(2012年4月19日作成、2022年12月28日改版、2024年3月20日改版)