-
Notifications
You must be signed in to change notification settings - Fork 6
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
Conversation
adamamer20
commented
Jan 10, 2024
•
edited
Loading
edited
- Agents of the model are included in a unique container class AgentContainer, in its agents attribute. Agents of the same type are stored in a DataFrame in the AgentSet class. Multiple types (all agents of the model) are stored in a list of AgentSets. This avoids having many missing values (which would occupy memory) if Agents of different type were stored in the same Dataframe.
- 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).
- All operations are mutable: better aligment with base mesa API. Possibility of functional programming with the use of fast copy methods.
- All methods and dunder methods that can act on a subset of agents support a custom mask = "active" that operates only on active agents. The private attribute is a mask (pl.Expr, pl.Series, pd.Series). The public property is self.active_agents. If you wanted to remove the agents you could simply use the "discard" or "remove" methods.
- Dunder methods to facilitate interaction introduced on top. Maybe can be added also to base mesa.
- pandas and polars are currently implemented in two different classes. Creating a single AgentSet class and use backend as environment variable or a method .to like Pytorch is not currently possible because type hinting is only static (see discussion Towards a unique AgentSet class #12). However having an abstract AgentSet class makes it easy to extend to other backends with relative ease.
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.
docs/scripts/readme_plot.py
Outdated
self.agents = self.agents.add(MoneyAgentsDF(N, model=self)) | ||
|
||
def step(self): | ||
self.agents = self.agents.do("step") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The API is simpler.
Not sure if Polars supports inplace operations. Needs to check the documentation. |
I like it on a high-level! Especially the active agents concept looks interesting. Are there any parts that significantly differ from regular mesa? Is there anything that you are in doubt of or would like to have a more in-depth review of? |
docs/scripts/readme_plot.py
Outdated
@@ -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)) |
There was a problem hiding this comment.
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(...)
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
docs/scripts/readme_plot.py
Outdated
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
script needs update
@rht i'm a bit behind the schedule as i'm working only during the evenings because i still have some exams left. from mid june i will be completely free and i will give my complete attention to development! |
No worries, looking forward it. |
…entations, update script
@rht I have made the last commit. For me it's ready to merge, let me know what you think about it. I went back to inplace operations because the API would be a bit more complex and too different from base mesa. I tried implementing a unique AgentSet class but type hinting becomes fairly complicated, especially with subclassing (see #12). Maybe in the future. |
Great progress so far! |
Thank you! |