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

Generalize CellAgent #2292

Merged
merged 21 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ff611c9
add some agents
Corvince Sep 11, 2024
ec36c08
restructure and rename
Corvince Sep 12, 2024
86f8cbc
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 12, 2024
1bcf17d
Merge branch 'main' of https://github.com/projectmesa/mesa into cell-…
Corvince Sep 30, 2024
a02eed5
Merge branch 'cell-space-agents' of https://github.com/projectmesa/me…
Corvince Sep 30, 2024
28379ed
Restructure mixins
Corvince Sep 30, 2024
a5f5930
rename and update Grid2DMovement
Corvince Sep 30, 2024
13a9dc0
Merge branch 'main' of https://github.com/projectmesa/mesa into cell-…
Corvince Oct 1, 2024
9e38f6a
use direction map instead of match
Corvince Oct 1, 2024
b0e95a0
Add Patch
Corvince Oct 1, 2024
181582c
Merge remote-tracking branch 'upstream/main' into cell-space-agents
quaquel Oct 2, 2024
9ad1a2a
tests for all new stuff
quaquel Oct 2, 2024
052cd7c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 2, 2024
51946ba
Update cell_agent.py
quaquel Oct 2, 2024
05597a5
Merge branch 'cell-space-agents' of https://github.com/projectmesa/me…
quaquel Oct 2, 2024
1d8a111
Update test_cell_space.py
quaquel Oct 2, 2024
87d6025
Rename Patch to FixedAgent
Corvince Oct 4, 2024
5d280ff
Rename Patch to FixedAgent in tests
EwoutH Oct 4, 2024
ddecebc
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 4, 2024
d6b3015
Use FixedAgent in examples/benchmarks
EwoutH Oct 4, 2024
5d605c2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions mesa/experimental/cell_space/cell.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from mesa.experimental.cell_space.cell_collection import CellCollection

if TYPE_CHECKING:
from mesa.experimental.cell_space.cell_agent import CellAgent
from mesa.experimental.cell_space.cell_agent import DiscreteSpaceAgent


class Cell:
Expand Down Expand Up @@ -80,7 +80,7 @@ def disconnect(self, other: Cell) -> None:
"""
self._connections.remove(other)

def add_agent(self, agent: CellAgent) -> None:
def add_agent(self, agent: DiscreteSpaceAgent[Cell]) -> None:
"""Adds an agent to the cell.

Args:
Expand All @@ -96,7 +96,7 @@ def add_agent(self, agent: CellAgent) -> None:

self.agents.append(agent)

def remove_agent(self, agent: CellAgent) -> None:
def remove_agent(self, agent: DiscreteSpaceAgent[Cell]) -> None:
"""Removes an agent from the cell.

Args:
Expand Down
84 changes: 65 additions & 19 deletions mesa/experimental/cell_space/cell_agent.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,83 @@
from __future__ import annotations

from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any, Protocol, TypeVar

from mesa import Agent, Model
from mesa.experimental.cell_space.discrete_space import DiscreteSpace

if TYPE_CHECKING:
from mesa.experimental.cell_space.cell import Cell
from mesa.experimental.cell_space import Cell

T = TypeVar("T", bound="Cell")

class CellAgent(Agent):
"""Cell Agent is an extension of the Agent class and adds behavior for moving in discrete spaces

class DiscreteSpaceAgent(Protocol[T]):
cell: T | None
space: DiscreteSpace[T]

def move_to(self, cell: T) -> None: ...

def move_relative(self, directions: tuple[int, ...], distance: int = 1): ...


class CellAgent:
"""Cell Agent is an Agent class that adds behavior for moving in discrete spaces

Attributes:
unique_id (int): A unique identifier for this agent.
model (Model): The model instance to which the agent belongs
pos: (Position | None): The position of the agent in the space
cell: (Cell | None): the cell which the agent occupies
space (DiscreteSpace): the discrete space the agent is in
cell (Cell): the cell the agent is in
"""

def __init__(self, model: Model) -> None:
"""
Create a new agent.
def __init__(
self,
space: DiscreteSpace[Cell],
cell: Cell | None = None,
*args: tuple[Any],
**kwargs: dict[str, Any],
):
super().__init__(*args, **kwargs)
self.space = space
self.cell = cell
if cell is not None:
cell.add_agent(self)

Args:
unique_id (int): A unique identifier for this agent.
model (Model): The model instance in which the agent exists.
"""
super().__init__(model)
self.cell: Cell | None = None
@property
def coordinate(self) -> tuple[int, ...]:
return self.cell.coordinate if self.cell else ()

def move_to(self, cell) -> None:
def move_to(self, cell: Cell) -> None:
if self.cell is not None:
self.cell.remove_agent(self)
self.cell = cell
cell.add_agent(self)

def move_relative(self, directions: tuple[int, ...], distance: int = 1):
new_position = tuple(
self.cell.coordinate[i] + directions[i] * distance
for i in range(len(directions))
if self.cell
)
new_cell = self.space[new_position]
self.move_to(new_cell)


class Grid2DMovingAgent:
def move(self: DiscreteSpaceAgent[Cell], direction: str, distance: int = 1):
match direction:
case "N" | "North" | "Up":
self.move_relative((-1, 0), distance)
case "S" | "South" | "Down":
self.move_relative((1, 0), distance)
case "E" | "East" | "Right":
self.move_relative((0, 1), distance)
case "W" | "West" | "Left":
self.move_relative((0, -1), distance)
case "NE" | "NorthEast" | "UpRight":
self.move_relative((-1, 1), distance)
case "NW" | "NorthWest" | "UpLeft":
self.move_relative((-1, -1), distance)
case "SE" | "SouthEast" | "DownRight":
self.move_relative((1, 1), distance)
case "SW" | "SouthWest" | "DownLeft":
self.move_relative((1, -1), distance)
case _:
raise ValueError(f"Invalid direction: {direction}")
Copy link
Member

Choose a reason for hiding this comment

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

  1. Great use of pattern matching!
  2. Should we also allow lower case? If so, we can do direction = direction.lower() and then match against the lower case strings for example.
  3. This now allows "queen" (chess) moves. Do we also want to allow knights and maybe even more? (south-south west, etc.)
  4. Do we want to allow a certain amount of degrees? 0 is up, 90 is east, 135 is south east, etc.
  5. Do we want to link this to the type of grid in any way? Because some grids (currently) have neighbours cells that are in different directions. Maybe add a filter "only_if_neighbour" or something better named than that.

I think none of these are blocking for this PR, just a few thoughts.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. Thanks!
  2. Yes, they should also work with lowercase directions I think.
  3. This is supposed to be used on a regular grid, where each cell has 8 neighbboring cells it touches. So these are the base movements, everything else can be composed of these. This is of course a bit arbitrary, technically 4 base movements are sufficient (linked to von Neumann vs Moore). But I think its a convenient set
  4. I think degrees deserve a separate implementation. Actually my first implementation was heading more towards degrees, where each agent has a "heading" and movement is implemented in terms of forward/backward/turnLeft/turnRight. I think this kind of movement would also be nice!
  5. Well the class is specifically called Grid2DMovingAgent to address this. It only works with 2DGrids, not necessarily with higher dimensions or other subclasses of DiscreteSpace

2 changes: 1 addition & 1 deletion mesa/experimental/cell_space/discrete_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def __getitem__(self, key):
return self._cells[key]

@property
def empties(self) -> CellCollection:
def empties(self) -> CellCollection[T]:
return self.all_cells.select(lambda cell: cell.is_empty)

def select_random_empty_cell(self) -> T:
Expand Down
2 changes: 1 addition & 1 deletion mesa/experimental/cell_space/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
T = TypeVar("T", bound=Cell)


class Grid(DiscreteSpace, Generic[T]):
class Grid(DiscreteSpace[T], Generic[T]):
"""Base class for all grid classes

Attributes:
Expand Down
3 changes: 1 addition & 2 deletions mesa/visualization/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from .components.altair import make_space_altair
from .components.matplotlib import make_plot_measure, make_space_matplotlib
from .solara_viz import JupyterViz, SolaraViz, make_text
from .solara_viz import JupyterViz, SolaraViz
from .UserParam import Slider

__all__ = [
"JupyterViz",
"SolaraViz",
"make_text",
"Slider",
"make_space_altair",
"make_space_matplotlib",
Expand Down
19 changes: 12 additions & 7 deletions tests/test_cell_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pytest

from mesa import Model
from mesa.agent import Agent
from mesa.experimental.cell_space import (
Cell,
CellAgent,
Expand All @@ -15,6 +16,10 @@
)


class TestAgent(Agent, CellAgent):
pass


def test_orthogonal_grid_neumann():
width = 10
height = 10
Expand Down Expand Up @@ -408,7 +413,7 @@ def test_empties_space():

model = Model()
for i in range(8):
grid._cells[i].add_agent(CellAgent(model))
grid._cells[i].add_agent(TestAgent(model))

cell = grid.select_random_empty_cell()
assert cell.coordinate in {8, 9}
Expand All @@ -432,7 +437,7 @@ def test_cell():

# add_agent
model = Model()
agent = CellAgent(model)
agent = TestAgent(model)

cell1.add_agent(agent)
assert agent in cell1.agents
Expand All @@ -445,11 +450,11 @@ def test_cell():
cell1.remove_agent(agent)

cell1 = Cell((1,), capacity=1, random=random.Random())
cell1.add_agent(CellAgent(model))
cell1.add_agent(TestAgent(model))
assert cell1.is_full

with pytest.raises(Exception):
cell1.add_agent(CellAgent(model))
cell1.add_agent(TestAgent(model))


def test_cell_collection():
Expand All @@ -475,9 +480,9 @@ def test_cell_collection():

cells = collection.cells
model = Model()
cells[0].add_agent(CellAgent(model))
cells[3].add_agent(CellAgent(model))
cells[7].add_agent(CellAgent(model))
cells[0].add_agent(TestAgent(model))
cells[3].add_agent(TestAgent(model))
cells[7].add_agent(TestAgent(model))
agents = collection.agents
assert len(list(agents)) == 3

Expand Down
Loading