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
Other
2.37k stars 864 forks source link

Proposal: Flexible move_agent method that allows multiple movement strategies #1903

Open EwoutH opened 8 months ago

EwoutH commented 8 months ago

The Mesa Space module allows a few specific agent movements and interactions within a grid environment. However, the current methods lack a consistent way to apply different movement strategies. This proposal suggests integrating a single, versatile move_agent method, accepting a parameter that can either be a tuple for specific coordinates, a string indicating a movement strategy (like "random" or "empty"), or an object defining a neighborhood.

In the future, it can be extended with movement strategies based on properties (see #1898).

Motivation

I was building a toy model, and

x = self.random.randrange(self.grid.width)
y = self.random.randrange(self.grid.height)
self.grid.place_agent(a, (x, y))

just looked weird and limited.

Proposed changes

  1. Unified move method:

    • move_agent(agent: Agent, destination) → None
    • The destination parameter is versatile:
      • It can be a tuple (x, y) for moving the agent to specific coordinates.
      • It can be a string, such as "random" for a random cell, "empty" for a random empty cell.
      • It can be an object or a dictionary defining a neighborhood, allowing custom definitions of neighboring cells. For this we need a formal neighborhood definition, see #1900.
  2. Retiring redundant methods:

    • Methods like move_to_empty and place_agent would be redundant and can be removed, as their functionalities are integrated into the new move_agent.
  3. Enhanced out-of-bounds and validity checking:

    • Maintain out_of_bounds method, but possibly enhance it to include checks for cell occupancy, ensuring valid movement destinations.
  4. Agent removal and position swapping:

    • remove_agent and swap_pos methods remain useful and unchanged.

Example Implementations

Conclusion

This proposal aims to simplify and unify the movement methods in the Mesa Space module. By consolidating various movement strategies into a single method, we enhance the API's usability and flexibility, allowing users to execute complex movements with minimal and more intuitive code.

Notes

Structural Pattern Matching in Python 3.10 might help a lot with the implementation.

EwoutH commented 8 months ago

I was thinking about how this would integrate with the _PropertyGrid introduced in https://github.com/projectmesa/mesa/pull/1898. That currently offers separate functions for the selection of target cells, and then moving to the target cells.

def select_cells_by_properties():
def move_agent_to_cell_by_properties():
def select_extreme_value_cells():
def move_agent_to_extreme_value_cell():

If move_agent would also take a list or mask of target cells, that could allow removing the two movement methods from #1898, and make the API more consistent. All movement goes through move_agents, and any amount of custom methods can be written to select some target cells.

Currently, #1898 also include two mask functions which limit the number of candidate cells to cells empty or in a neighborhood.

def get_empty_mask():
def get_neighborhood_mask():

We might also want to integrate that into the move_agent method. It could take a mask or a list of masks.

A utility method to combine a list of masks into a single mask could also be provided.

EwoutH commented 8 months ago

Maybe we can combine things:

def move_agent(agent, pos=None, empty=False, neighborhood=None, mask=None, selection="random"):
EwoutH commented 8 months ago

@jackiekazil @tpike3 Same story as with #1905, I would like to discuss and potentially implement this before moving on the PropertyLayer (#1898), since this is a more generalized solution for a problem that I specifically solve in that PR. So if we can implement this, it would make especially the _PropertyGrid in #1898 a lot simpler.

So my specific questions are:

  1. Do you agree we should expand the built-in move_agent() movement method to allow moving to an empty, random or neighbouring cell?
  2. What do you think of the current proposed API? Do you like the initial one better or the last one?
EwoutH commented 8 months ago

I went for a relatively simple implementation: The move_agent method now can take a list of positions and choose either one randomly or the closest:

Selecting neighborhoods and empty cells can be done with other functions, and support for masks will be added in #1898.

EwoutH commented 4 months ago

There is still possibility for improvement here. The current most elegant way to place an agent on an empty cell I discovered is:

self.grid.place_agent(agent, pos=mesa.model.random.choice(list(self.grid.empties)))

This can't be the best way. The current problems are:

I would like to discuss how we can make placing an agent on a random empty cell more elegant.