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

Add cell-centric discrete spaces (experimental) #1994

Merged
merged 74 commits into from
Feb 27, 2024

Conversation

Corvince
Copy link
Contributor

@Corvince Corvince commented Jan 23, 2024

Summary

This PR introduces an alteranative implementation for discrete spaces. This implementation centers on the explicit inclusion of a Cell class. Agents can occupy cells. Cells have connections, specifying their neighbors. The resulting classes offer a cell centric API where agents interact with a cell, and query the cell for its neighbors.

To capture a collection of cells, and their content (i.e., Agents), this PR adds a new CellCollection class. This is an immutable collection of cells with convenient attribute accessors to the cells, or their agents.

This PR also includes a CellAgent class which extends the default Agent class by adding a move_to method that works in conjunction with the new discrete spaces.

From a performance point of view, the current code is a bit slower in building the grid and cell data structure, but in most use cases this increase in time for model initialization will be more than offset by the faster retrieval of neighboring cells and the agents that occupy them.

Motive

The PR emerged out of various experiments aimed at improving the performance of the current discrete space code. Moreover, it turned out that a cell centric API resolved various open issues (e.g., #1900, #1903, #1953).

Implementation

The key idea is to have Cells with connections, and using this to generate neighborhoods for a given radius. So all discrete space classes are in essence a linked data structure.

The cell centric API idea is used to implement 4 key discrete space classes: OrthogonalMooreGrid, OrthogonalVonNeumannGrid (alternative for SingleGrid and MultiGrid, and moore and von Neumann neighborhood) , HexGrid (alternative for SingleHexGrid and MultiHexGrid), and Network (alternative for NetworkGrid). Cells have a capacity, so there is no longer a need for seperating Single and Multi grids. Moore and von Neumann reflect different neighborhood connections and so are now implemented as seperate classes.

Usage examples

Below are a few examples demonstrating the functionality of the cell spaces.

Creating a Grid and Adding Agents

from mesa import Model
from mesa.experimental.cell_space import OrthogonalMooreGrid, CellAgent

class MyAgent(CellAgent):
    def __init__(self, unique_id, model):
        super().__init__(unique_id, model)

# Initialize a model and grid
model = Model()
grid = OrthogonalMooreGrid([10, 10], torus=True, capacity=1, random=model.random)

# Create an agent and add it to a cell
agent = MyAgent(1, model)
cell = grid.select_random_empty_cell()
agent.move_to(cell)

Agent Movement

# Assuming `agent` and `grid` are already defined
# Move the agent to a random neighboring cell
new_cell = agent.cell.neighborhood().select_random_cell()
agent.move_to(new_cell)

Querying the Neighborhood

# Get all neighboring agents within a radius of 1
neighbors = agent.cell.neighborhood(radius=1).agents

# Iterate over neighbors and perform actions
for neighbor in neighbors:
    # Example action: print neighbor's ID
    print(neighbor.unique_id)

Agent Reproduction

# Agent reproduces, creating a new agent in the same cell
def reproduce(agent):
    offspring = MyAgent(model.next_id(), model)
    offspring.move_to(agent.cell)
    model.schedule.add(offspring)

# Call reproduce function for an agent
reproduce(agent)

Removing an Agent

# Remove an agent from the simulation
def remove_agent(agent):
    agent.cell.remove_agent(agent)  # Remove from cell
    model.schedule.remove(agent)    # Remove from the scheduler

# Call remove_agent function for an agent
remove_agent(agent)

Checking Cell Properties

# Check if a cell is empty or full before moving an agent
if cell.is_empty:
    agent.move_to(cell)
elif cell.is_full:
    print("Cell is full. Agent cannot move here.")

@EwoutH
Copy link
Member

EwoutH commented Jan 23, 2024

There’s a lot in here that I like!

let me know when you want any input / a review.

@EwoutH
Copy link
Member

EwoutH commented Jan 23, 2024

If you add matplotlib here, the current error in the benchmarks is resolved.

run: pip install numpy pandas tqdm tabulate

@Corvince
Copy link
Contributor Author

If you add matplotlib here, the current error in the benchmarks is resolved.

run: pip install numpy pandas tqdm tabulate

Thanks, I now tried to bypass the jupyter stuff, but I am not sure if this is working. If I change the benchmark.yml won't the action stop working? Because of the pull_request_target thingy?

@Corvince
Copy link
Contributor Author

There’s a lot in here that I like!

let me know when you want any input / a review.

Feedback ist welcomed at all times!

@Corvince
Copy link
Contributor Author

/rerun benchmarks

@EwoutH
Copy link
Member

EwoutH commented Jan 23, 2024

If I change the benchmark.yml won't the action stop working?

Yeah it might, honestly I don't know.

Copy link
Member

@EwoutH EwoutH left a comment

Choose a reason for hiding this comment

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

Just some random thoughts from reading the code. On a high level, I think I really like it but I feel like I don't have enough comprehension of all the nuances and implications to say for sure.

Guess I would need to start make models with it to see what happens!

mesa/agent.py Outdated Show resolved Hide resolved
mesa/experimental/cell_space.py Outdated Show resolved Hide resolved
mesa/experimental/cell_space.py Outdated Show resolved Hide resolved
mesa/experimental/cell_space.py Outdated Show resolved Hide resolved
mesa/experimental/cell_space.py Outdated Show resolved Hide resolved
mesa/experimental/cell_space.py Outdated Show resolved Hide resolved
mesa/experimental/cell_space.py Outdated Show resolved Hide resolved
mesa/experimental/cell_space.py Outdated Show resolved Hide resolved
mesa/experimental/cell_space.py Outdated Show resolved Hide resolved
@rht
Copy link
Contributor

rht commented Jan 23, 2024

Had 3 comments at 2e78940#comments.

@rht
Copy link
Contributor

rht commented Jan 23, 2024

/rerun-benchmarks

Copy link

Performance benchmarks:

Model Size Init time [95% CI] Run time [95% CI]
Schelling small 🔵 +0.0% [-0.3%, +0.4%] 🔵 -0.1% [-0.3%, +0.1%]
Schelling large 🔵 -0.5% [-1.1%, +0.1%] 🔵 -3.9% [-6.4%, -1.2%]
WolfSheep small 🔵 -2.4% [-2.8%, -1.9%] 🔵 -0.3% [-0.4%, -0.1%]
WolfSheep large 🔵 -2.2% [-3.0%, -1.5%] 🔵 -3.5% [-4.4%, -2.3%]
BoidFlockers small 🔵 -1.7% [-2.2%, -1.1%] 🔵 +0.4% [-0.0%, +0.9%]
BoidFlockers large 🔵 +0.2% [-0.4%, +0.9%] 🔵 -0.5% [-1.0%, -0.0%]

@Corvince
Copy link
Contributor Author

@EwoutH it looks like the benchmark wasn't run for this PR after the comment trigger. You can see that from the action. Maybe you can look into that?

@EwoutH
Copy link
Member

EwoutH commented Jan 23, 2024

it looks like the benchmark wasn't run for this PR after the comment trigger.

You forgot the dash between rerun and benchmarks. It has to be the exact string (see rht's one).

if radius == 0:
return {self: self.agents}
if radius == 1:
return CellCollection(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this currently breaks neighborhoods with radius>1 Made some errors while refactoring

Copy link
Member

Choose a reason for hiding this comment

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

I tested in on Schelling, radius 2 and seems fine?

@Corvince
Copy link
Contributor Author

it looks like the benchmark wasn't run for this PR after the comment trigger.

You forgot the dash between rerun and benchmarks. It has to be the exact string (see rht's one).

Ha, I see how my comment was completely miswritten. I already realized after @rht made the correct comment. But in the action that was actually triggered this PR isn't checked out, hence no difference in performance. Performance should actually be about 20% faster.

Here is the exact line of the Action. It should check out this PR, but instead creates a new branch "main"
https://github.com/projectmesa/mesa/actions/runs/7632442972/job/20792667533#step:9:149

@EwoutH
Copy link
Member

EwoutH commented Jan 24, 2024

Looks like it checks it out differently when initiated from a PR opened then from a comment. Will check it out!

(current workaround it so mark PR as ready to review (and back to draft))

@Corvince Corvince marked this pull request as ready for review January 24, 2024 10:44
@Corvince Corvince marked this pull request as draft January 24, 2024 10:46
@quaquel
Copy link
Member

quaquel commented Jan 24, 2024

Thanks for this! I will take a deeper dive at this, hopefully, tonight or otherwise over the weekend. I quickly checked the code this morning and have a few additional comments and suggestions in addition to the various points already raised

I will also move my versions of Schelling and WolfSheep into this (do I have write access)? So we can properly see the change in performance.

@EwoutH
Copy link
Member

EwoutH commented Jan 24, 2024

It should check out this PR, but instead creates a new branch "main"

I tried some stuff in #1998. Let's see if it works.

@EwoutH
Copy link
Member

EwoutH commented Jan 24, 2024

Merged, here we go: /rerun-benchmarks

@quaquel
Copy link
Member

quaquel commented Feb 23, 2024

I did some more testing. I can't reproduce my initial performance differences so I guess I made a mistake when I thought there was a massive difference. I also tested a dedicated SingleAgentCell next to the normal cell and updated CellCollection.agents to take advantage of this. Again, this did not produce a massive speedup. So, I suggest we leave the code as is for now and just merge it. I have updated the PR text to better reflect the actual performance figures.

@EwoutH EwoutH added trigger-benchmarks Special label that triggers the benchmarking CI and removed trigger-benchmarks Special label that triggers the benchmarking CI labels Feb 23, 2024
@EwoutH
Copy link
Member

EwoutH commented Feb 23, 2024

Agreed, we can always optimize more later if needed.

On thing I'm not sure about is already porting our main benchmarks over. Maybe we could add those models to mesa-examples, and leave the benchmarks here as is.

Copy link

Performance benchmarks:

Model Size Init time [95% CI] Run time [95% CI]
Schelling small 🔴 +240.8% [+239.2%, +242.4%] 🔴 +7.5% [+7.1%, +7.8%]
Schelling large 🔴 +170.1% [+132.4%, +190.5%] 🔴 +26.7% [+24.4%, +28.8%]
WolfSheep small 🔴 +72.9% [+71.1%, +74.5%] 🟢 -15.1% [-16.1%, -14.2%]
WolfSheep large 🔴 +61.9% [+44.7%, +71.8%] 🟢 -15.8% [-17.9%, -13.6%]
BoidFlockers small 🔵 +1.8% [+0.8%, +2.7%] 🔵 +0.1% [-0.5%, +0.7%]
BoidFlockers large 🔵 +2.0% [+1.2%, +2.7%] 🔵 +0.9% [+0.4%, +1.4%]

@quaquel
Copy link
Member

quaquel commented Feb 24, 2024

In both my datacollection and devs branch, I have added a simple examples folder within the folder with the new experimental code. I am fine with using either mesa-examples or doing what I have been doing in devs and datacollection. Let me know what you prefer and why.

@EwoutH
Copy link
Member

EwoutH commented Feb 24, 2024

I would suggest this PR shouldn't change the models in benchmarks. I can do those last bits however, I will try to do that and merge it tomorrow.

Out of curiosity, where any models with an NetworkGrid tested?

@Corvince
Copy link
Contributor Author

I think an example folder inside the experiments sounds good and like the place to be.

But maybe it would still be nice to have one of the benchmarks use this feature, since I think we all agree that cell space is going to be the recommended space implementation eventually

@quaquel
Copy link
Member

quaquel commented Feb 24, 2024

I like @Corvince suggestions. It also allows us to track how the performance of the new grid stuff is affected going forward.

@EwoutH
Copy link
Member

EwoutH commented Feb 25, 2024

We could have two sets of benchmarks, regular ones and experimental ones

@quaquel
Copy link
Member

quaquel commented Feb 26, 2024

I am also fine with adding additional benchmarks, but how would that work out if we add additional experimental stuff? For example, if I add devs, which benchmarks would I edit to showcase their impact?

@EwoutH
Copy link
Member

EwoutH commented Feb 26, 2024

Thought about it a bit, let's keep it simple. Benchmarks are an internal development tool, not user examples. So let's keep them on the cutting edge.

I'm good with merging (please clean ip or squash). I would like a second aproval before doing so.

Thanks for the thremendous effort!

@Corvince
Copy link
Contributor Author

@quaquel can you handle the merging, since I opened the PR? I'm swamped with work until next work and won't be able to do so until then

@EwoutH
Copy link
Member

EwoutH commented Feb 27, 2024

I can also do it

@EwoutH EwoutH added trigger-benchmarks Special label that triggers the benchmarking CI and removed trigger-benchmarks Special label that triggers the benchmarking CI labels Feb 27, 2024
Copy link

Performance benchmarks:

Model Size Init time [95% CI] Run time [95% CI]
Schelling small 🔴 +248.5% [+246.4%, +250.5%] 🔴 +6.8% [+6.4%, +7.1%]
Schelling large 🔴 +174.0% [+135.6%, +195.2%] 🔴 +21.7% [+19.5%, +23.6%]
WolfSheep small 🔴 +75.2% [+73.4%, +77.1%] 🟢 -16.1% [-17.1%, -15.1%]
WolfSheep large 🔴 +60.8% [+45.4%, +69.1%] 🟢 -19.7% [-21.1%, -17.9%]
BoidFlockers small 🔵 +2.1% [+1.4%, +2.8%] 🔵 +0.7% [+0.2%, +1.2%]
BoidFlockers large 🔵 +2.6% [+1.9%, +3.1%] 🔵 +0.4% [-0.0%, +0.9%]

@EwoutH EwoutH changed the title Experimental/cell space Add cell-centric discrete spaces (experimental) Feb 27, 2024
@EwoutH EwoutH merged commit ac183a8 into projectmesa:main Feb 27, 2024
13 checks passed
@EwoutH EwoutH mentioned this pull request Mar 1, 2024
2 tasks
quaquel added a commit to quaquel/mesa that referenced this pull request Apr 9, 2024
## Summary
This PR introduces an alteranative implementation for discrete spaces. This implementation centers on the explicit inclusion of a Cell class. Agents can occupy cells. Cells have connections, specifying their neighbors. The resulting classes offer a cell centric API where agents interact with a cell, and query the cell for its neighbors. 

To capture a collection of cells, and their content (_i.e._, Agents), this PR adds a new CellCollection class. This is an immutable collection of cells with convenient attribute accessors to the cells, or their agents. 

This PR also includes a CellAgent class which extends the default Agent class by adding a `move_to` method that works in conjunction with the new discrete spaces. 

From a performance point of view, the current code is a bit slower in building the grid and cell data structure, but in most use cases this increase in time for model initialization will be more than offset by the faster retrieval of neighboring cells and the agents that occupy them.

## Motive
The PR emerged out of various experiments aimed at improving the performance of the current discrete space code. Moreover, it turned out that a cell centric API resolved various open issues (_e.g._, projectmesa#1900, projectmesa#1903, projectmesa#1953). 

## Implementation
The key idea is to have Cells with connections, and using this to generate neighborhoods for a given radius. So all discrete space classes are in essence a [linked data structure](https://en.wikipedia.org/wiki/Linked_data_structure).

The cell centric API idea is used to implement 4 key discrete space classes: OrthogonalMooreGrid, OrthogonalVonNeumannGrid (alternative for SingleGrid and MultiGrid, and moore and von Neumann neighborhood) , HexGrid (alternative for SingleHexGrid and MultiHexGrid), and Network (alternative for NetworkGrid). Cells have a capacity, so there is no longer a need for seperating Single and Multi grids. Moore and von Neumann reflect different neighborhood connections and so are now implemented as seperate classes. 

---------

Co-authored-by: Jan Kwakkel <j.h.kwakkel@tudelft.nl>
@EwoutH EwoutH mentioned this pull request Aug 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
experimental Release notes label feature Release notes label
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants