Skip to content

Commit 46a8752

Browse files
authored
track unique_id automatically (#2260)
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 Automatically assign unique_id to Agents #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.
1 parent cfa2635 commit 46a8752

File tree

17 files changed

+163
-158
lines changed

17 files changed

+163
-158
lines changed

benchmarks/BoltzmannWealth/boltzmann_wealth.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ def __init__(self, seed=None, n=100, width=10, height=10):
2727
model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"}
2828
)
2929
# Create agents
30-
for i in range(self.num_agents):
31-
a = MoneyAgent(i, self)
30+
for _ in range(self.num_agents):
31+
a = MoneyAgent(self)
3232
# Add the agent to a random grid cell
3333
x = self.random.randrange(self.grid.width)
3434
y = self.random.randrange(self.grid.height)
@@ -50,8 +50,8 @@ def run_model(self, n):
5050
class MoneyAgent(mesa.Agent):
5151
"""An agent with fixed initial wealth."""
5252

53-
def __init__(self, unique_id, model):
54-
super().__init__(unique_id, model)
53+
def __init__(self, model):
54+
super().__init__(model)
5555
self.wealth = 1
5656

5757
def move(self):

benchmarks/Flocking/flocking.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ class Boid(mesa.Agent):
2727

2828
def __init__(
2929
self,
30-
unique_id,
3130
model,
3231
speed,
3332
direction,
@@ -41,7 +40,6 @@ def __init__(
4140
Create a new Boid flocker agent.
4241
4342
Args:
44-
unique_id: Unique agent identifier.
4543
speed: Distance to move per step.
4644
direction: numpy vector for the Boid's direction of movement.
4745
vision: Radius to look around for nearby Boids.
@@ -51,7 +49,7 @@ def __init__(
5149
match: the relative importance of matching neighbors' directions
5250
5351
"""
54-
super().__init__(unique_id, model)
52+
super().__init__(model)
5553
self.speed = speed
5654
self.direction = direction
5755
self.vision = vision
@@ -131,13 +129,12 @@ def __init__(
131129
"match": match,
132130
}
133131

134-
for i in range(self.population):
132+
for _ in range(self.population):
135133
x = self.random.random() * self.space.x_max
136134
y = self.random.random() * self.space.y_max
137135
pos = np.array((x, y))
138136
direction = np.random.random(2) * 2 - 1
139137
boid = Boid(
140-
unique_id=i,
141138
model=self,
142139
speed=speed,
143140
direction=direction,

benchmarks/Schelling/schelling.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,14 @@ class SchellingAgent(CellAgent):
88
Schelling segregation agent
99
"""
1010

11-
def __init__(self, unique_id, model, agent_type, radius, homophily):
11+
def __init__(self, model, agent_type, radius, homophily):
1212
"""
1313
Create a new Schelling agent.
1414
Args:
15-
unique_id: Unique identifier for the agent.
1615
x, y: Agent initial location.
1716
agent_type: Indicator for the agent's type (minority=1, majority=0)
1817
"""
19-
super().__init__(unique_id, model)
18+
super().__init__(model)
2019
self.type = agent_type
2120
self.radius = radius
2221
self.homophily = homophily
@@ -81,9 +80,7 @@ def __init__(
8180
for cell in self.grid:
8281
if self.random.random() < density:
8382
agent_type = 1 if self.random.random() < self.minority_pc else 0
84-
agent = SchellingAgent(
85-
self.next_id(), self, agent_type, radius, homophily
86-
)
83+
agent = SchellingAgent(self, agent_type, radius, homophily)
8784
agent.move_to(cell)
8885
self.schedule.add(agent)
8986

benchmarks/WolfSheep/wolf_sheep.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717

1818

1919
class Animal(CellAgent):
20-
def __init__(self, unique_id, model, energy, p_reproduce, energy_from_food):
21-
super().__init__(unique_id, model)
20+
def __init__(self, model, energy, p_reproduce, energy_from_food):
21+
super().__init__(model)
2222
self.energy = energy
2323
self.p_reproduce = p_reproduce
2424
self.energy_from_food = energy_from_food
@@ -29,7 +29,6 @@ def random_move(self):
2929
def spawn_offspring(self):
3030
self.energy /= 2
3131
offspring = self.__class__(
32-
self.model.next_id(),
3332
self.model,
3433
self.energy,
3534
self.p_reproduce,
@@ -107,7 +106,7 @@ def fully_grown(self, value: bool) -> None:
107106
function_args=[self, "fully_grown", True],
108107
)
109108

110-
def __init__(self, unique_id, model, fully_grown, countdown, grass_regrowth_time):
109+
def __init__(self, model, fully_grown, countdown, grass_regrowth_time):
111110
"""
112111
TODO:: fully grown can just be an int --> so one less param (i.e. countdown)
113112
@@ -119,7 +118,7 @@ def __init__(self, unique_id, model, fully_grown, countdown, grass_regrowth_time
119118
grass_regrowth_time : time to fully regrow grass
120119
countdown : Time for the patch of grass to be fully regrown if fully grown is False
121120
"""
122-
super().__init__(unique_id, model)
121+
super().__init__(model)
123122
self._fully_grown = fully_grown
124123
self.grass_regrowth_time = grass_regrowth_time
125124

@@ -189,7 +188,6 @@ def __init__(
189188
)
190189
energy = self.random.randrange(2 * sheep_gain_from_food)
191190
sheep = Sheep(
192-
self.next_id(),
193191
self,
194192
energy,
195193
sheep_reproduce,
@@ -205,7 +203,6 @@ def __init__(
205203
)
206204
energy = self.random.randrange(2 * wolf_gain_from_food)
207205
wolf = Wolf(
208-
self.next_id(),
209206
self,
210207
energy,
211208
wolf_reproduce,
@@ -221,9 +218,7 @@ def __init__(
221218
countdown = grass_regrowth_time
222219
else:
223220
countdown = self.random.randrange(grass_regrowth_time)
224-
patch = GrassPatch(
225-
self.next_id(), self, fully_grown, countdown, grass_regrowth_time
226-
)
221+
patch = GrassPatch(self, fully_grown, countdown, grass_regrowth_time)
227222
patch.move_to(cell)
228223

229224
def step(self):

docs/tutorials/MoneyModel.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ def compute_gini(model):
1515
class MoneyAgent(mesa.Agent):
1616
"""An agent with fixed initial wealth."""
1717

18-
def __init__(self, unique_id, model):
19-
super().__init__(unique_id, model)
18+
def __init__(self, model):
19+
super().__init__(model)
2020
self.wealth = 1
2121

2222
def move(self):

mesa/agent.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
import contextlib
1212
import copy
13+
import functools
14+
import itertools
1315
import operator
1416
import warnings
1517
import weakref
@@ -32,21 +34,49 @@ class Agent:
3234
Base class for a model agent in Mesa.
3335
3436
Attributes:
35-
unique_id (int): A unique identifier for this agent.
3637
model (Model): A reference to the model instance.
37-
self.pos: Position | None = None
38+
unique_id (int): A unique identifier for this agent.
39+
pos (Position): A reference to the position where this agent is located.
40+
41+
Notes:
42+
unique_id is unique relative to a model instance and starts from 1
43+
3844
"""
3945

40-
def __init__(self, unique_id: int, model: Model) -> None:
46+
# this is a class level attribute
47+
# it is a dictionary, indexed by model instance
48+
# so, unique_id is unique relative to a model, and counting starts from 1
49+
_ids = defaultdict(functools.partial(itertools.count, 1))
50+
51+
def __init__(self, *args, **kwargs) -> None:
4152
"""
4253
Create a new agent.
4354
4455
Args:
45-
unique_id (int): A unique identifier for this agent.
4656
model (Model): The model instance in which the agent exists.
4757
"""
48-
self.unique_id = unique_id
49-
self.model = model
58+
# TODO: Cleanup in future Mesa version (3.1+)
59+
match args:
60+
# Case 1: Only the model is provided. The new correct behavior.
61+
case [model]:
62+
self.model = model
63+
self.unique_id = next(self._ids[model])
64+
# Case 2: Both unique_id and model are provided, deprecated
65+
case [_, model]:
66+
warnings.warn(
67+
"unique ids are assigned automatically to Agents in Mesa 3. The use of custom unique_id is "
68+
"deprecated. Only input a model when calling `super()__init__(model)`. The unique_id inputted is not used.",
69+
DeprecationWarning,
70+
stacklevel=2,
71+
)
72+
self.model = model
73+
self.unique_id = next(self._ids[model])
74+
# Case 3: Anything else, raise an error
75+
case _:
76+
raise ValueError(
77+
"Invalid arguments provided to initialize the Agent. Only input a model: `super()__init__(model)`."
78+
)
79+
5080
self.pos: Position | None = None
5181

5282
self.model.register_agent(self)

mesa/experimental/cell_space/cell_agent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ class CellAgent(Agent):
1919
cell: (Cell | None): the cell which the agent occupies
2020
"""
2121

22-
def __init__(self, unique_id: int, model: Model) -> None:
22+
def __init__(self, model: Model) -> None:
2323
"""
2424
Create a new agent.
2525
2626
Args:
2727
unique_id (int): A unique identifier for this agent.
2828
model (Model): The model instance in which the agent exists.
2929
"""
30-
super().__init__(unique_id, model)
30+
super().__init__(model)
3131
self.cell: Cell | None = None
3232

3333
def move_to(self, cell) -> None:

mesa/experimental/devs/examples/epstein_civil_violence.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88

99
class EpsteinAgent(Agent):
10-
def __init__(self, unique_id, model, vision, movement):
11-
super().__init__(unique_id, model)
10+
def __init__(self, model, vision, movement):
11+
super().__init__(model)
1212
self.vision = vision
1313
self.movement = movement
1414

@@ -46,7 +46,6 @@ class Citizen(EpsteinAgent):
4646

4747
def __init__(
4848
self,
49-
unique_id,
5049
model,
5150
vision,
5251
movement,
@@ -59,7 +58,6 @@ def __init__(
5958
"""
6059
Create a new Citizen.
6160
Args:
62-
unique_id: unique int
6361
model : model instance
6462
hardship: Agent's 'perceived hardship (i.e., physical or economic
6563
privation).' Exogenous, drawn from U(0,1).
@@ -71,7 +69,7 @@ def __init__(
7169
vision: number of cells in each direction (N, S, E and W) that
7270
agent can inspect. Exogenous.
7371
"""
74-
super().__init__(unique_id, model, vision, movement)
72+
super().__init__(model, vision, movement)
7573
self.hardship = hardship
7674
self.regime_legitimacy = regime_legitimacy
7775
self.risk_aversion = risk_aversion
@@ -144,8 +142,8 @@ class Cop(EpsteinAgent):
144142
able to inspect
145143
"""
146144

147-
def __init__(self, unique_id, model, vision, movement, max_jail_term):
148-
super().__init__(unique_id, model, vision, movement)
145+
def __init__(self, model, vision, movement, max_jail_term):
146+
super().__init__(model, vision, movement)
149147
self.max_jail_term = max_jail_term
150148

151149
def step(self):
@@ -236,15 +234,13 @@ def __init__(
236234
for _, pos in self.grid.coord_iter():
237235
if self.random.random() < self.cop_density:
238236
agent = Cop(
239-
self.next_id(),
240237
self,
241238
cop_vision,
242239
movement,
243240
max_jail_term,
244241
)
245242
elif self.random.random() < (self.cop_density + self.citizen_density):
246243
agent = Citizen(
247-
self.next_id(),
248244
self,
249245
citizen_vision,
250246
movement,
@@ -270,4 +266,4 @@ def step(self):
270266

271267
simulator.setup(model)
272268

273-
simulator.run(time_delta=100)
269+
simulator.run_for(time_delta=100)

mesa/experimental/devs/examples/wolf_sheep.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414

1515

1616
class Animal(mesa.Agent):
17-
def __init__(self, unique_id, model, moore, energy, p_reproduce, energy_from_food):
18-
super().__init__(unique_id, model)
17+
def __init__(self, model, moore, energy, p_reproduce, energy_from_food):
18+
super().__init__(model)
1919
self.energy = energy
2020
self.p_reproduce = p_reproduce
2121
self.energy_from_food = energy_from_food
@@ -30,7 +30,6 @@ def random_move(self):
3030
def spawn_offspring(self):
3131
self.energy /= 2
3232
offspring = self.__class__(
33-
self.model.next_id(),
3433
self.model,
3534
self.moore,
3635
self.energy,
@@ -109,15 +108,15 @@ def fully_grown(self, value: bool):
109108
function_args=[self, "fully_grown", True],
110109
)
111110

112-
def __init__(self, unique_id, model, fully_grown, countdown, grass_regrowth_time):
111+
def __init__(self, model, fully_grown, countdown, grass_regrowth_time):
113112
"""
114113
Creates a new patch of grass
115114
116115
Args:
117116
grown: (boolean) Whether the patch of grass is fully grown or not
118117
countdown: Time for the patch of grass to be fully grown again
119118
"""
120-
super().__init__(unique_id, model)
119+
super().__init__(model)
121120
self._fully_grown = fully_grown
122121
self.grass_regrowth_time = grass_regrowth_time
123122

@@ -191,7 +190,6 @@ def __init__(
191190
)
192191
energy = self.random.randrange(2 * sheep_gain_from_food)
193192
sheep = Sheep(
194-
self.next_id(),
195193
self,
196194
moore,
197195
energy,
@@ -208,7 +206,6 @@ def __init__(
208206
)
209207
energy = self.random.randrange(2 * wolf_gain_from_food)
210208
wolf = Wolf(
211-
self.next_id(),
212209
self,
213210
moore,
214211
energy,
@@ -225,9 +222,7 @@ def __init__(
225222
countdown = grass_regrowth_time
226223
else:
227224
countdown = self.random.randrange(grass_regrowth_time)
228-
patch = GrassPatch(
229-
self.next_id(), self, fully_grown, countdown, grass_regrowth_time
230-
)
225+
patch = GrassPatch(self, fully_grown, countdown, grass_regrowth_time)
231226
self.grid.place_agent(patch, pos)
232227

233228
def step(self):

0 commit comments

Comments
 (0)