Skip to content

Commit 70fbaf5

Browse files
committed
Merge remote-tracking branch 'upstream/main'
2 parents 6e4c72e + 3cf1b76 commit 70fbaf5

File tree

5 files changed

+100
-31
lines changed

5 files changed

+100
-31
lines changed

mesa/agent.py

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
import contextlib
1212
import copy
1313
import operator
14-
import warnings
1514
import weakref
16-
from collections import defaultdict
1715
from collections.abc import Callable, Iterable, Iterator, MutableSet, Sequence
1816
from random import Random
1917

@@ -52,17 +50,11 @@ def __init__(self, unique_id: int, model: Model) -> None:
5250
# register agent
5351
try:
5452
self.model.agents_[type(self)][self] = None
55-
except AttributeError:
53+
except AttributeError as err:
5654
# model super has not been called
57-
self.model.agents_ = defaultdict(dict)
58-
self.model.agents_[type(self)][self] = None
59-
self.model.agentset_experimental_warning_given = False
60-
61-
warnings.warn(
62-
"The Mesa Model class was not initialized. In the future, you need to explicitly initialize the Model by calling super().__init__() on initialization.",
63-
FutureWarning,
64-
stacklevel=2,
65-
)
55+
raise RuntimeError(
56+
"The Mesa Model class was not initialized. You must explicitly initialize the Model by calling super().__init__() on initialization."
57+
) from err
6658

6759
def remove(self) -> None:
6860
"""Remove and delete the agent from the model."""
@@ -100,8 +92,6 @@ class AgentSet(MutableSet, Sequence):
10092
which means that agents not referenced elsewhere in the program may be automatically removed from the AgentSet.
10193
"""
10294

103-
agentset_experimental_warning_given = False
104-
10595
def __init__(self, agents: Iterable[Agent], model: Model):
10696
"""
10797
Initializes the AgentSet with a collection of agents and a reference to the model.
@@ -227,26 +217,37 @@ def _update(self, agents: Iterable[Agent]):
227217
return self
228218

229219
def do(
230-
self, method_name: str, *args, return_results: bool = False, **kwargs
220+
self, method: str | Callable, *args, return_results: bool = False, **kwargs
231221
) -> AgentSet | list[Any]:
232222
"""
233-
Invoke a method on each agent in the AgentSet.
223+
Invoke a method or function on each agent in the AgentSet.
234224
235225
Args:
236-
method_name (str): The name of the method to call on each agent.
226+
method (str, callable): the callable to do on each agents
227+
228+
* in case of str, the name of the method to call on each agent.
229+
* in case of callable, the function to be called with each agent as first argument
230+
237231
return_results (bool, optional): If True, returns the results of the method calls; otherwise, returns the AgentSet itself. Defaults to False, so you can chain method calls.
238-
*args: Variable length argument list passed to the method being called.
239-
**kwargs: Arbitrary keyword arguments passed to the method being called.
232+
*args: Variable length argument list passed to the callable being called.
233+
**kwargs: Arbitrary keyword arguments passed to the callable being called.
240234
241235
Returns:
242-
AgentSet | list[Any]: The results of the method calls if return_results is True, otherwise the AgentSet itself.
236+
AgentSet | list[Any]: The results of the callable calls if return_results is True, otherwise the AgentSet itself.
243237
"""
244238
# we iterate over the actual weakref keys and check if weakref is alive before calling the method
245-
res = [
246-
getattr(agent, method_name)(*args, **kwargs)
247-
for agentref in self._agents.keyrefs()
248-
if (agent := agentref()) is not None
249-
]
239+
if isinstance(method, str):
240+
res = [
241+
getattr(agent, method)(*args, **kwargs)
242+
for agentref in self._agents.keyrefs()
243+
if (agent := agentref()) is not None
244+
]
245+
else:
246+
res = [
247+
method(agent, *args, **kwargs)
248+
for agentref in self._agents.keyrefs()
249+
if (agent := agentref()) is not None
250+
]
250251

251252
return res if return_results else self
252253

mesa/space.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ class PropertyLayer:
586586
aggregate_property(operation): Performs an aggregate operation over all cells.
587587
"""
588588

589-
agentset_experimental_warning_given = False
589+
propertylayer_experimental_warning_given = False
590590

591591
def __init__(
592592
self, name: str, width: int, height: int, default_value, dtype=np.float64
@@ -633,14 +633,14 @@ def __init__(
633633

634634
self.data = np.full((width, height), default_value, dtype=dtype)
635635

636-
if not self.__class__.agentset_experimental_warning_given:
636+
if not self.__class__.propertylayer_experimental_warning_given:
637637
warnings.warn(
638638
"The new PropertyLayer and _PropertyGrid classes experimental. It may be changed or removed in any and all future releases, including patch releases.\n"
639639
"We would love to hear what you think about this new feature. If you have any thoughts, share them with us here: https://github.com/projectmesa/mesa/discussions/1932",
640640
FutureWarning,
641641
stacklevel=2,
642642
)
643-
self.__class__.agentset_experimental_warning_given = True
643+
self.__class__.propertylayer_experimental_warning_given = True
644644

645645
def set_cell(self, position: Coordinate, value):
646646
"""

mesa/visualization/solara_viz.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def Card(
7070
)
7171
elif space_drawer:
7272
# if specified, draw agent space with an alternate renderer
73-
space_drawer(model, agent_portrayal)
73+
space_drawer(model, agent_portrayal, dependencies=dependencies)
7474
elif "Measure" in layout_type:
7575
rv.CardTitle(children=["Measure"])
7676
measure = measures[layout_type["Measure"]]

tests/test_agent.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ def test_agentset_get_item():
176176
_ = agentset[20]
177177

178178

179-
def test_agentset_do_method():
179+
def test_agentset_do_str():
180180
model = Model()
181181
agents = [TestAgent(model.next_id(), model) for _ in range(10)]
182182
agentset = AgentSet(agents, model)
@@ -210,6 +210,72 @@ def test_agentset_do_method():
210210
assert len(agentset) == 0
211211

212212

213+
def test_agentset_do_callable():
214+
model = Model()
215+
agents = [TestAgent(model.next_id(), model) for _ in range(10)]
216+
agentset = AgentSet(agents, model)
217+
218+
# Test callable with non-existent function
219+
with pytest.raises(AttributeError):
220+
agentset.do(lambda agent: agent.non_existing_method())
221+
222+
# tests for addition and removal in do using callables
223+
# do iterates, so no error should be raised to change size while iterating
224+
# related to issue #1595
225+
226+
# setup for lambda function tests
227+
n = 10
228+
model = Model()
229+
agents = [TestAgentDo(model.next_id(), model) for _ in range(n)]
230+
agentset = AgentSet(agents, model)
231+
for agent in agents:
232+
agent.agent_set = agentset
233+
234+
# Lambda for addition
235+
agentset.do(lambda agent: agent.do_add())
236+
assert len(agentset) == 2 * n
237+
238+
# setup again for lambda function tests
239+
model = Model()
240+
agents = [TestAgentDo(model.next_id(), model) for _ in range(10)]
241+
agentset = AgentSet(agents, model)
242+
for agent in agents:
243+
agent.agent_set = agentset
244+
245+
# Lambda for removal
246+
agentset.do(lambda agent: agent.do_remove())
247+
assert len(agentset) == 0
248+
249+
# setup for actual function tests
250+
def add_function(agent):
251+
agent.do_add()
252+
253+
def remove_function(agent):
254+
agent.do_remove()
255+
256+
# setup again for actual function tests
257+
model = Model()
258+
agents = [TestAgentDo(model.next_id(), model) for _ in range(n)]
259+
agentset = AgentSet(agents, model)
260+
for agent in agents:
261+
agent.agent_set = agentset
262+
263+
# Actual function for addition
264+
agentset.do(add_function)
265+
assert len(agentset) == 2 * n
266+
267+
# setup again for actual function tests
268+
model = Model()
269+
agents = [TestAgentDo(model.next_id(), model) for _ in range(10)]
270+
agentset = AgentSet(agents, model)
271+
for agent in agents:
272+
agent.agent_set = agentset
273+
274+
# Actual function for removal
275+
agentset.do(remove_function)
276+
assert len(agentset) == 0
277+
278+
213279
def test_agentset_get_attribute():
214280
model = Model()
215281
agents = [TestAgent(model.next_id(), model) for _ in range(10)]

tests/test_solara_viz.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,9 @@ def test_call_space_drawer(mocker):
136136
space_drawer=altspace_drawer,
137137
)
138138
)
139-
altspace_drawer.assert_called_with(model, agent_portrayal)
139+
altspace_drawer.assert_called_with(
140+
model, agent_portrayal, dependencies=dependencies
141+
)
140142

141143

142144
def test_slider():

0 commit comments

Comments
 (0)