Skip to content

Agent spatial methods from GaelLucero #2149

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
70 changes: 70 additions & 0 deletions mesa/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import contextlib
import copy
import math
import operator
import warnings
import weakref
Expand Down Expand Up @@ -68,6 +69,8 @@ def remove(self) -> None:
"""Remove and delete the agent from the model."""
with contextlib.suppress(KeyError):
self.model.agents_[type(self)].pop(self)
self.pos: Position | None
self.heading = 90

def step(self) -> None:
"""A single step of the agent."""
Expand All @@ -79,6 +82,73 @@ def advance(self) -> None:
def random(self) -> Random:
return self.model.random

def move_forward_or_backward(self, amount, sign):
"""Does the calculation to find the agent's next move and is used within the forward and backward functions"""
new_x = (
float(self.pos[0]) + sign * math.cos(self.heading * math.pi / 180) * amount
)
new_y = (
float(self.pos[1]) + sign * math.sin(self.heading * math.pi / -180) * amount
)
next_pos = (new_x, new_y)

try:
self.model.space.move_agent(self, next_pos)
except AttributeError as exc:
raise AttributeError("Agent's model does not define space") from exc
except Exception as exc:
warnings.warn(
f"agent.py (forward_backwards): could not move agent "
f"{self.unique_id} within self.model.space\n{exc}"
)

def move_forward(self, amount=1):
"""Moves the agent forward by the amount given"""
self.move_forward_or_backward(amount, 1)

def move_backward(self, amount=1):
"""Moves the agent backwards from where its facing by the given amount"""
self.move_forward_or_backward(amount, -1)

def turn_right(self, degree=90):
"""Turns the agent right by the given degree"""
self.heading = (self.heading - degree) % 360

def turn_left(self, degree=90):
"""Turns the agent left by the given degree"""
self.heading = (self.heading + degree) % 360

def distancexy(self, x, y):
"""Gives you the distance of the agent and the given coordinate"""

return math.dist(self.pos, (x, y))

def distance(self, another_agent):
"""Gives you the distance between the agent and another agent"""
return self.distancexy(**(another_agent.pos))

def towardsxy(self, x, y):
"""Calculates angle between a given coordinate and horizon as if the current position is the origin"""
dx = x - float(self.pos[0])
dy = float(self.pos[1]) - y
if dx == 0:
return 90 if dy > 0 else 270
else:
return math.degrees(math.atan2(dy, dx))

def towards(self, another_agent):
"""Calculates angle between an agent and horizon as if the current position is the origin"""
return self.towardsxy(*another_agent.pos)

def facexy(self, x, y):
"""Makes agent face a given coordinate"""
self.heading = self.towardsxy(x, y)

def face(self, another_agent):
"""Makes agent face another agent"""
x, y = another_agent.pos
self.facexy(x, y)


class AgentSet(MutableSet, Sequence):
"""
Expand Down
72 changes: 72 additions & 0 deletions tests/test_agent_spatial_methods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import pytest

from mesa.agent import Agent
from mesa.model import Model
from mesa.space import ContinuousSpace, MultiGrid


@pytest.fixture
def agent_in_space():
model = Model()
model.space = ContinuousSpace(10, 10, torus=True)
agent = Agent(1, model)
agent.pos = (2, 1)
agent.heading = 0
return agent


def test_move_forward(agent_in_space):
agent_in_space.heading = 90
agent_in_space.move_forward(1)
assert agent_in_space.pos[0] == pytest.approx(2)
assert agent_in_space.pos[1] == pytest.approx(0)


def test_turn_right(agent_in_space):
agent_in_space.heading = 0
agent_in_space.turn_right(60)
assert agent_in_space.heading == 300
agent_in_space.move_forward(1)
assert agent_in_space.pos[0] == pytest.approx(2.5)
assert agent_in_space.pos[1] == pytest.approx(1.8660254)


def test_move_forward_toroid(agent_in_space):
"Verify that toroidal wrapping applies to move_forward"

agent_in_space.move_forward(10.0)
assert agent_in_space.pos[0] == pytest.approx(2)
assert agent_in_space.pos[1] == pytest.approx(1)


def test_move_forward_raises_if_no_space():
"""move_forward only applies for models with ContinuousSpace"""

model = Model()
model.grid = MultiGrid(10, 10, torus=True)
agent = Agent(1, model)
agent.pos = (2, 1)
with pytest.raises(Exception):
agent.move_forward(10.0)


def test_towards(agent_in_space):
agent2 = Agent(2, agent_in_space.model)
agent2.pos = (5, 1)
assert agent_in_space.towards(agent2) == pytest.approx(0)
agent2.pos = (2, 4)
assert agent_in_space.towards(agent2) == pytest.approx(270)
agent2.pos = (5, 4)
assert agent_in_space.towards(agent2) == pytest.approx(-45)


def test_facexy(agent_in_space):
agent_in_space.facexy(2, 5)
assert agent_in_space.heading == pytest.approx(270)


def test_face(agent_in_space):
agent2 = Agent(2, agent_in_space.model)
agent2.pos = (5, 1)
agent_in_space.face(agent2)
assert agent_in_space.heading == pytest.approx(0)
Loading