Skip to content
Open
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)