|
39 | 39 | FloatCoordinate = Union[Tuple[float, float], np.ndarray] |
40 | 40 |
|
41 | 41 |
|
| 42 | +def clamp(x: float, lowest: float, highest: float) -> float: |
| 43 | + # This should be faster than np.clip for a scalar x. |
| 44 | + # TODO: measure how much faster this function is. |
| 45 | + return max(lowest, min(x, highest)) |
| 46 | + |
| 47 | + |
42 | 48 | def accept_tuple_argument(wrapped_function): |
43 | 49 | """Decorator to allow grid methods that take a list of (x, y) coord tuples |
44 | 50 | to also handle a single position, by automatically wrapping tuple in |
@@ -410,12 +416,46 @@ def is_cell_empty(self, pos: Coordinate) -> bool: |
410 | 416 | x, y = pos |
411 | 417 | return self.grid[x][y] == self.default_val() |
412 | 418 |
|
413 | | - def move_to_empty(self, agent: Agent) -> None: |
| 419 | + def move_to_empty( |
| 420 | + self, agent: Agent, cutoff: float = 0.998, num_agents: Optional[int] = None |
| 421 | + ) -> None: |
414 | 422 | """Moves agent to a random empty cell, vacating agent's old cell.""" |
415 | 423 | pos = agent.pos |
416 | 424 | if len(self.empties) == 0: |
417 | 425 | raise Exception("ERROR: No empty cells") |
418 | | - new_pos = agent.random.choice(sorted(self.empties)) |
| 426 | + if num_agents is None: |
| 427 | + try: |
| 428 | + num_agents = agent.model.schedule.get_agent_count() |
| 429 | + except AttributeError: |
| 430 | + raise Exception( |
| 431 | + "Your agent is not attached to a model, and so Mesa is unable\n" |
| 432 | + "to figure out the total number of agents you have created.\n" |
| 433 | + "This number is required in order to calculate the threshold\n" |
| 434 | + "for using a much faster algorithm to find an empty cell.\n" |
| 435 | + "In this case, you must specify `num_agents`." |
| 436 | + ) |
| 437 | + new_pos = (0, 0) # Initialize it with a starting value. |
| 438 | + # This method is based on Agents.jl's random_empty() implementation. |
| 439 | + # See https://github.com/JuliaDynamics/Agents.jl/pull/541. |
| 440 | + # For the discussion, see |
| 441 | + # https://github.com/projectmesa/mesa/issues/1052. |
| 442 | + # This switch assumes the worst case (for this algorithm) of one |
| 443 | + # agent per position, which is not true in general but is appropriate |
| 444 | + # here. |
| 445 | + if clamp(num_agents / (self.width * self.height), 0.0, 1.0) < cutoff: |
| 446 | + # The default cutoff value provided is the break-even comparison |
| 447 | + # with the time taken in the else branching point. |
| 448 | + # The number is measured to be 0.998 in Agents.jl, but since Mesa |
| 449 | + # run under different environment, the number is different here. |
| 450 | + while True: |
| 451 | + new_pos = ( |
| 452 | + agent.random.randrange(self.width), |
| 453 | + agent.random.randrange(self.height), |
| 454 | + ) |
| 455 | + if self.is_cell_empty(new_pos): |
| 456 | + break |
| 457 | + else: |
| 458 | + new_pos = agent.random.choice(sorted(self.empties)) |
419 | 459 | self._place_agent(new_pos, agent) |
420 | 460 | agent.pos = new_pos |
421 | 461 | self._remove_agent(pos, agent) |
|
0 commit comments