Skip to content

Commit

Permalink
space: Allow move_agent_to_one_of to handle empty input list
Browse files Browse the repository at this point in the history
Allow move_agent_to_one_of to handle an empty list by either passing silently (default), throwing an warning or an error.
  • Loading branch information
EwoutH authored and tpike3 committed Dec 24, 2023
1 parent a083f7d commit 03d0c81
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 19 deletions.
53 changes: 34 additions & 19 deletions mesa/space.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ def move_agent_to_one_of(
agent: Agent,
pos: list[Coordinate],
selection: str = "random",
handle_empty: str | None = None,
) -> None:
"""
Move an agent to one of the given positions.
Expand All @@ -445,29 +446,43 @@ def move_agent_to_one_of(
pos: List of possible positions.
selection: String, either "random" (default) or "closest". If "closest" is selected and multiple
cells are the same distance, one is chosen randomly.
handle_empty: String, either "warning", "error" or None (default). If "warning" or "error" is selected
and no positions are given (an empty list), a warning or error is raised respectively.
"""
# Handle list of positions
if selection == "random":
chosen_pos = agent.random.choice(pos)
elif selection == "closest":
current_pos = agent.pos
# Find the closest position without sorting all positions
closest_pos = None
min_distance = float("inf")
for p in pos:
distance = self._distance_squared(p, current_pos)
if distance < min_distance:
min_distance = distance
closest_pos = p
chosen_pos = closest_pos
else:
# Only move agent if there are positions given (non-empty list)
if pos:
if selection == "random":
chosen_pos = agent.random.choice(pos)
elif selection == "closest":
current_pos = agent.pos
# Find the closest position without sorting all positions
closest_pos = None
min_distance = float("inf")
for p in pos:
distance = self._distance_squared(p, current_pos)
if distance < min_distance:
min_distance = distance
closest_pos = p
chosen_pos = closest_pos
else:
raise ValueError(
f"Invalid selection method {selection}. Choose 'random' or 'closest'."
)
# Move agent to chosen position
self.move_agent(agent, chosen_pos)

# If no positions are given, throw warning/error if selected
elif handle_empty == "warning":
warn(
f"No positions given, could not move agent {agent.unique_id}.",
RuntimeWarning,
stacklevel=2,
)
elif handle_empty == "error":
raise ValueError(
f"Invalid selection method {selection}. Choose 'random' or 'closest'."
f"No positions given, could not move agent {agent.unique_id}."
)

# Move agent
self.move_agent(agent, chosen_pos)

def _distance_squared(self, pos1: Coordinate, pos2: Coordinate) -> float:
"""
Calculate the squared Euclidean distance between two points for performance.
Expand Down
24 changes: 24 additions & 0 deletions tests/test_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,30 @@ def test_move_agent_invalid_selection(self):
agent, possible_positions, selection="invalid_option"
)

def test_move_agent_empty_list(self):
agent = self.agents[0]
possible_positions = []
agent.pos = (3, 3)
self.space.move_agent_to_one_of(agent, possible_positions, selection="random")
assert agent.pos == (3, 3)

def test_move_agent_empty_list_warning(self):
agent = self.agents[0]
possible_positions = []
# Should assert RuntimeWarning
with self.assertWarns(RuntimeWarning):
self.space.move_agent_to_one_of(
agent, possible_positions, selection="random", handle_empty="warning"
)

def test_move_agent_empty_list_error(self):
agent = self.agents[0]
possible_positions = []
with self.assertRaises(ValueError):
self.space.move_agent_to_one_of(
agent, possible_positions, selection="random", handle_empty="error"
)

def test_distance_squared_torus(self):
pos1 = (0, 0)
pos2 = (49, 49)
Expand Down

0 comments on commit 03d0c81

Please sign in to comment.