Skip to content

Commit 12a2f63

Browse files
committed
API implementation of AgentSet and Agents
1) Agents of the same type are stored in a pd.DataFrame of the AgentSetDF class. This avoids having many missing values (which would occupy memory) if Agents or different type were stored in the same Dataframe. All agents of the model are stored in the AgentsDF class as a list of the AgentSetDF, which virtually supports the same operations as AgentSet. 2) Went with encapsulation, avoiding extensions and subclassing because they didn't always work well with storing additional attributes and it wasn't easy to extend the created classes by subclassing (as it's often done with base mesa.Agent). 3) All operations are inplace (no immutability): if we wanted to keep immutability, there couldn't be method chaining with encapsulation (since methods would have to return a pd.Dataframe) If we were to return an AgentSet. I think it also aligns well with base mesa API. 4) The "select" operations was changed to selecting "active_agents" (by a latent mask) which are the ones effectively used for the "do" operations (since usually you don't want to remove the other agents from the df but simply use a method on a part of agents of the DF). If you wanted to remove the agents you could simply use the "discard" or "remove" methods.
1 parent a8327e6 commit 12a2f63

File tree

4 files changed

+694
-176
lines changed

4 files changed

+694
-176
lines changed

docs/scripts/readme_plot.py

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
from hmac import new
2+
from random import seed
3+
from typing import TYPE_CHECKING
4+
15
import mesa
26
import numpy as np
7+
import pandas as pd
38
import perfplot
49

5-
from mesa_frames.agent import AgentDF
6-
from mesa_frames.model import ModelDF
10+
from mesa_frames import AgentSetDF, ModelDF
711

812

913
# Mesa implementation
@@ -57,6 +61,14 @@ def run_model(self, n_steps) -> None:
5761
self.step()
5862

5963

64+
"""def compute_gini(model):
65+
agent_wealths = model.agents.get("wealth")
66+
x = sorted(agent_wealths)
67+
N = model.num_agents
68+
B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x))
69+
return 1 + (1 / N) - 2 * B"""
70+
71+
6072
# Mesa Frames implementation
6173
def mesa_frames_implementation(n_agents: int) -> None:
6274
model = MoneyModelDF(n_agents)
@@ -67,45 +79,48 @@ class MoneyModelDF(ModelDF):
6779
def __init__(self, N):
6880
super().__init__()
6981
self.num_agents = N
70-
self.create_agents(N, {MoneyAgentDF: 1})
82+
self.agents = self.agents.add(MoneyAgentsDF(N, model=self))
83+
84+
def step(self):
85+
self.agents = self.agents.do("step")
7186

72-
def step(self, merged_mro=True):
73-
self.agents = self.agents.sample(frac=1)
74-
self.update_agents_masks()
75-
super().step(merged_mro)
87+
def run_model(self, n):
88+
for _ in range(n):
89+
self.step()
7690

7791

78-
class MoneyAgentDF(AgentDF):
79-
dtypes: dict[str, str] = {"wealth": "int64"}
92+
class MoneyAgentsDF(AgentSetDF):
93+
def __init__(self, n: int, model: MoneyModelDF):
94+
super().__init__(model=model)
95+
self.add(n, data={"wealth": np.ones(n)})
8096

81-
@classmethod
82-
def __init__(cls):
83-
super().__init__()
84-
cls.model.agents.loc[cls.mask, "wealth"] = 1
97+
def step(self):
98+
wealthy_agents = self.agents["wealth"] > 0
99+
self.select(wealthy_agents).do("give_money")
85100

86-
@classmethod
87-
def step(cls):
88-
wealthy_agents = cls.model.agents.loc[cls.mask, "wealth"] > 0
89-
if wealthy_agents.any():
90-
other_agents = cls.model.agents.index.isin(
91-
cls.model.agents.sample(n=wealthy_agents.sum()).index
92-
)
93-
cls.model.agents.loc[wealthy_agents, "wealth"] -= 1
94-
cls.model.agents.loc[other_agents, "wealth"] += 1
101+
def give_money(self):
102+
other_agents = self.agents.sample(len(self.active_agents), replace=True)
103+
new_wealth = (
104+
other_agents.index.value_counts()
105+
.reindex(self.active_agents.index)
106+
.fillna(-1)
107+
)
108+
self.set_attribute("wealth", self.get_attribute("wealth") + new_wealth)
95109

96110

97111
def main():
112+
mesa_frames_implementation(100)
98113
out = perfplot.bench(
99114
setup=lambda n: n,
100115
kernels=[mesa_implementation, mesa_frames_implementation],
101116
labels=["mesa", "mesa-frames"],
102-
n_range=[k for k in range(10, 10000, 100)],
117+
n_range=[k for k in range(100, 1000, 100)],
103118
xlabel="Number of agents",
104119
equality_check=None,
105120
title="100 steps of the Boltzmann Wealth model",
106121
)
107122
out.show()
108-
out.save("docs/images/readme_plot.png")
123+
# out.save("docs/images/readme_plot.png")
109124

110125

111126
if __name__ == "__main__":

mesa_frames/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
from .agent import AgentSetPandas, AgentDF
2-
from .datacollection import DataCollectorDF
1+
from .agent import AgentsDF, AgentSetDF
32
from .model import ModelDF

0 commit comments

Comments
 (0)