Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring mesa.Agent, mesa.AgentSet, mesa.Model -> AgentSetDF, AgentsDF, ModelDF #8

Merged
merged 16 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 39 additions & 24 deletions docs/scripts/readme_plot.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from hmac import new
from random import seed
from typing import TYPE_CHECKING

import mesa
import numpy as np
import pandas as pd
import perfplot

from mesa_frames.agent import AgentDF
from mesa_frames.model import ModelDF
from mesa_frames import AgentSetDF, ModelDF


# Mesa implementation
Expand Down Expand Up @@ -57,6 +61,14 @@ def run_model(self, n_steps) -> None:
self.step()


"""def compute_gini(model):
agent_wealths = model.agents.get("wealth")
x = sorted(agent_wealths)
N = model.num_agents
B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x))
return 1 + (1 / N) - 2 * B"""


# Mesa Frames implementation
def mesa_frames_implementation(n_agents: int) -> None:
model = MoneyModelDF(n_agents)
Expand All @@ -67,45 +79,48 @@ class MoneyModelDF(ModelDF):
def __init__(self, N):
super().__init__()
self.num_agents = N
self.create_agents(N, {MoneyAgentDF: 1})
self.agents = self.agents.add(MoneyAgentsDF(N, model=self))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be an inplace operation, based on the API of AgentSet?

self.agents.add(...)
# instead of
self.agents = self.agents.add(...)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it because Polars doesn't support inplace operations?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rht I've set up a fast immutable system using custom DataFrame copies, aligning better with the pandas/polars API. I think it reduces confusion.
I'll update the script soon.


def step(self):
self.agents = self.agents.do("step")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API is simpler.


def step(self, merged_mro=True):
self.agents = self.agents.sample(frac=1)
self.update_agents_masks()
super().step(merged_mro)
def run_model(self, n):
for _ in range(n):
self.step()


class MoneyAgentDF(AgentDF):
dtypes: dict[str, str] = {"wealth": "int64"}
class MoneyAgentsDF(AgentSetDF):
def __init__(self, n: int, model: MoneyModelDF):
super().__init__(model=model)
self.add(n, data={"wealth": np.ones(n)})

@classmethod
def __init__(cls):
super().__init__()
cls.model.agents.loc[cls.mask, "wealth"] = 1
def step(self):
wealthy_agents = self.agents["wealth"] > 0
self.select(wealthy_agents).do("give_money")

@classmethod
def step(cls):
wealthy_agents = cls.model.agents.loc[cls.mask, "wealth"] > 0
if wealthy_agents.any():
other_agents = cls.model.agents.index.isin(
cls.model.agents.sample(n=wealthy_agents.sum()).index
)
cls.model.agents.loc[wealthy_agents, "wealth"] -= 1
cls.model.agents.loc[other_agents, "wealth"] += 1
def give_money(self):
other_agents = self.agents.sample(len(self.active_agents), replace=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the term "active agent" an internal one, given that it is the ones selected by select? If so, it'd be confusing to use it as a public API.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but i declared it as a property so it should be fine to use it in the public api right? it returns a view.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I think selected_agents is clearer, but we can do it in another PR. I think the user is more used to how it is done in pandas/Polars, in that the selected agents are stored as a variable defined by the user themselves.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can change the term active_agents to selected_agents (and also the corresponding string mask to "selected"). The active_agents property has a setter so that user can define it directly with a mask, instead of using the select method.

new_wealth = (
other_agents.index.value_counts()
.reindex(self.active_agents.index)
.fillna(-1)
)
self.set_attribute("wealth", self.get_attribute("wealth") + new_wealth)


def main():
mesa_frames_implementation(100)
out = perfplot.bench(
setup=lambda n: n,
kernels=[mesa_implementation, mesa_frames_implementation],
labels=["mesa", "mesa-frames"],
n_range=[k for k in range(10, 10000, 100)],
n_range=[k for k in range(100, 1000, 100)],
xlabel="Number of agents",
equality_check=None,
title="100 steps of the Boltzmann Wealth model",
)
out.show()
out.save("docs/images/readme_plot.png")
# out.save("docs/images/readme_plot.png")


if __name__ == "__main__":
Expand Down
2 changes: 2 additions & 0 deletions mesa_frames/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .agent import AgentsDF, AgentSetDF
from .model import ModelDF
Loading