-
Notifications
You must be signed in to change notification settings - Fork 17
Sugarscape Instantaneous Growback (Pandas-with-loop implementation) #63
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
Conversation
… 54-sugarscape-with-instantaneous-growback
… 54-sugarscape-with-instantaneous-growback
|
🚀 👨🚀 👩🚀 finally! @tpike3 check this out. |
Codecov ReportAll modified and coverable lines are covered by tests ✅ |
| best_moves = pd.DataFrame() | ||
|
|
||
| # While there are agents that do not have a best move, keep looking for one | ||
| while len(best_moves) < len(self.agents): |
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.
This is awesome, a hybrid of sequential and GPU/CPU parallelizable executions (whenever there is no collision). We should use this pattern a lot more in other models. I would want to know how many iterations this does across the steps, as a function of (num_agents / (width * height)).
I can't remember how the FLAME 2 folks did it, or how the Sugarscape on steroid folks did it. I think the former did tie-breaking whenever there is a collision?
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.
I would want to know how many iterations this does across the steps, as a function of (num_agents / (width * height)).
This is optional, by the way. There are a lot more other pressing stuff.
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.
This is awesome, a hybrid of sequential and GPU/CPU parallelizable executions (whenever there is no collision). We should use this pattern a lot more in other models. I would want to know how many iterations this does across the steps, as a function of (num_agents / (width * height)).
I can't remember how the FLAME 2 folks did it, or how the Sugarscape on steroid folks did it. I think the former did tie-breaking whenever there is a collision?
Yes, you mentioned that, I don't know about enough CUDA to look through the code :).
When prototyping with 100 agents 10*10 space (so full of collisions) was about 4 iterations.
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.
That's really good to know. I'm going over the CUDA code right now. I approved this PR so that we can move faster. Any direct comparison with the same algorithm as FLAME 2 can happen later.
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 movement diagram for FLAME GPU 2 can be found at figure 6 of the paper:
- Movement request
- Movement response, if there is a conflict, tie breaking happens, where the priority is chosen at random
float message_priority = FLAMEGPU->random.uniform<float>(). - Movement transaction
Based on what they said:
The cell responds by selecting the agent with highest priority therefore allowing a single agent to move in the first iteration of the model. An exit condition only allows the main simulation to resume once all agents are ‘resolved’ and as such a second iteration of the sub-model is required for the second agent to find a collision free location (and the exit condition to pass).
That "2nd iteration" could probably happen several times as long as there are still conflicts, I think? Their algorithm might be very similar to yours.
| neighborhood.groupby("agent_id_center", sort=False) | ||
| .first() | ||
| .sort_values("agent_order") | ||
| .drop_duplicates(["dim_0", "dim_1"], keep="first") |
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 ["dim_0", "dim_1"] pair keeps occurring several times. What if we name them, in a way that can be imported, such as from mesa_frames import POSITION_INDEX (or POSITION_INDICES, POSITION_COLUMNS)?
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.
yeah we can definitely do that, but you mean for type hinting?
I was thinking of refactoring DataFrameMixin more "object-like" having DF and index as attributes and putting an interface on that. we are building the backend for all DataFrames, we might as well use AgentSetDF as interface.
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.
It was for making it more semantic. It was capitalized because it's a constant. Would the mental model & designing the code be easier with object attributes instead of DF columns?
| self.max_sugar = max_sugar | ||
|
|
||
| def step(self): | ||
| self.amount = self.max_sugar |
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.
FLAME 2 folks did the growback only when unoccupied: https://github.com/FLAMEGPU/FLAMEGPU2-submodel-benchmark/blob/290702073492677f2fc9164419e9d5900d119e02/src/main.cu#L75.
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 do that
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.
Yes, so that it is apple vs apple benchmark comparison. Even though this probably won't affect the runtime that much.
This PR adds the first example of mesa-frames, SugarScape with Instantaneous Growback. Results are very promising as you can see in the plot (I tried to make results comparable, changing
mesa.model.agentsto a list instead of AgentSet).The model is completely analogous to mesa-examples, where agents take decisions sequentially. Since agents' sequential decisions might affect the State Space for other agents, I implemented a looping mechanism. Note that although there is a loop, vectorized operations are still used and the iterations are minimal.
I considered a ranking mechanism to completely avoid loops, but I believe it's not possible. There's potential to use Numba for a custom rolling expression, which will be added in a future PR.
Observations for future issues found during development:
It would be beneficial to have a function in the DiscreteSpaceDF (and AgentContainer) for moving agents based on attributes:
For example:
move_to('sugar', 'max'). This would abstract the code for handling sequential conflicts, making it easier than handling them from scratch each time.Previously, I returned pandas DataFrames with set indices for cells or agents:
self.space.cellshad dimensions as indexself.agentshad 'unique_id' as indexThis causes issues with pandas operations due to index alignment. It might be better to always return a neutral (1,n) index, making the functionality more similar to polars, which doesn't have an index.
Renaming
self.agentstoself.dfwill make the API clearer for AgentContainer.When
get_neighborhoodorget_neighborsare called with agents, it should return 'agent_id_center' to indicate which agent_id called, rather than 'dim_0_center' and 'dim_1_center' (or possibly both?).I encountered and solved some bugs related to edge cases and duck typing. As the project scales, we might need to seriously consider using hypothesis/deal and runtime type checking.
Tomorrow, I will upload Sugarscape IG with Polars.