Mesaでマルチエージェントシミュレーションを試してみる
米国の経済学者トーマス・シェリングが1971年に発表した論文"Dynamic Models of Segregation"のなかで,エージェント型シミュレーションの手法を用いて人間社会のなかに人種によるすみわけが発生する理由を提示しました. この研究は戦略理論の発展に大きな影響を与えたといわれています.シェリングは2005年にノーベル経済学賞を受賞しています.この研究以降社会・経済をはじめ多くの分野でエージェントベースシミュレーションの適用が進んでいます.
Mesa(Multi-Agent Simulation Environment)は,エージェントベースのモデリングとシミュレーションを目的としたPythonライブラリです.このライブラリはオープンソースとして提供され現在も機能強化が継続されています(Webサイトははこちら).本稿では有名なエージェントシミュレーションの事例であるSugarscapeを題材にしてMesaの基本的な機能と動作を確認していこうと思います.
Sugarscapeとは?
Sugarscapeは,Joshua M. Epstein と Robert Axtell による著書「Growing Artificial Societies by Rob Axtell and Joshua Epstein, 1996」(邦題:「人工社会」 服部・木村訳 共立出版)のなかで取り扱われているエージェントベースの社会シミュレーションモデルです.著書の一人(Robert Axtell)が登場する解説動画がWebサイト(COMPLEXITY EXPLORER by SANTAFE INSTITUTE)で公開されています.またSugarscapeのキーワードでネットを検索すると様々な情報が得られると思います.そしてMesaのサンプルコード集のなかでSugarscapeモデルが取り扱われているので合わせて参照ください.
このモデルは2次元の格子空間のなかに,上下左右に移動し資源(シュガー,スパイス)を探索・摂取しながら生活する多数のエージェントを配置します.そしてエージェント間の相互作用等必要な動作規則を定めて1ステップずつ時間進行させ,格子世界の様子を観察します.非常に単純なシステムのようにも思えますが,結果は著書に書かれているように驚くほど多様な構造が創発され変化していくのです.
以下ではSugarscapeを参考としてMesaを用いたシンプルなマルチエージェントシミュレーションを組み立てていきます.シミュレーションでは以下の特徴を実現します.簡単化のため本家のSugarscapeとはいくつかの点で異なっています
個々のエージェントは環境内の個体を表す
エージェントは近隣をランダムに探索し発見した資源を取り込むことで「豊かさ(Wealth)」を増やす.
エージェントの豊かさが一定以上になると「子孫」が生まれる.
エージェントは寿命に達すると空間から除去される.
枯渇した資源は空間から除去される.
モデルはMesaのグリッド機能を使いエージェントと資源の位置を管理する.
モデルは各時間ステップごとにエージェントと資源の内部状態を収集する.
モデルはシミュレーション終了後に豊かさ,資源量の経時変化をチャート表示する.
エージェントと資源の初期状態のイメージは以下のようにランダムに設定されます.
1. Mesaのプログラミング
事前にpythonの環境を準備したうえで,以下のコマンドをコマンドプロンプトやターミナルで実行してツールをインストールします.このサンプルプログラムではシミュレーション結果を作図するためにpythonのチャート作図用ライブラリmatplotlib,その日本語表示用ライブラリjapanize_matplotlib使うため合わせてインストールしておきます.
pip install mesa
pip install matplotlib japanize_matplotlib
1.1. モデルを定義する:
MesaのModelクラスを継承したサブクラスをつくります.そしてクラスの初期化処理のなかで,エージェントの時間進行を制御するためのスケジューラ機能を準備します.以下の例では各時間ステップのなかで,登録された全てのエージェントをランダムに一回時間進行させるRandomActivationクラスを使っています.
from mesa.time import RandomActivation
class SugarscapeModel(Model):
def __init__(self):
self.schedule = RandomActivation(self)
1.2. エージェントを定義する:
Agentクラスを継承したサブクラスを定義することでエージェントに振る舞いや属性,他のエージェントとの相互作用を利用できるようになります.エージェントに固有の内部状態を保持させるためには初期化処理のなかでプロパティを定義します. 以下の例では3個のプロパティ(wealth,lifespan,age)を定義しています.これらのプロパティの値は後述するDataCollectorによって各時間ステップごとに自動的に抽出され,シミュレーション結果を可視化する際に参照できるようになります. また親クラスのstep メソッドをオーバーライドすることでエージェント固有の処理を各時間ステップで実行することができます.
from mesa import Agent
class SugarscapeAgent(Agent):
def __init__(self, unique_id, model):
super().__init__(unique_id, model)
self.wealth = 1
self.lifespan = int(random.normalvariate(50, 10))
self.age = 0
def step(self):
# describe procedure
1.3. 空間へエージェントを配置する:
Mesaは格子空間を定義しそのうえでエージェントを配置することができます.この機能は空間構造を想定したマルチエージェントシミュレーションを構築されたい利用者には非常に助けになるのではないでしょうか.利用できる格子空間としてSingleGrid,MultiGrid,HexGrid,ContinuousSpace,NetworkGrid等を選ぶことができます.例えば正方格子の空間モデルについては以下の特徴があります.
SingleGrid
格子点に高々1個のエージェントを配置できるモデルMultiGrid
格子点に複数のエージェントを配置できる空間モデル
サンプルコードではMultiGridを使ってみます.このためには格子空間のサイズと,空間上でのエージェントの初期配置を指定する必要があります.
少し注意が必要と思えるのがエージェントクラスに渡すユニークIDです.Mesaのプログラムでは多くのエージェントを生成・消滅させることが容易にできますのでIDがダブらないようにアプリ側で管理する必要があります.以下の説明では0から始まる連番をユニークIDとして渡しています.一方文末の全体コードではエージェントの数を動的に増減できるようにするためpythonのitertoolsを使ってユニークIDを生成しています.
from mesa import Agent, Model
from mesa.space import MultiGrid
from mesa.time import RandomActivation
class MySugarscapeModelModel(Model):
def __init__(self, width, height, num_agents):
self.num_agents = num_agents
self.grid = MultiGrid(width, height, True)
self.schedule = RandomActivation(self)
# エージェントの生成と配置
for i in range(self.num_agents):
agent = SugarscapeAgent(i, self)
x = self.random.randrange(self.grid.width)
y = self.random.randrange(self.grid.height)
self.grid.place_agent(agent, (x, y))
self.schedule.add(agent)
1.4. データ収集の仕組みを登録する:
Mesaでは,DataCollectorを使うことでシミュレーションの実行中にさまざまな統計情報を収集することできます.このデータ収集の仕組みを準備するためにはモデルクラスの初期化処理のなかでDataCollectorのインスタンスを生成します.
from mesa.datacollection import DataCollector
class MySugarscapeModelModel(Model):
def __init__(self, width, height, num_agents):
# ...途中省略
self.datacollector = DataCollector(
agent_reporters={"SugarscapeAgent": "wealth"},
model_reporters={"Total_Wealth": "total_wealth"}
)
agent_reporters
このパラメータに辞書形式で収集したい変数名とアクセスのためのキーを設定することでエージェントからデータを収集できるようになります.上記のようにキー名をエージェントクラスにした場合にはそのエージェントクラスに定義されたプロパティの値を自動収集します.一方,以下のようにキー名として好きな名前を定義することもできます.こうするとAgentクラスを継承して作成した全てのサブクラスへアクセスして名前が一致するプロパティ値を自動収集します.これは便利に使えるときもあると思いますがそのクラスに一致するプロパティが定義されていないとエラーとなりますので注意が必要です.
class MySugarscapeModelModel(Model):
def __init__(self, width, height, num_agents):
# ...途中省略
self.datacollector = DataCollector(
agent_reporters={"OurWealth": "wealth"}
)
# ... 以下略
model_reporters
モデル全体の統計情報を計算したいときにはこのパラメータに辞書形式を設定します.以下はキー名として"Total_Wealth"を,値としてエージェントクラスのwealthプロパティの値を合計するラムダ関数を指定しています.
class MySugarscapeModelModel(Model):
def __init__(self, width, height, num_agents):
self.datacollector = DataCollector(
model_reporters={
"TotalWealth": lambda m: sum(a.wealth for a in m.schedule.agents if isinstance(a, SugarscapeAgent)),
}
)
# ... 以下略
あるいはモデルクラスに"Total_Wealth"を計算するメソッドを定義して,辞書にセットすることもできます.
class MySugarscapeModelModel(Model):
def __init__(self, width, height, num_agents):
self.datacollector = DataCollector(
model_reporters={
"TotalWealth": self.total_wealth),
}
)
# ... 以下略
def total_wealth(self):
return sum([a.wealth for a in self.schedule.agents if isinstance(a, SugarscapeAgent)])
1.5. データ収集を実行する:
シミュレーションの各ステップで,DataCollectorクラスのcollectメソッドを実行することで前項の手順で登録したデータを収集することができます
class MySugarscapeModelModel(Model):
def step(self):
self.datacollector.collect(self)
1.6. 収集したデータへアクセスし可視化する:
シミュレーション実行後に,DataCollector クラスのget_agent_vars_dataframe メソッドやget_model_vars_dataframe メソッドを使用して,収集したデータにアクセスできます.
agent_data = self.datacollector.get_agent_vars_dataframe()
model_data = self.datacollector.get_model_vars_dataframe()
agent_reportersが収集したデータagent_dataの行は以下のようにStep列とAgentID列によってユニークに指定することができます.
model_reportersが収集したデータmodel_dataの行は以下のようにindex列によってユニークに指定することができます.
1.7. シミュレーションを開始する:
エージェントクラス(SugarscapeAgent)のインスタンスを生成し,スケジューラのadd メソッドを使ってインスタンスを登録します.
そしてモデルクラスのstep メソッドなかでスケジューラのstep メソッドを実行するようにします.するとスケジューラは各エージェントのstep メソッドを実行し,シミュレーションを進行させます.
これでシミュレーションの実行準備が完了しました.
モデルクラスのstep メソッドを呼び出す毎に時間ステップが進行します.
from mesa import Model
from mesa.time import RandomActivation
class SugarscapeModel(Model):
def __init__(self):
self.schedule = RandomActivation(self)
unique_id = 1
agent = SugarscapeAgent(unique_id, self)
self.schedule.add(agent)
def step(self):
self.schedule.step()
model = SugarscapeModelyModel()
for i in range(10):
my_model.step()
ここまででMesaの最低限の機能を見てきました.Mesaには上記で説明されたスケジューラクラスやエージェントの配置方法等多くの機能があります.Mesaの公式ドキュメントやサンプルコードを参照してより詳細な情報を得てください.
2. 可視化機能に関する補足
Mesaの可視化機能について補足します. 今回作成するサンプルコードではシミュレーション結果として得られるデータフレームをまずはmatplotlibのチャートを独自に使って可視化してみました.
Mesaでは,mesa.visualizationモジュールを使用してシミュレーションの進行やエージェントの状態をブラウザ上に可視化する機能がもともと整備されています.次にこれを活用してシミュレーションの実行途中の様子をアニメーションとして確認できるようにします.
コードはMesaのチュートリアルを参考としています(Advanced Tutorial)
2.1. 可視化モジュールのインポート:
from mesa.visualization.modules import CanvasGrid
from mesa.visualization.ModularVisualization import ModularServer
from mesa.visualization.modules import ChartModule
from mesa.visualization.UserParam import Slider,StaticText,Checkbox,Choice,NumberInput
2.2. CanvasGridの設定:
CanvasGridクラスを使用してエージェントの配置や外観を登録します.第一引数にはエージェントの外観を定義する関数を渡すようにします.
canvas_width, canvas_height = 500,500
canvas_element = CanvasGrid(agent_portrayal, grid_width, grid_height, canvas_width, canvas_height)
エージェントの外観を設定する関数を定義します.例えば,エージェントを円で,資源を四角で表現する場合,以下のように設定します
def agent_portrayal(self, agent):
if agent is None:
return
portrayal = {}
if isinstance(agent, SugarscapeAgent):
portrayal["Shape"] = "circle"
portrayal["Filled"] = "true"
portrayal["Color"] = "red" if agent.wealth > 0 else "gray"
portrayal["Layer"] = 0
portrayal["r"] = 0.8
elif isinstance(agent, Resource):
portrayal["Shape"] = "rect"
portrayal["Filled"] = "true"
portrayal["Color"] = "green"
portrayal["Layer"] = 1
portrayal["w"] = 1
portrayal["h"] = 1
return portrayal
2.3. チャートモジュールの設定
チャートモジュールを使うとWebUI上に動的に変化するチャートを表示することができます.このために以下のようにChartModule クラスをインスタンス化します.辞書形式で描画するチャートのパラメータを渡します.LabelはDataCollectorのmodel_reporters に登録したキー名に合わせます.またdata_collector_nameにはDataCollectorのインスタンス名を文字列で指定します.
from mesa.visualization.modules import ChartModule
chart = ChartModule(
[{"Label": "TotalWealth","Color": "Blue"},
{"Label": "TotalResources","Color": "Green"}],
data_collector_name='datacollector'
)
2.4. 可視化用サーバの設定:
シミュレーションの設定や実行過程をブラウザ上で確認できるようにするための組み込みWebサーバに必要な情報を設定します.ModularServerの引数として渡す辞書には作成したモデルクラスの引数名をキーとして設定します,値として例えば以下のようにWebUIをつくるためのクラスを指定することもできます.以下の例ではスライダーを入力部品として使えるようにします.
server = ModularServer(SugarscapeModel,
[canvas_element,chart],
"Sugarscape Model",
{
"width": width,
"height": height,
"initial_num_resources":initial_num_resources,
"num_agents": Slider("Number of agents", 10, 1, 50, 1),
"resource_capacity": Slider("Resource Capacity", 5, 1, 100, 1),
})
server.launch()
2.4. サーバーを起動する:
server.launch()で可視化サーバーを起動します.このサーバーは既定値では8521ポートでブラウザからの接続を待ちます.
server.launch()
可視化サーバーが起動した後でブラウザからhttp://127.0.0.1:8521 へアクセスすると以下のようにWebUIが表示されます.
3. コードの全体
from mesa import Agent, Model
from mesa.time import RandomActivation
from mesa.space import MultiGrid
from mesa.datacollection import DataCollector
from mesa.visualization.modules import CanvasGrid
from mesa.visualization.ModularVisualization import ModularServer
from mesa.visualization.modules import ChartModule
from mesa.visualization.UserParam import Slider,StaticText,Checkbox,Choice,NumberInput
import random
import matplotlib.pyplot as plt
from itertools import count
import japanize_matplotlib
random.seed(111)
class Resource(Agent):
def __init__(self, unique_id, model, capacity):
super().__init__(unique_id, model)
self.capacity = capacity
self.resources = self.capacity
self.age = 0
self.lifespan = 0
class SugarscapeAgent(Agent):
def __init__(self, unique_id, model):
super().__init__(unique_id, model)
self.wealth = 1
self.initial_position = self.pos
# 正規分布に従った寿命を設定する
self.lifespan = int(random.normalvariate(50, 10))
self.age = 0
def move(self):
possible_steps = self.model.grid.get_neighborhood(
self.pos,
moore=True,
include_center=False
)
new_position = random.choice(possible_steps)
self.model.grid.move_agent(self, new_position)
def eat(self):
cellmates = self.model.grid.get_cell_list_contents([self.pos])
resources = [obj for obj in cellmates if isinstance(obj, Resource)]
if resources:
patch = resources[0]
self.wealth += 1
patch.resources -= 1
patch.resources = max(0,patch.resources)
# 枯渇した資源を空間から除去する
if patch.resources==0:
self.model.grid.remove_agent(patch)
self.model.schedule.remove(patch)
print("step= %d resource_id= %d resource= %d" % (self.model.schedule.steps,patch.unique_id, patch.resources))
def reproduce(self):
if self.wealth > 10:
self.model.new_agent()
def step(self):
self.move()
self.eat()
self.reproduce()
self.age += 1
print("step= %d id= %d age= %d lifespane= %d wealth= %f" %(self.model.schedule.steps,self.unique_id,self.age,self.lifespan,self.wealth) )
class SugarscapeModel(Model):
def __init__(self, width, height, num_agents, resource_capacity, initial_num_resources):
self.agent_id_counter = count()
self.width = width
self.height = height
self.num_agents = num_agents
self.grid = MultiGrid(width, height, True)
self.schedule = RandomActivation(self)
self.datacollector = DataCollector(
agent_reporters={
"SugarscapeAgent": "wealth",
"Resource": "resources",
},
model_reporters={
"TotalWealth": lambda m: sum(a.wealth for a in m.schedule.agents if isinstance(a, SugarscapeAgent)),
"TotalResources": lambda m: sum(a.resources for a in m.schedule.agents if isinstance(a, Resource)),
}
)
self.initial_positions = {}
self.initial_resource_positions = {}
for i in range(self.num_agents):
agent = SugarscapeAgent(next(self.agent_id_counter), self)
x = random.randrange(self.grid.width)
y = random.randrange(self.grid.height)
self.grid.place_agent(agent, (x, y))
self.schedule.add(agent)
self.initial_positions[i] = (x, y)
for i in range(initial_num_resources):
patch = Resource(next(self.agent_id_counter), self, resource_capacity)
x = random.randrange(self.grid.width)
y = random.randrange(self.grid.height)
self.grid.place_agent(patch, (x, y))
self.initial_resource_positions[i] = (x, y)
self.schedule.add(patch)
def new_agent(self):
agent = SugarscapeAgent(next(self.agent_id_counter), self)
x = random.randrange(self.grid.width)
y = random.randrange(self.grid.height)
self.grid.place_agent(agent, (x, y))
self.schedule.add(agent)
def step(self):
self.datacollector.collect(self)
self.schedule.step()
for cell in self.grid.coord_iter():
content, [x, y] = cell
if self.width*self.height * random.random() < 1:
patch = Resource(next(self.agent_id_counter), self, 5)
self.grid.place_agent(patch, (x, y))
self.schedule.add(patch)
# 寿命に到達したエージェントを空間から除去する
self.remove_old_agents()
def remove_old_agents(self):
agents_to_remove = []
for agent in self.schedule.agents:
if isinstance(agent, SugarscapeAgent):
if agent.age >= agent.lifespan:
agents_to_remove.append(agent)
for agent in agents_to_remove:
self.grid.remove_agent(agent)
self.schedule.remove(agent)
def visualize(self):
#agent_df = model.datacollector.get_agent_vars_dataframe().reset_index()
#print(agent_df)
#model_df = model.datacollector.get_model_vars_dataframe().reset_index()
#print(model_df)
total_wealth = model.datacollector.get_model_vars_dataframe()["TotalWealth"]
total_resources = model.datacollector.get_model_vars_dataframe()["TotalResources"]
fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(15, 5))
axes[0].plot(total_wealth, label="豊かさ", color='blue')
axes2 = axes[0].twinx()
axes[0].set_xlabel("ステップ")
axes[0].set_ylabel("豊かさ")
axes[0].set_title("豊かさと資源量の推移")
axes[0].legend(loc=2)
axes2.plot(total_resources, label="資源量", color='green')
axes2.set_ylabel("資源量")
axes2.legend(loc=1)
initial_x = [pos[0] for pos in model.initial_positions.values()]
initial_y = [pos[1] for pos in model.initial_positions.values()]
axes[1].scatter(initial_x, initial_y, marker='o', color='blue', label='エージェント初期配置', alpha=0.5)
initial_x = [pos[0] for pos in model.initial_resource_positions.values()]
initial_y = [pos[1] for pos in model.initial_resource_positions.values()]
axes[1].scatter(initial_x, initial_y, marker='x', color='blue', label='資源初期配置', alpha=0.5)
axes[1].set_xlabel("X-軸")
axes[1].set_ylabel("Y-軸")
axes[1].set_title("エージェントと資源初期配置")
axes[1].legend(loc=1)
final_x = [agent.pos[0] for agent in model.schedule.agents]
final_y = [agent.pos[1] for agent in model.schedule.agents]
axes[2].scatter(final_x, final_y, marker='o',color='red', label='エージェント最終配置', alpha=0.5)
initial_x = [pos[0] for pos in model.initial_resource_positions.values()]
initial_y = [pos[1] for pos in model.initial_resource_positions.values()]
axes[2].scatter(initial_x, initial_y, marker='x', color='blue', label='資源最終配置', alpha=0.5)
axes[2].set_xlabel("X-軸")
axes[2].set_ylabel("Y-軸")
axes[2].set_title("エージェントと資源最終配置")
axes[2].legend(loc=1)
plt.tight_layout()
plt.show()
def agent_portrayal(self, agent):
if agent is None:
return
portrayal = {}
if isinstance(agent, SugarscapeAgent):
portrayal["Shape"] = "circle"
portrayal["Filled"] = "true"
portrayal["Color"] = "red" if agent.wealth > 0 else "gray"
portrayal["Layer"] = 0
portrayal["r"] = 0.8
elif isinstance(agent, Resource):
portrayal["Shape"] = "rect"
portrayal["Filled"] = "true"
portrayal["Color"] = "green"
portrayal["Layer"] = 1
portrayal["w"] = 1
portrayal["h"] = 1
return portrayal
# Run the model
width = 20
height = 20
num_agents = 50
resource_capacity = 50
initial_num_resources = 20
model = SugarscapeModel(width, height, num_agents, resource_capacity, initial_num_resources)
for i in range(300):
model.step()
# Draw results as chart.
model.visualize()
# Visualize the results by ModularServer
canvas_element = CanvasGrid(model.agent_portrayal, width, height, 500, 500)
chart = ChartModule(
[{"Label": "TotalWealth","Color": "Blue"},
{"Label": "TotalResources","Color": "Green"}],
data_collector_name='datacollector'
)
server = ModularServer(
SugarscapeModel,
[canvas_element,chart],
"Sugarscape Model",
{
"width": width,
"height": height,
"initial_num_resources":initial_num_resources,
"num_agents": Slider("Number of agents", 50, 1, 100, 1),
"resource_capacity": Slider("Resource Capacity", 20, 1, 50, 1),
}
)
server.launch()
このプログラムを例えばtest_mesa.pyという名前で保存した場合,以下のようにpythonプログラムとして実行してみてください.
> python test_mesa.py
問題なければシミュレーションの終了後に以下のようなmatplotlibのチャートが表示されます. このチャートを閉じると可視化サーバーが起動されるので,前述のようにブラウザからアクセスすることでWebUIが表示されます.
例えばコードのなかの以下の値をいろいろ変えて試してみるとシミュレーションの結果(状態推移のパターン)がかなり大きく変動します.
エージェントの個数
エージェントの寿命
エージェントの子孫が発生するための豊かさの閾値
各時間ステップで資源が再生産される確率
資源摂取のタイミングでの豊かさの増分量,資源の減少量
自分が試した感じでは300から400時間ステップのうちにエージェント集団が(資源量減少のためか?)消滅してしまうことがほとんどでした.なにかよい対策があるのかもしれませんが時間の都合でフォローできていません.
実際にMesaを使ってみるとマルチエージェントシミュレーションを実装するのに非常に適していると感じます.コードを何か所か修正すれば「人工社会」で説明された事例の再現はバリエーションを考えることも比較的に容易にできそうです.
最後にまとめとしてサンプルコードの内容をサマリしておきます.たまに長時間集団が栄枯盛衰を繰り返しながら維持されるケースもありましたのでそのときの結果もあわせて掲載しておきます.
SugarscapeAgent クラス
豊かさ(wealth)と行動:
エージェントは wealth(豊かさ)を主要な指標とします.
各時間ステップにおいて上下左右ランダムに動き,資源を摂取します.
寿命:
エージェントには寿命があり,一定の年齢に達するとモデルから削除されます.
資源の消費と再生産:
eat メソッドによって,エージェントは周囲のセルから資源を消費し,その量に応じて wealth が増加します.
消費により資源量は減少し,ゼロとなった場合に格子空間から削除されます.
reproduce メソッドによって,一定の wealth を超えたエージェントは子孫を発生させます.このときwealthを一定量減少させるようにしてもよいかもしれません
データの記録:
各ステップでのエージェントの状態(寿命,年齢,豊かさなど)はDataCollectorを通じて記録されます.
Resource クラス
資源:
環境内の資源を表現するクラスです.
各資源は容量 (capacity) を持ち,資源量 (resources) がその容量に基づいて初期化されます.
DataCollectorによって資源の状態が記録されます.
年齢と寿命:
エージェントと同様にageとlifespan プロパティを定義していますがこのコードにおいては特に利用していません.
SugarscapeModel クラス
エージェントと資源の管理:
SugarscapeAgent と Resource を管理し,エージェントと資源の配置,生成,削除を行います.
ランダムな初期配置:
エージェントと資源はモデルの初期化時にランダムな位置に配置されます.
データ収集:
DataCollector を使用してエージェントとモデル全体のデータ(豊かさ,資源量など)を収集します.
時間ステップごとの処理:
step メソッドにより,エージェントと資源の動作,データ収集,資源の再生成などが時間ステップごとに実行されます.
資源の再生産に関しては,self.width*self.height * random.random() が1未満のときに一つの資源を再生産します.
エージェントの寿命処理:
エージェントが寿命に達した場合,モデルから削除されます.
可視化:
初期位置,最終位置,エージェントと資源の分布などを含む可視化機能が提供されています.
対話的な可視化 (Mesa ModularServer):
Mesa ModularServer を使用して,エージェント数や資源容量などのパラメータを対話的に調整できます.
以上です