Repository: projectmesa/mesa
Pull request #2260 (2024-08-30 12:14:03)

track unique_id automatically
This PR ensures that the `unique_id` in Agent is assigned automatically. It draws on work done in #2226. In short, this PR makes 3 changes: 

- `Agent.unique_id` is assigned automatically and no longer used as an argument to Agent. A deprecation warning is issued if the Agent is instantiated with 2 arguments (`unique_id` and `model`). The value passed for `unique_id` is ignored in favor of the new system. This last point is in line with #2226.
- `Model.next_id()` raises a deprecation warning and always return 0. 
-  `unique_id` is unique relative to a Model instance. Internally, Agent has a class-level attribute `_ids`. This attribute is a dictionary with the Model instance as key and the "generator" for `unique_id` as value.

All unit tests and benchmarks work with the new structure. They raise no deprecation warnings. 

examples, tutorials, etc., still need to be updated to be consistent with this change.


github-actions[bot]: Performance benchmarks:

| Model           | Size   | Init time [95% CI]          | Run time [95% CI]         |
|-----------------|--------|-----------------------------|---------------------------|
| BoltzmannWealth | small  | 🔴 +72.1% [+69.9%, +74.2%]  | 🔵 +0.5% [+0.4%, +0.6%]   |
| BoltzmannWealth | large  | 🔴 +60.9% [+21.5%, +108.1%] | 🔴 +6.0% [+3.1%, +8.7%]   |
| Schelling       | small  | 🔴 +23.7% [+23.3%, +24.0%]  | 🔵 +1.4% [+1.1%, +1.7%]   |
| Schelling       | large  | 🔴 +27.9% [+27.3%, +28.8%]  | 🔵 +5.9% [+3.0%, +9.0%]   |
| WolfSheep       | small  | 🔴 +37.4% [+36.3%, +38.3%]  | 🔴 +7.8% [+7.5%, +8.0%]   |
| WolfSheep       | large  | 🔴 +37.1% [+36.3%, +37.9%]  | 🔴 +11.7% [+8.9%, +14.0%] |
| BoidFlockers    | small  | 🔴 +33.0% [+31.8%, +34.2%]  | 🔵 +3.1% [+2.4%, +3.8%]   |
| BoidFlockers    | large  | 🔴 +33.5% [+32.0%, +34.9%]  | 🔵 +3.7% [+3.0%, +4.2%]   |

Corvince: What I don't understand, but unfortunately can't test right now: doesn't this keep increasing the counter indefinitely, even across models? So if I run the same model twice without restarting my kernel, won't the ids be different? 
EwoutH: I would like all maintainers to check our Matrix chat before moving further.
quaquel: I continued some work on this, mainly to understand how breaking automatic counting actually is. And it indeed creates a massive mess throughout the various tests, including very old test code that probably has not been touched in a long while (e.g., the test code of the schedulers). 

I did 3 things here
* `_ids` has become essentially a defaultdict with the model as key and the intertools.count(1) as value. This means that (a) `unique_id` is unique relative to the model (so option 2 above) and (b) that unique_id starts from 1 just as the old code did
* removed model.next_id 
* made the various tests work again. 

Like in #2226, The code here still accepts a unique_id and raises a warning. If you remove that, the number of tests that fails explodes again. There are many tests that use custom unique ids (so not even model.next_id. 

Not sure where this leaves #2226 or this one. It seems there is broad agreement on having unique_id assigned automatically in MESA 3, but it is equally clear that this involves quite some more work depending on how breaking this new feature is implemented. 

github-actions[bot]: Performance benchmarks:

| Model           | Size   | Init time [95% CI]         | Run time [95% CI]       |
|-----------------|--------|----------------------------|-------------------------|
| BoltzmannWealth | small  | 🔴 +12.4% [+10.8%, +13.8%] | 🔵 -1.0% [-1.3%, -0.7%] |
| BoltzmannWealth | large  | 🔵 -9.6% [-26.2%, +6.8%]   | 🟢 -5.3% [-6.8%, -3.5%] |
| Schelling       | small  | 🔵 +1.7% [+1.3%, +2.1%]    | 🔵 +0.3% [+0.0%, +0.5%] |
| Schelling       | large  | 🔵 +1.5% [+0.6%, +2.4%]    | 🔵 +1.5% [+0.2%, +3.1%] |
| WolfSheep       | small  | 🔴 +4.3% [+3.3%, +5.1%]    | 🔵 +2.0% [+1.7%, +2.3%] |
| WolfSheep       | large  | 🔵 +3.2% [+2.7%, +3.7%]    | 🔵 +2.3% [+1.7%, +3.0%] |
| BoidFlockers    | small  | 🔵 +3.6% [+3.0%, +4.2%]    | 🔵 +0.2% [-0.7%, +1.0%] |
| BoidFlockers    | large  | 🔵 +3.6% [+2.8%, +4.3%]    | 🔵 -0.2% [-0.6%, +0.2%] |


Patch:
diff --git a/benchmarks/BoltzmannWealth/boltzmann_wealth.py b/benchmarks/BoltzmannWealth/boltzmann_wealth.py
index f6b1bf3d..56443c6f 100644
--- a/benchmarks/BoltzmannWealth/boltzmann_wealth.py
+++ b/benchmarks/BoltzmannWealth/boltzmann_wealth.py
@@ -27,8 +27,8 @@ class BoltzmannWealth(mesa.Model):
             model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"}
         )
         # Create agents
-        for i in range(self.num_agents):
-            a = MoneyAgent(i, self)
+        for _ in range(self.num_agents):
+            a = MoneyAgent(self)
             # Add the agent to a random grid cell
             x = self.random.randrange(self.grid.width)
             y = self.random.randrange(self.grid.height)
@@ -50,8 +50,8 @@ class BoltzmannWealth(mesa.Model):
 class MoneyAgent(mesa.Agent):
     """An agent with fixed initial wealth."""
 
-    def __init__(self, unique_id, model):
-        super().__init__(unique_id, model)
+    def __init__(self, model):
+        super().__init__(model)
         self.wealth = 1
 
     def move(self):
diff --git a/benchmarks/Flocking/flocking.py b/benchmarks/Flocking/flocking.py
index ff9f97c0..284d8318 100644
--- a/benchmarks/Flocking/flocking.py
+++ b/benchmarks/Flocking/flocking.py
@@ -27,7 +27,6 @@ class Boid(mesa.Agent):
 
     def __init__(
         self,
-        unique_id,
         model,
         speed,
         direction,
@@ -41,7 +40,6 @@ class Boid(mesa.Agent):
         Create a new Boid flocker agent.
 
         Args:
-            unique_id: Unique agent identifier.
             speed: Distance to move per step.
             direction: numpy vector for the Boid's direction of movement.
             vision: Radius to look around for nearby Boids.
@@ -51,7 +49,7 @@ class Boid(mesa.Agent):
             match: the relative importance of matching neighbors' directions
 
         """
-        super().__init__(unique_id, model)
+        super().__init__(model)
         self.speed = speed
         self.direction = direction
         self.vision = vision
@@ -131,13 +129,12 @@ class BoidFlockers(mesa.Model):
             "match": match,
         }
 
-        for i in range(self.population):
+        for _ in range(self.population):
             x = self.random.random() * self.space.x_max
             y = self.random.random() * self.space.y_max
             pos = np.array((x, y))
             direction = np.random.random(2) * 2 - 1
             boid = Boid(
-                unique_id=i,
                 model=self,
                 speed=speed,
                 direction=direction,
diff --git a/benchmarks/Schelling/schelling.py b/benchmarks/Schelling/schelling.py
index b7702a84..b582e7af 100644
--- a/benchmarks/Schelling/schelling.py
+++ b/benchmarks/Schelling/schelling.py
@@ -8,15 +8,14 @@ class SchellingAgent(CellAgent):
     Schelling segregation agent
     """
 
-    def __init__(self, unique_id, model, agent_type, radius, homophily):
+    def __init__(self, model, agent_type, radius, homophily):
         """
         Create a new Schelling agent.
         Args:
-           unique_id: Unique identifier for the agent.
            x, y: Agent initial location.
            agent_type: Indicator for the agent's type (minority=1, majority=0)
         """
-        super().__init__(unique_id, model)
+        super().__init__(model)
         self.type = agent_type
         self.radius = radius
         self.homophily = homophily
@@ -81,9 +80,7 @@ class Schelling(Model):
         for cell in self.grid:
             if self.random.random() < density:
                 agent_type = 1 if self.random.random() < self.minority_pc else 0
-                agent = SchellingAgent(
-                    self.next_id(), self, agent_type, radius, homophily
-                )
+                agent = SchellingAgent(self, agent_type, radius, homophily)
                 agent.move_to(cell)
                 self.schedule.add(agent)
 
diff --git a/benchmarks/WolfSheep/wolf_sheep.py b/benchmarks/WolfSheep/wolf_sheep.py
index 655d51bb..ceb6efef 100644
--- a/benchmarks/WolfSheep/wolf_sheep.py
+++ b/benchmarks/WolfSheep/wolf_sheep.py
@@ -17,8 +17,8 @@ from mesa.experimental.devs import ABMSimulator
 
 
 class Animal(CellAgent):
-    def __init__(self, unique_id, model, energy, p_reproduce, energy_from_food):
-        super().__init__(unique_id, model)
+    def __init__(self, model, energy, p_reproduce, energy_from_food):
+        super().__init__(model)
         self.energy = energy
         self.p_reproduce = p_reproduce
         self.energy_from_food = energy_from_food
@@ -29,7 +29,6 @@ class Animal(CellAgent):
     def spawn_offspring(self):
         self.energy /= 2
         offspring = self.__class__(
-            self.model.next_id(),
             self.model,
             self.energy,
             self.p_reproduce,
@@ -107,7 +106,7 @@ class GrassPatch(CellAgent):
                 function_args=[self, "fully_grown", True],
             )
 
-    def __init__(self, unique_id, model, fully_grown, countdown, grass_regrowth_time):
+    def __init__(self, model, fully_grown, countdown, grass_regrowth_time):
         """
         TODO:: fully grown can just be an int --> so one less param (i.e. countdown)
 
@@ -119,7 +118,7 @@ class GrassPatch(CellAgent):
             grass_regrowth_time : time to fully regrow grass
             countdown : Time for the patch of grass to be fully regrown if fully grown is False
         """
-        super().__init__(unique_id, model)
+        super().__init__(model)
         self._fully_grown = fully_grown
         self.grass_regrowth_time = grass_regrowth_time
 
@@ -189,7 +188,6 @@ class WolfSheep(Model):
             )
             energy = self.random.randrange(2 * sheep_gain_from_food)
             sheep = Sheep(
-                self.next_id(),
                 self,
                 energy,
                 sheep_reproduce,
@@ -205,7 +203,6 @@ class WolfSheep(Model):
             )
             energy = self.random.randrange(2 * wolf_gain_from_food)
             wolf = Wolf(
-                self.next_id(),
                 self,
                 energy,
                 wolf_reproduce,
@@ -221,9 +218,7 @@ class WolfSheep(Model):
                 countdown = grass_regrowth_time
             else:
                 countdown = self.random.randrange(grass_regrowth_time)
-            patch = GrassPatch(
-                self.next_id(), self, fully_grown, countdown, grass_regrowth_time
-            )
+            patch = GrassPatch(self, fully_grown, countdown, grass_regrowth_time)
             patch.move_to(cell)
 
     def step(self):
diff --git a/docs/tutorials/MoneyModel.py b/docs/tutorials/MoneyModel.py
index 94555f98..93e19dec 100644
--- a/docs/tutorials/MoneyModel.py
+++ b/docs/tutorials/MoneyModel.py
@@ -15,8 +15,8 @@ def compute_gini(model):
 class MoneyAgent(mesa.Agent):
     """An agent with fixed initial wealth."""
 
-    def __init__(self, unique_id, model):
-        super().__init__(unique_id, model)
+    def __init__(self, model):
+        super().__init__(model)
         self.wealth = 1
 
     def move(self):
diff --git a/mesa/agent.py b/mesa/agent.py
index 8d04df9e..225e89cd 100644
--- a/mesa/agent.py
+++ b/mesa/agent.py
@@ -10,6 +10,8 @@ from __future__ import annotations
 
 import contextlib
 import copy
+import functools
+import itertools
 import operator
 import warnings
 import weakref
@@ -32,21 +34,49 @@ class Agent:
     Base class for a model agent in Mesa.
 
     Attributes:
-        unique_id (int): A unique identifier for this agent.
         model (Model): A reference to the model instance.
-        self.pos: Position | None = None
+        unique_id (int): A unique identifier for this agent.
+        pos (Position): A reference to the position where this agent is located.
+
+    Notes:
+          unique_id is unique relative to a model instance and starts from 1
+
     """
 
-    def __init__(self, unique_id: int, model: Model) -> None:
+    # this is a class level attribute
+    # it is a dictionary, indexed by model instance
+    # so, unique_id is unique relative to a model, and counting starts from 1
+    _ids = defaultdict(functools.partial(itertools.count, 1))
+
+    def __init__(self, *args, **kwargs) -> None:
         """
         Create a new agent.
 
         Args:
-            unique_id (int): A unique identifier for this agent.
             model (Model): The model instance in which the agent exists.
         """
-        self.unique_id = unique_id
-        self.model = model
+        # TODO: Cleanup in future Mesa version (3.1+)
+        match args:
+            # Case 1: Only the model is provided. The new correct behavior.
+            case [model]:
+                self.model = model
+                self.unique_id = next(self._ids[model])
+            # Case 2: Both unique_id and model are provided, deprecated
+            case [_, model]:
+                warnings.warn(
+                    "unique ids are assigned automatically to Agents in Mesa 3. The use of custom unique_id is "
+                    "deprecated. Only input a model when calling `super()__init__(model)`. The unique_id inputted is not used.",
+                    DeprecationWarning,
+                    stacklevel=2,
+                )
+                self.model = model
+                self.unique_id = next(self._ids[model])
+            # Case 3: Anything else, raise an error
+            case _:
+                raise ValueError(
+                    "Invalid arguments provided to initialize the Agent. Only input a model: `super()__init__(model)`."
+                )
+
         self.pos: Position | None = None
 
         self.model.register_agent(self)
diff --git a/mesa/experimental/cell_space/cell_agent.py b/mesa/experimental/cell_space/cell_agent.py
index abc5155a..5f1cca5c 100644
--- a/mesa/experimental/cell_space/cell_agent.py
+++ b/mesa/experimental/cell_space/cell_agent.py
@@ -19,7 +19,7 @@ class CellAgent(Agent):
         cell: (Cell | None): the cell which the agent occupies
     """
 
-    def __init__(self, unique_id: int, model: Model) -> None:
+    def __init__(self, model: Model) -> None:
         """
         Create a new agent.
 
@@ -27,7 +27,7 @@ class CellAgent(Agent):
             unique_id (int): A unique identifier for this agent.
             model (Model): The model instance in which the agent exists.
         """
-        super().__init__(unique_id, model)
+        super().__init__(model)
         self.cell: Cell | None = None
 
     def move_to(self, cell) -> None:
diff --git a/mesa/experimental/devs/examples/epstein_civil_violence.py b/mesa/experimental/devs/examples/epstein_civil_violence.py
index 9a7314e0..c976bb73 100644
--- a/mesa/experimental/devs/examples/epstein_civil_violence.py
+++ b/mesa/experimental/devs/examples/epstein_civil_violence.py
@@ -7,8 +7,8 @@ from mesa.space import SingleGrid
 
 
 class EpsteinAgent(Agent):
-    def __init__(self, unique_id, model, vision, movement):
-        super().__init__(unique_id, model)
+    def __init__(self, model, vision, movement):
+        super().__init__(model)
         self.vision = vision
         self.movement = movement
 
@@ -46,7 +46,6 @@ class Citizen(EpsteinAgent):
 
     def __init__(
         self,
-        unique_id,
         model,
         vision,
         movement,
@@ -59,7 +58,6 @@ class Citizen(EpsteinAgent):
         """
         Create a new Citizen.
         Args:
-            unique_id: unique int
             model : model instance
             hardship: Agent's 'perceived hardship (i.e., physical or economic
                 privation).' Exogenous, drawn from U(0,1).
@@ -71,7 +69,7 @@ class Citizen(EpsteinAgent):
             vision: number of cells in each direction (N, S, E and W) that
                 agent can inspect. Exogenous.
         """
-        super().__init__(unique_id, model, vision, movement)
+        super().__init__(model, vision, movement)
         self.hardship = hardship
         self.regime_legitimacy = regime_legitimacy
         self.risk_aversion = risk_aversion
@@ -144,8 +142,8 @@ class Cop(EpsteinAgent):
             able to inspect
     """
 
-    def __init__(self, unique_id, model, vision, movement, max_jail_term):
-        super().__init__(unique_id, model, vision, movement)
+    def __init__(self, model, vision, movement, max_jail_term):
+        super().__init__(model, vision, movement)
         self.max_jail_term = max_jail_term
 
     def step(self):
@@ -236,7 +234,6 @@ class EpsteinCivilViolence(Model):
         for _, pos in self.grid.coord_iter():
             if self.random.random() < self.cop_density:
                 agent = Cop(
-                    self.next_id(),
                     self,
                     cop_vision,
                     movement,
@@ -244,7 +241,6 @@ class EpsteinCivilViolence(Model):
                 )
             elif self.random.random() < (self.cop_density + self.citizen_density):
                 agent = Citizen(
-                    self.next_id(),
                     self,
                     citizen_vision,
                     movement,
@@ -270,4 +266,4 @@ if __name__ == "__main__":
 
     simulator.setup(model)
 
-    simulator.run(time_delta=100)
+    simulator.run_for(time_delta=100)
diff --git a/mesa/experimental/devs/examples/wolf_sheep.py b/mesa/experimental/devs/examples/wolf_sheep.py
index 99fde6a9..cd81cb2f 100644
--- a/mesa/experimental/devs/examples/wolf_sheep.py
+++ b/mesa/experimental/devs/examples/wolf_sheep.py
@@ -14,8 +14,8 @@ from mesa.experimental.devs.simulator import ABMSimulator
 
 
 class Animal(mesa.Agent):
-    def __init__(self, unique_id, model, moore, energy, p_reproduce, energy_from_food):
-        super().__init__(unique_id, model)
+    def __init__(self, model, moore, energy, p_reproduce, energy_from_food):
+        super().__init__(model)
         self.energy = energy
         self.p_reproduce = p_reproduce
         self.energy_from_food = energy_from_food
@@ -30,7 +30,6 @@ class Animal(mesa.Agent):
     def spawn_offspring(self):
         self.energy /= 2
         offspring = self.__class__(
-            self.model.next_id(),
             self.model,
             self.moore,
             self.energy,
@@ -109,7 +108,7 @@ class GrassPatch(mesa.Agent):
                 function_args=[self, "fully_grown", True],
             )
 
-    def __init__(self, unique_id, model, fully_grown, countdown, grass_regrowth_time):
+    def __init__(self, model, fully_grown, countdown, grass_regrowth_time):
         """
         Creates a new patch of grass
 
@@ -117,7 +116,7 @@ class GrassPatch(mesa.Agent):
             grown: (boolean) Whether the patch of grass is fully grown or not
             countdown: Time for the patch of grass to be fully grown again
         """
-        super().__init__(unique_id, model)
+        super().__init__(model)
         self._fully_grown = fully_grown
         self.grass_regrowth_time = grass_regrowth_time
 
@@ -191,7 +190,6 @@ class WolfSheep(mesa.Model):
             )
             energy = self.random.randrange(2 * sheep_gain_from_food)
             sheep = Sheep(
-                self.next_id(),
                 self,
                 moore,
                 energy,
@@ -208,7 +206,6 @@ class WolfSheep(mesa.Model):
             )
             energy = self.random.randrange(2 * wolf_gain_from_food)
             wolf = Wolf(
-                self.next_id(),
                 self,
                 moore,
                 energy,
@@ -225,9 +222,7 @@ class WolfSheep(mesa.Model):
                 countdown = grass_regrowth_time
             else:
                 countdown = self.random.randrange(grass_regrowth_time)
-            patch = GrassPatch(
-                self.next_id(), self, fully_grown, countdown, grass_regrowth_time
-            )
+            patch = GrassPatch(self, fully_grown, countdown, grass_regrowth_time)
             self.grid.place_agent(patch, pos)
 
     def step(self):
diff --git a/mesa/model.py b/mesa/model.py
index f276cc82..f859aea3 100644
--- a/mesa/model.py
+++ b/mesa/model.py
@@ -28,7 +28,6 @@ class Model:
     Attributes:
         running: A boolean indicating if the model should continue running.
         schedule: An object to manage the order and execution of agent steps.
-        current_id: A counter for assigning unique IDs to agents.
 
     Properties:
         agents: An AgentSet containing all agents in the model
@@ -74,7 +73,6 @@ class Model:
 
         self.running = True
         self.schedule = None
-        self.current_id = 0
         self.steps: int = 0
 
         self._setup_agent_registration()
@@ -90,6 +88,14 @@ class Model:
         # Call the original user-defined step method
         self._user_step(*args, **kwargs)
 
+    def next_id(self) -> int:
+        warnings.warn(
+            "using model.next_id() is deprecated. Agents track their unique ID automatically",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return 0
+
     @property
     def agents(self) -> AgentSet:
         """Provides an AgentSet of all agents in the model, combining agents from all types."""
@@ -189,11 +195,6 @@ class Model:
     def step(self) -> None:
         """A single step. Fill in here."""
 
-    def next_id(self) -> int:
-        """Return the next unique ID for agents, increment current_id"""
-        self.current_id += 1
-        return self.current_id
-
     def reset_randomizer(self, seed: int | None = None) -> None:
         """Reset the model random number generator.
 


Test patch:
diff --git a/tests/test_agent.py b/tests/test_agent.py
index bb61ae1c..e78a4dce 100644
--- a/tests/test_agent.py
+++ b/tests/test_agent.py
@@ -15,17 +15,16 @@ class TestAgent(Agent):
 class TestAgentDo(Agent):
     def __init__(
         self,
-        unique_id,
         model,
     ):
-        super().__init__(unique_id, model)
+        super().__init__(model)
         self.agent_set = None
 
     def get_unique_identifier(self):
         return self.unique_id
 
     def do_add(self):
-        agent = TestAgentDo(self.model.next_id(), self.model)
+        agent = TestAgentDo(self.model)
         self.agent_set.add(agent)
 
     def do_remove(self):
@@ -34,7 +33,7 @@ class TestAgentDo(Agent):
 
 def test_agent_removal():
     model = Model()
-    agent = TestAgent(model.next_id(), model)
+    agent = TestAgent(model)
     # Check if the agent is added
     assert agent in model.agents
 
@@ -46,7 +45,7 @@ def test_agent_removal():
 def test_agentset():
     # create agentset
     model = Model()
-    agents = [TestAgent(model.next_id(), model) for _ in range(10)]
+    agents = [TestAgent(model) for _ in range(10)]
 
     agentset = AgentSet(agents, model)
 
@@ -117,14 +116,14 @@ def test_agentset_initialization():
     empty_agentset = AgentSet([], model)
     assert len(empty_agentset) == 0
 
-    agents = [TestAgent(model.next_id(), model) for _ in range(10)]
+    agents = [TestAgent(model) for _ in range(10)]
     agentset = AgentSet(agents, model)
     assert len(agentset) == 10
 
 
 def test_agentset_serialization():
     model = Model()
-    agents = [TestAgent(model.next_id(), model) for _ in range(5)]
+    agents = [TestAgent(model) for _ in range(5)]
     agentset = AgentSet(agents, model)
 
     serialized = pickle.dumps(agentset)
@@ -138,16 +137,16 @@ def test_agentset_serialization():
 
 def test_agent_membership():
     model = Model()
-    agents = [TestAgent(model.next_id(), model) for _ in range(5)]
+    agents = [TestAgent(model) for _ in range(5)]
     agentset = AgentSet(agents, model)
 
     assert agents[0] in agentset
-    assert TestAgent(model.next_id(), model) not in agentset
+    assert TestAgent(model) not in agentset
 
 
 def test_agent_add_remove_discard():
     model = Model()
-    agent = TestAgent(model.next_id(), model)
+    agent = TestAgent(model)
     agentset = AgentSet([], model)
 
     agentset.add(agent)
@@ -166,7 +165,7 @@ def test_agent_add_remove_discard():
 
 def test_agentset_get_item():
     model = Model()
-    agents = [TestAgent(model.next_id(), model) for _ in range(10)]
+    agents = [TestAgent(model) for _ in range(10)]
     agentset = AgentSet(agents, model)
 
     assert agentset[0] == agents[0]
@@ -179,7 +178,7 @@ def test_agentset_get_item():
 
 def test_agentset_do_str():
     model = Model()
-    agents = [TestAgent(model.next_id(), model) for _ in range(10)]
+    agents = [TestAgent(model) for _ in range(10)]
     agentset = AgentSet(agents, model)
 
     with pytest.raises(AttributeError):
@@ -192,7 +191,7 @@ def test_agentset_do_str():
     # setup
     n = 10
     model = Model()
-    agents = [TestAgentDo(model.next_id(), model) for _ in range(n)]
+    agents = [TestAgentDo(model) for _ in range(n)]
     agentset = AgentSet(agents, model)
     for agent in agents:
         agent.agent_set = agentset
@@ -202,7 +201,7 @@ def test_agentset_do_str():
 
     # setup
     model = Model()
-    agents = [TestAgentDo(model.next_id(), model) for _ in range(10)]
+    agents = [TestAgentDo(model) for _ in range(10)]
     agentset = AgentSet(agents, model)
     for agent in agents:
         agent.agent_set = agentset
@@ -213,7 +212,7 @@ def test_agentset_do_str():
 
 def test_agentset_do_callable():
     model = Model()
-    agents = [TestAgent(model.next_id(), model) for _ in range(10)]
+    agents = [TestAgent(model) for _ in range(10)]
     agentset = AgentSet(agents, model)
 
     # Test callable with non-existent function
@@ -227,7 +226,7 @@ def test_agentset_do_callable():
     # setup for lambda function tests
     n = 10
     model = Model()
-    agents = [TestAgentDo(model.next_id(), model) for _ in range(n)]
+    agents = [TestAgentDo(model) for _ in range(n)]
     agentset = AgentSet(agents, model)
     for agent in agents:
         agent.agent_set = agentset
@@ -238,7 +237,7 @@ def test_agentset_do_callable():
 
     # setup again for lambda function tests
     model = Model()
-    agents = [TestAgentDo(model.next_id(), model) for _ in range(10)]
+    agents = [TestAgentDo(model) for _ in range(10)]
     agentset = AgentSet(agents, model)
     for agent in agents:
         agent.agent_set = agentset
@@ -256,7 +255,7 @@ def test_agentset_do_callable():
 
     # setup again for actual function tests
     model = Model()
-    agents = [TestAgentDo(model.next_id(), model) for _ in range(n)]
+    agents = [TestAgentDo(model) for _ in range(n)]
     agentset = AgentSet(agents, model)
     for agent in agents:
         agent.agent_set = agentset
@@ -267,7 +266,7 @@ def test_agentset_do_callable():
 
     # setup again for actual function tests
     model = Model()
-    agents = [TestAgentDo(model.next_id(), model) for _ in range(10)]
+    agents = [TestAgentDo(model) for _ in range(10)]
     agentset = AgentSet(agents, model)
     for agent in agents:
         agent.agent_set = agentset
@@ -279,7 +278,7 @@ def test_agentset_do_callable():
 
 def test_agentset_agg():
     model = Model()
-    agents = [TestAgent(i, model) for i in range(10)]
+    agents = [TestAgent(model) for i in range(10)]
 
     # Assign some values to attributes
     for i, agent in enumerate(agents):
@@ -315,12 +314,12 @@ def test_agentset_agg():
 def test_agentset_set_method():
     # Initialize the model and agents with and without existing attributes
     class TestAgentWithAttribute(Agent):
-        def __init__(self, unique_id, model, age=None):
-            super().__init__(unique_id, model)
+        def __init__(self, model, age=None):
+            super().__init__(model)
             self.age = age
 
     model = Model()
-    agents = [TestAgentWithAttribute(model.next_id(), model, age=i) for i in range(5)]
+    agents = [TestAgentWithAttribute(model, age=i) for i in range(5)]
     agentset = AgentSet(agents, model)
 
     # Set a new attribute "health" and an existing attribute "age" for all agents
@@ -338,7 +337,7 @@ def test_agentset_set_method():
 
 def test_agentset_map_str():
     model = Model()
-    agents = [TestAgent(model.next_id(), model) for _ in range(10)]
+    agents = [TestAgent(model) for _ in range(10)]
     agentset = AgentSet(agents, model)
 
     with pytest.raises(AttributeError):
@@ -350,7 +349,7 @@ def test_agentset_map_str():
 
 def test_agentset_map_callable():
     model = Model()
-    agents = [TestAgent(model.next_id(), model) for _ in range(10)]
+    agents = [TestAgent(model) for _ in range(10)]
     agentset = AgentSet(agents, model)
 
     # Test callable with non-existent function
@@ -367,7 +366,7 @@ def test_agentset_map_callable():
 
 def test_agentset_get_attribute():
     model = Model()
-    agents = [TestAgent(model.next_id(), model) for _ in range(10)]
+    agents = [TestAgent(model) for _ in range(10)]
     agentset = AgentSet(agents, model)
 
     unique_ids = agentset.get("unique_id")
@@ -379,7 +378,7 @@ def test_agentset_get_attribute():
     model = Model()
     agents = []
     for i in range(10):
-        agent = TestAgent(model.next_id(), model)
+        agent = TestAgent(model)
         agent.i = i**2
         agents.append(agent)
     agentset = AgentSet(agents, model)
@@ -403,8 +402,8 @@ class OtherAgentType(Agent):
 def test_agentset_select_by_type():
     model = Model()
     # Create a mix of agents of two different types
-    test_agents = [TestAgent(model.next_id(), model) for _ in range(4)]
-    other_agents = [OtherAgentType(model.next_id(), model) for _ in range(6)]
+    test_agents = [TestAgent(model) for _ in range(4)]
+    other_agents = [OtherAgentType(model) for _ in range(6)]
 
     # Combine the two types of agents
     mixed_agents = test_agents + other_agents
@@ -428,7 +427,7 @@ def test_agentset_select_by_type():
 
 def test_agentset_shuffle():
     model = Model()
-    test_agents = [TestAgent(model.next_id(), model) for _ in range(12)]
+    test_agents = [TestAgent(model) for _ in range(12)]
 
     agentset = AgentSet(test_agents, model=model)
     agentset = agentset.shuffle()
@@ -441,15 +440,15 @@ def test_agentset_shuffle():
 
 def test_agentset_groupby():
     class TestAgent(Agent):
-        def __init__(self, unique_id, model):
-            super().__init__(unique_id, model)
+        def __init__(self, model):
+            super().__init__(model)
             self.even = self.unique_id % 2 == 0
 
         def get_unique_identifier(self):
             return self.unique_id
 
     model = Model()
-    agents = [TestAgent(model.next_id(), model) for _ in range(10)]
+    agents = [TestAgent(model) for _ in range(10)]
     agentset = AgentSet(agents, model)
 
     groups = agentset.groupby("even")
@@ -479,3 +478,14 @@ def test_agentset_groupby():
     groups = agentset.groupby("even", result_type="agentset")
     another_ref_to_groups = groups.do(lambda x: x.do("step"))
     assert groups == another_ref_to_groups
+
+
+def test_oldstyle_agent_instantiation():
+    """Old behavior of Agent creation with unique_id and model as positional arguments.
+    Can be removed/updated in the future."""
+    model = Model()
+    agent = Agent("some weird unique id", model)
+    assert isinstance(agent.unique_id, int)
+    assert agent.model == model
+    assert isinstance(agent.model, Model)
+    assert agent.unique_id == 1  # test that we ignore unique ID that is passed
diff --git a/tests/test_batch_run.py b/tests/test_batch_run.py
index 2de7c447..784c0416 100644
--- a/tests/test_batch_run.py
+++ b/tests/test_batch_run.py
@@ -28,9 +28,8 @@ class MockAgent(Agent):
     Minimalistic agent implementation for testing purposes
     """
 
-    def __init__(self, unique_id, model, val):
-        super().__init__(unique_id, model)
-        self.unique_id = unique_id
+    def __init__(self, model, val):
+        super().__init__(model)
         self.val = val
         self.local = 0
 
@@ -76,8 +75,8 @@ class MockModel(Model):
             agent_val = 1
         else:
             agent_val = self.variable_agent_param
-        for i in range(self.n_agents):
-            self.schedule.add(MockAgent(i, self, agent_val))
+        for _ in range(self.n_agents):
+            self.schedule.add(MockAgent(self, agent_val))
 
     def get_local_model_param(self):
         return 42
@@ -95,8 +94,8 @@ def test_batch_run():
             "iteration": 0,
             "Step": 1000,
             "reported_model_param": 42,
-            "AgentID": 0,
-            "agent_id": 0,
+            "AgentID": 1,
+            "agent_id": 1,
             "agent_local": 250.0,
         },
         {
@@ -104,8 +103,8 @@ def test_batch_run():
             "iteration": 0,
             "Step": 1000,
             "reported_model_param": 42,
-            "AgentID": 1,
-            "agent_id": 1,
+            "AgentID": 2,
+            "agent_id": 2,
             "agent_local": 250.0,
         },
         {
@@ -113,8 +112,8 @@ def test_batch_run():
             "iteration": 0,
             "Step": 1000,
             "reported_model_param": 42,
-            "AgentID": 2,
-            "agent_id": 2,
+            "AgentID": 3,
+            "agent_id": 3,
             "agent_local": 250.0,
         },
     ]
@@ -172,29 +171,29 @@ def test_batch_run_unhashable_param():
         {
             "RunId": 0,
             "iteration": 0,
-            "AgentID": 0,
-            "agent_id": 0,
+            "AgentID": 1,
+            "agent_id": 1,
             **template,
         },
         {
             "RunId": 0,
             "iteration": 0,
-            "AgentID": 1,
-            "agent_id": 1,
+            "AgentID": 2,
+            "agent_id": 2,
             **template,
         },
         {
             "RunId": 1,
             "iteration": 1,
-            "AgentID": 0,
-            "agent_id": 0,
+            "AgentID": 1,
+            "agent_id": 1,
             **template,
         },
         {
             "RunId": 1,
             "iteration": 1,
-            "AgentID": 1,
-            "agent_id": 1,
+            "AgentID": 2,
+            "agent_id": 2,
             **template,
         },
     ]
diff --git a/tests/test_cell_space.py b/tests/test_cell_space.py
index a918f59e..4127847d 100644
--- a/tests/test_cell_space.py
+++ b/tests/test_cell_space.py
@@ -408,7 +408,7 @@ def test_empties_space():
 
     model = Model()
     for i in range(8):
-        grid._cells[i].add_agent(CellAgent(i, model))
+        grid._cells[i].add_agent(CellAgent(model))
 
     cell = grid.select_random_empty_cell()
     assert cell.coordinate in {8, 9}
@@ -432,7 +432,7 @@ def test_cell():
 
     # add_agent
     model = Model()
-    agent = CellAgent(1, model)
+    agent = CellAgent(model)
 
     cell1.add_agent(agent)
     assert agent in cell1.agents
@@ -445,11 +445,11 @@ def test_cell():
         cell1.remove_agent(agent)
 
     cell1 = Cell((1,), capacity=1, random=random.Random())
-    cell1.add_agent(CellAgent(1, model))
+    cell1.add_agent(CellAgent(model))
     assert cell1.is_full
 
     with pytest.raises(Exception):
-        cell1.add_agent(CellAgent(2, model))
+        cell1.add_agent(CellAgent(model))
 
 
 def test_cell_collection():
@@ -475,9 +475,9 @@ def test_cell_collection():
 
     cells = collection.cells
     model = Model()
-    cells[0].add_agent(CellAgent(1, model))
-    cells[3].add_agent(CellAgent(2, model))
-    cells[7].add_agent(CellAgent(3, model))
+    cells[0].add_agent(CellAgent(model))
+    cells[3].add_agent(CellAgent(model))
+    cells[7].add_agent(CellAgent(model))
     agents = collection.agents
     assert len(list(agents)) == 3
 
diff --git a/tests/test_datacollector.py b/tests/test_datacollector.py
index bf74e379..94e7742a 100644
--- a/tests/test_datacollector.py
+++ b/tests/test_datacollector.py
@@ -13,8 +13,8 @@ class MockAgent(Agent):
     Minimalistic agent for testing purposes.
     """
 
-    def __init__(self, unique_id, model, val=0):
-        super().__init__(unique_id, model)
+    def __init__(self, model, val=0):
+        super().__init__(model)
         self.val = val
         self.val2 = val
 
@@ -40,14 +40,6 @@ def agent_function_with_params(agent, multiplier, offset):
     return (agent.val * multiplier) + offset
 
 
-class DifferentMockAgent(MockAgent):
-    # We define a different MockAgent to test for attributes that are present
-    # only in 1 type of agent, but not the other.
-    def __init__(self, unique_id, model, val=0):
-        super().__init__(unique_id, model, val=val)
-        self.val3 = val + 42
-
-
 class MockModel(Model):
     """
     Minimalistic model for testing purposes.
@@ -61,8 +53,8 @@ class MockModel(Model):
         self.model_val = 100
 
         self.n = 10
-        for i in range(self.n):
-            self.schedule.add(MockAgent(i, self, val=i))
+        for i in range(1, self.n + 1):
+            self.schedule.add(MockAgent(self, val=i))
         self.initialize_data_collector(
             model_reporters={
                 "total_agents": lambda m: m.schedule.get_agent_count(),
diff --git a/tests/test_lifespan.py b/tests/test_lifespan.py
index cfd60cde..00dd878c 100644
--- a/tests/test_lifespan.py
+++ b/tests/test_lifespan.py
@@ -29,9 +29,7 @@ class LifeTimeModel(Model):
         self.schedule = RandomActivation(self)
 
         for _ in range(n_agents):
-            self.schedule.add(
-                FiniteLifeAgent(self.next_id(), self.agent_lifetime, self)
-            )
+            self.schedule.add(FiniteLifeAgent(self.agent_lifetime, self))
 
     def step(self):
         """Add agents back to n_agents in each step"""
@@ -40,9 +38,7 @@ class LifeTimeModel(Model):
 
         if len(self.schedule.agents) < self.n_agents:
             for _ in range(self.n_agents - len(self.schedule.agents)):
-                self.schedule.add(
-                    FiniteLifeAgent(self.next_id(), self.agent_lifetime, self)
-                )
+                self.schedule.add(FiniteLifeAgent(self.agent_lifetime, self))
 
     def run_model(self, step_count=100):
         for _ in range(step_count):
@@ -54,8 +50,8 @@ class FiniteLifeAgent(Agent):
     Also has a 10% chance of dying in each tick.
     """
 
-    def __init__(self, unique_id, lifetime, model):
-        super().__init__(unique_id, model)
+    def __init__(self, lifetime, model):
+        super().__init__(model)
         self.remaining_life = lifetime
         self.steps = 0
         self.model = model
diff --git a/tests/test_model.py b/tests/test_model.py
index c92fcb2e..016185e6 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -6,9 +6,6 @@ def test_model_set_up():
     model = Model()
     assert model.running is True
     assert model.schedule is None
-    assert model.current_id == 0
-    assert model.current_id + 1 == model.next_id()
-    assert model.current_id == 1
     assert model.steps == 0
     model.step()
     assert model.steps == 1
@@ -50,7 +47,7 @@ def test_agent_types():
         pass
 
     model = Model()
-    test_agent = TestAgent(model.next_id(), model)
+    test_agent = TestAgent(model)
     assert test_agent in model.agents
     assert type(test_agent) in model.agent_types
 
@@ -63,8 +60,8 @@ def test_agents_by_type():
         pass
 
     model = Model()
-    wolf = Wolf(1, model)
-    sheep = Sheep(2, model)
+    wolf = Wolf(model)
+    sheep = Sheep(model)
 
     assert model.agents_by_type[Wolf] == AgentSet([wolf], model)
     assert model.agents_by_type[Sheep] == AgentSet([sheep], model)
diff --git a/tests/test_time.py b/tests/test_time.py
index 4203ca5d..13d71d22 100644
--- a/tests/test_time.py
+++ b/tests/test_time.py
@@ -25,8 +25,8 @@ class MockAgent(Agent):
     Minimalistic agent for testing purposes.
     """
 
-    def __init__(self, unique_id, model):
-        super().__init__(unique_id, model)
+    def __init__(self, model):
+        super().__init__(model)
         self.steps = 0
         self.advances = 0
 
@@ -38,10 +38,10 @@ class MockAgent(Agent):
     def stage_one(self):
         if self.model.enable_kill_other_agent:
             self.kill_other_agent()
-        self.model.log.append(self.unique_id + "_1")
+        self.model.log.append(f"{self.unique_id}_1")
 
     def stage_two(self):
-        self.model.log.append(self.unique_id + "_2")
+        self.model.log.append(f"{self.unique_id}_2")
 
     def advance(self):
         self.advances += 1
@@ -89,8 +89,8 @@ class MockModel(Model):
             self.schedule = BaseScheduler(self)
 
         # Make agents
-        for name in ["A", "B"]:
-            agent = MockAgent(name, self)
+        for _ in range(2):
+            agent = MockAgent(self)
             self.schedule.add(agent)
 
     def step(self):
@@ -105,7 +105,7 @@ class TestStagedActivation(TestCase):
     Test the staged activation.
     """
 
-    expected_output = ["A_1", "B_1", "model_stage", "A_2", "B_2"]
+    expected_output = ["1_1", "1_1", "model_stage", "1_2", "1_2"]
 
     def test_no_shuffle(self):
         """
@@ -168,7 +168,7 @@ class TestRandomActivation(TestCase):
 
     def test_init(self):
         model = Model()
-        agents = [MockAgent(model.next_id(), model) for _ in range(10)]
+        agents = [MockAgent(model) for _ in range(10)]
 
         scheduler = RandomActivation(model, agents)
         assert all(agent in scheduler.agents for agent in agents)
@@ -227,7 +227,7 @@ class TestRandomActivation(TestCase):
         model = MockModel(activation=RANDOM)
         # Create 10 agents
         for _ in range(10):
-            model.schedule.add(MockAgent(model.next_id(), model))
+            model.schedule.add(MockAgent(model))
         # Run 3 steps
         for _ in range(3):
             model.step()
@@ -273,8 +273,8 @@ class TestRandomActivationByType(TestCase):
 
     def test_init(self):
         model = Model()
-        agents = [MockAgent(model.next_id(), model) for _ in range(10)]
-        agents += [Agent(model.next_id(), model) for _ in range(10)]
+        agents = [MockAgent(model) for _ in range(10)]
+        agents += [Agent(model) for _ in range(10)]
 
         scheduler = RandomActivationByType(model, agents)
         assert all(agent in scheduler.agents for agent in agents)