diff --git a/mesa/space.py b/mesa/space.py index 3158cca263d..987afaf9b51 100644 --- a/mesa/space.py +++ b/mesa/space.py @@ -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. @@ -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. diff --git a/tests/test_space.py b/tests/test_space.py index 221fbf861e5..ab0e0da3cc0 100644 --- a/tests/test_space.py +++ b/tests/test_space.py @@ -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)