projectmesa / mesa

Mesa is an open-source Python library for agent-based modeling, ideal for simulating complex systems and exploring emergent behaviors.
https://mesa.readthedocs.io
Apache License 2.0
2.58k stars 900 forks source link

Cell space: Place agents in AgentSet randomly on (empty) cells #2530

Open EwoutH opened 1 day ago

EwoutH commented 1 day ago

Often you create a bunch of Agents, and then you need to place them on the grid. Either you loop over them, or do some other unwieldy thing, none of it is elegant.

So it would be great if there was a simple function that would get all agents and place them on, optionally empty, cells. This could be implemented faster than placing them individually, since now only once has to be checked which cells are available / empty.

It's especially handy to be used together with create_agents (https://github.com/projectmesa/mesa/pull/2351), combined you can create and place all your agents with two lines of code.

some_agents = Ant.create_agents(n=100)
self.space.place_agents(some_agents, only_empty=True)
quaquel commented 1 day ago

Yes either have a place_agents method, or have a way of getting a list of positions drawn either with it without replacement:


cells = self.space.choices_empty_cells(100,) # with replacement, follows random.choices
cells = self.space.sample_empty_cells(100)  # no replacement, follows random.sample
EwoutH commented 1 day ago

once we agree on how to go about it: do it via sampling/choosing empty cells or do it via placing agents, or even have both?

Sorry, didn't recognize there also was a question in there. I would say its a two-stage function:

  1. Select a random sample of cells, with or without replacement (keyword) and only empty or all (keyword)
  2. Place the agents on these sampled cells.
self.space.place(agents, unique=False, empty=False)
quaquel commented 1 day ago

I am not sure. A key design choice with the new discrete spaces is to put the agent central and in control of its movement. So, to me it makes more sense to just sample/choice the cells, pass a cell to the agent as an argument and let the agent place itself via self.cell = cell. In that design, there is no need for a space-level place method.

EwoutH commented 23 hours ago

Interesting, I'm not that familiar with the cell space yet.

In that case you could doe something like:

some_agents = MyAgent.create_agents(n=100)
cells = self.space ...
some_agents.map(lambda agent, cell: setattr(agent, 'cell', cell), zip(agents, cells))

That map is a bit cumbersome.

However, changing it around might work, right?

cells = self.space ...
some_agents = MyAgent.create_agents(n=100, cell=cells)

Can create_agents handle a CellCollection?

quaquel commented 18 hours ago

A CellCollection is a sequence so it supports item-based access. This is what is used in create_agents, so yes that should work.

Looking at the code again, there are actually a couple more options on how to implement this. A DiscreteSpace has an empties attribute and aan all_cells attribute. Both return a CellCollection, so sample and choices might also be implemented on the CellCollection so you could do


cells = self.space.empties.sample(100)
cells = self.space.empties.choices(100)
cells = self.space.all_cells.sample(100)
cells = self.space.all_cells.choices(100)

and then

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

agents = MyAgent.create_agents(100, cells)
EwoutH commented 18 hours ago

So we don't need anything new at all to do this. Damn. Powerful stuff.

I will update a few example models tomorrow.

quaquel commented 18 hours ago

sample and choices are currently not implemented on CellCollection, but self.random.sample(self.space.empties, 100) might already work.