Mesa 3.0.0a1__py3-none-any.whl → 3.0.0a2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of Mesa might be problematic. Click here for more details.

mesa/__init__.py CHANGED
@@ -24,7 +24,7 @@ __all__ = [
24
24
  ]
25
25
 
26
26
  __title__ = "mesa"
27
- __version__ = "3.0.0a1"
27
+ __version__ = "3.0.0a2"
28
28
  __license__ = "Apache 2.0"
29
29
  _this_year = datetime.datetime.now(tz=datetime.timezone.utc).date().year
30
30
  __copyright__ = f"Copyright {_this_year} Project Mesa Team"
mesa/agent.py CHANGED
@@ -11,9 +11,7 @@ from __future__ import annotations
11
11
  import contextlib
12
12
  import copy
13
13
  import operator
14
- import warnings
15
14
  import weakref
16
- from collections import defaultdict
17
15
  from collections.abc import Callable, Iterable, Iterator, MutableSet, Sequence
18
16
  from random import Random
19
17
 
@@ -52,17 +50,11 @@ class Agent:
52
50
  # register agent
53
51
  try:
54
52
  self.model.agents_[type(self)][self] = None
55
- except AttributeError:
53
+ except AttributeError as err:
56
54
  # 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
66
58
 
67
59
  def remove(self) -> None:
68
60
  """Remove and delete the agent from the model."""
@@ -100,8 +92,6 @@ class AgentSet(MutableSet, Sequence):
100
92
  which means that agents not referenced elsewhere in the program may be automatically removed from the AgentSet.
101
93
  """
102
94
 
103
- agentset_experimental_warning_given = False
104
-
105
95
  def __init__(self, agents: Iterable[Agent], model: Model):
106
96
  """
107
97
  Initializes the AgentSet with a collection of agents and a reference to the model.
@@ -227,26 +217,37 @@ class AgentSet(MutableSet, Sequence):
227
217
  return self
228
218
 
229
219
  def do(
230
- self, method_name: str, *args, return_results: bool = False, **kwargs
220
+ self, method: str | Callable, *args, return_results: bool = False, **kwargs
231
221
  ) -> AgentSet | list[Any]:
232
222
  """
233
- Invoke a method on each agent in the AgentSet.
223
+ Invoke a method or function on each agent in the AgentSet.
234
224
 
235
225
  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
+
237
231
  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.
240
234
 
241
235
  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.
243
237
  """
244
238
  # 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
+ ]
250
251
 
251
252
  return res if return_results else self
252
253
 
@@ -155,6 +155,17 @@ class EventList:
155
155
  def __len__(self) -> int:
156
156
  return len(self._events)
157
157
 
158
+ def __repr__(self) -> str:
159
+ """Return a string representation of the event list"""
160
+ events_str = ", ".join(
161
+ [
162
+ f"Event(time={e.time}, priority={e.priority}, id={e.unique_id})"
163
+ for e in self._events
164
+ if not e.CANCELED
165
+ ]
166
+ )
167
+ return f"EventList([{events_str}])"
168
+
158
169
  def remove(self, event: SimulationEvent) -> None:
159
170
  """remove an event from the event list"""
160
171
  # we cannot simply remove items from _eventlist because this breaks
mesa/model.py CHANGED
@@ -10,7 +10,6 @@ from __future__ import annotations
10
10
 
11
11
  import itertools
12
12
  import random
13
- import warnings
14
13
  from collections import defaultdict
15
14
 
16
15
  # mypy
@@ -89,12 +88,10 @@ class Model:
89
88
 
90
89
  @agents.setter
91
90
  def agents(self, agents: Any) -> None:
92
- warnings.warn(
93
- "You are trying to set model.agents. In a next release, this attribute is used "
94
- "by MESA itself so you cannot use it directly anymore."
95
- "Please adjust your code to use a different attribute name for custom agent storage",
96
- UserWarning,
97
- stacklevel=2,
91
+ raise AttributeError(
92
+ "You are trying to set model.agents. In Mesa 3.0 and higher, this attribute will be "
93
+ "used by Mesa itself, so you cannot use it directly anymore."
94
+ "Please adjust your code to use a different attribute name for custom agent storage."
98
95
  )
99
96
 
100
97
  self._agents = agents
mesa/space.py CHANGED
@@ -586,7 +586,7 @@ class PropertyLayer:
586
586
  aggregate_property(operation): Performs an aggregate operation over all cells.
587
587
  """
588
588
 
589
- agentset_experimental_warning_given = False
589
+ propertylayer_experimental_warning_given = False
590
590
 
591
591
  def __init__(
592
592
  self, name: str, width: int, height: int, default_value, dtype=np.float64
@@ -633,14 +633,14 @@ class PropertyLayer:
633
633
 
634
634
  self.data = np.full((width, height), default_value, dtype=dtype)
635
635
 
636
- if not self.__class__.agentset_experimental_warning_given:
636
+ if not self.__class__.propertylayer_experimental_warning_given:
637
637
  warnings.warn(
638
638
  "The new PropertyLayer and _PropertyGrid classes experimental. It may be changed or removed in any and all future releases, including patch releases.\n"
639
639
  "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",
640
640
  FutureWarning,
641
641
  stacklevel=2,
642
642
  )
643
- self.__class__.agentset_experimental_warning_given = True
643
+ self.__class__.propertylayer_experimental_warning_given = True
644
644
 
645
645
  def set_cell(self, position: Coordinate, value):
646
646
  """
@@ -1,3 +1,5 @@
1
+ from collections import defaultdict
2
+
1
3
  import networkx as nx
2
4
  import solara
3
5
  from matplotlib.figure import Figure
@@ -23,12 +25,44 @@ def SpaceMatplotlib(model, agent_portrayal, dependencies: list[any] | None = Non
23
25
  solara.FigureMatplotlib(space_fig, format="png", dependencies=dependencies)
24
26
 
25
27
 
28
+ # matplotlib scatter does not allow for multiple shapes in one call
29
+ def _split_and_scatter(portray_data, space_ax):
30
+ grouped_data = defaultdict(lambda: {"x": [], "y": [], "s": [], "c": []})
31
+
32
+ # Extract data from the dictionary
33
+ x = portray_data["x"]
34
+ y = portray_data["y"]
35
+ s = portray_data["s"]
36
+ c = portray_data["c"]
37
+ m = portray_data["m"]
38
+
39
+ if not (len(x) == len(y) == len(s) == len(c) == len(m)):
40
+ raise ValueError(
41
+ "Length mismatch in portrayal data lists: "
42
+ f"x: {len(x)}, y: {len(y)}, size: {len(s)}, "
43
+ f"color: {len(c)}, marker: {len(m)}"
44
+ )
45
+
46
+ # Group the data by marker
47
+ for i in range(len(x)):
48
+ marker = m[i]
49
+ grouped_data[marker]["x"].append(x[i])
50
+ grouped_data[marker]["y"].append(y[i])
51
+ grouped_data[marker]["s"].append(s[i])
52
+ grouped_data[marker]["c"].append(c[i])
53
+
54
+ # Plot each group with the same marker
55
+ for marker, data in grouped_data.items():
56
+ space_ax.scatter(data["x"], data["y"], s=data["s"], c=data["c"], marker=marker)
57
+
58
+
26
59
  def _draw_grid(space, space_ax, agent_portrayal):
27
60
  def portray(g):
28
61
  x = []
29
62
  y = []
30
63
  s = [] # size
31
64
  c = [] # color
65
+ m = [] # shape
32
66
  for i in range(g.width):
33
67
  for j in range(g.height):
34
68
  content = g._grid[i][j]
@@ -41,23 +75,23 @@ def _draw_grid(space, space_ax, agent_portrayal):
41
75
  data = agent_portrayal(agent)
42
76
  x.append(i)
43
77
  y.append(j)
44
- if "size" in data:
45
- s.append(data["size"])
46
- if "color" in data:
47
- c.append(data["color"])
48
- out = {"x": x, "y": y}
49
- # This is the default value for the marker size, which auto-scales
50
- # according to the grid area.
51
- out["s"] = (180 / max(g.width, g.height)) ** 2
52
- if len(s) > 0:
53
- out["s"] = s
54
- if len(c) > 0:
55
- out["c"] = c
78
+
79
+ # This is the default value for the marker size, which auto-scales
80
+ # according to the grid area.
81
+ default_size = (180 / max(g.width, g.height)) ** 2
82
+ # establishing a default prevents misalignment if some agents are not given size, color, etc.
83
+ size = data.get("size", default_size)
84
+ s.append(size)
85
+ color = data.get("color", "tab:blue")
86
+ c.append(color)
87
+ mark = data.get("shape", "o")
88
+ m.append(mark)
89
+ out = {"x": x, "y": y, "s": s, "c": c, "m": m}
56
90
  return out
57
91
 
58
92
  space_ax.set_xlim(-1, space.width)
59
93
  space_ax.set_ylim(-1, space.height)
60
- space_ax.scatter(**portray(space))
94
+ _split_and_scatter(portray(space), space_ax)
61
95
 
62
96
 
63
97
  def _draw_network_grid(space, space_ax, agent_portrayal):
@@ -77,20 +111,23 @@ def _draw_continuous_space(space, space_ax, agent_portrayal):
77
111
  y = []
78
112
  s = [] # size
79
113
  c = [] # color
114
+ m = [] # shape
80
115
  for agent in space._agent_to_index:
81
116
  data = agent_portrayal(agent)
82
117
  _x, _y = agent.pos
83
118
  x.append(_x)
84
119
  y.append(_y)
85
- if "size" in data:
86
- s.append(data["size"])
87
- if "color" in data:
88
- c.append(data["color"])
89
- out = {"x": x, "y": y}
90
- if len(s) > 0:
91
- out["s"] = s
92
- if len(c) > 0:
93
- out["c"] = c
120
+
121
+ # This is matplotlib's default marker size
122
+ default_size = 20
123
+ # establishing a default prevents misalignment if some agents are not given size, color, etc.
124
+ size = data.get("size", default_size)
125
+ s.append(size)
126
+ color = data.get("color", "tab:blue")
127
+ c.append(color)
128
+ mark = data.get("shape", "o")
129
+ m.append(mark)
130
+ out = {"x": x, "y": y, "s": s, "c": c, "m": m}
94
131
  return out
95
132
 
96
133
  # Determine border style based on space.torus
@@ -110,7 +147,7 @@ def _draw_continuous_space(space, space_ax, agent_portrayal):
110
147
  space_ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding)
111
148
 
112
149
  # Portray and scatter the agents in the space
113
- space_ax.scatter(**portray(space))
150
+ _split_and_scatter(portray(space), space_ax)
114
151
 
115
152
 
116
153
  @solara.component
@@ -70,7 +70,7 @@ def Card(
70
70
  )
71
71
  elif space_drawer:
72
72
  # if specified, draw agent space with an alternate renderer
73
- space_drawer(model, agent_portrayal)
73
+ space_drawer(model, agent_portrayal, dependencies=dependencies)
74
74
  elif "Measure" in layout_type:
75
75
  rv.CardTitle(children=["Measure"])
76
76
  measure = measures[layout_type["Measure"]]
@@ -103,7 +103,8 @@ def SolaraViz(
103
103
  model_params: Parameters for initializing the model
104
104
  measures: List of callables or data attributes to plot
105
105
  name: Name for display
106
- agent_portrayal: Options for rendering agents (dictionary)
106
+ agent_portrayal: Options for rendering agents (dictionary);
107
+ Default drawer supports custom `"size"`, `"color"`, and `"shape"`.
107
108
  space_drawer: Method to render the agent space for
108
109
  the model; default implementation is the `SpaceMatplotlib` component;
109
110
  simulations with no space to visualize should
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: Mesa
3
- Version: 3.0.0a1
3
+ Version: 3.0.0a2
4
4
  Summary: Agent-based modeling (ABM) in Python
5
5
  Project-URL: homepage, https://github.com/projectmesa/mesa
6
6
  Project-URL: repository, https://github.com/projectmesa/mesa
@@ -30,7 +30,6 @@ Requires-Dist: pandas
30
30
  Requires-Dist: solara
31
31
  Requires-Dist: tqdm
32
32
  Provides-Extra: dev
33
- Requires-Dist: coverage; extra == 'dev'
34
33
  Requires-Dist: pytest-cov; extra == 'dev'
35
34
  Requires-Dist: pytest-mock; extra == 'dev'
36
35
  Requires-Dist: pytest>=4.6; extra == 'dev'
@@ -75,13 +74,19 @@ can be displayed in browser windows or Jupyter.*
75
74
 
76
75
  ## Using Mesa
77
76
 
78
- Getting started quickly:
77
+ To install our latest stable release (2.3.x), run:
79
78
 
80
79
  ``` bash
81
- pip install mesa
80
+ pip install -U mesa
82
81
  ```
83
82
 
84
- You can also use `pip` to install the github version:
83
+ To install our latest pre-release (3.0.0 alpha), run:
84
+
85
+ ``` bash
86
+ pip install -U --pre mesa
87
+ ```
88
+
89
+ You can also use `pip` to install the latest GitHub version:
85
90
 
86
91
  ``` bash
87
92
  pip install -U -e git+https://github.com/projectmesa/mesa@main#egg=mesa
@@ -1,10 +1,10 @@
1
- mesa/__init__.py,sha256=XNwJOFa_LglQJTMbYPWHXvartntKeOrSn9DGSbXj1rc,618
2
- mesa/agent.py,sha256=fx_h8RnX5DJCmfJtloIb_fprXXp8bFzC3_RnLOLlOvY,12902
1
+ mesa/__init__.py,sha256=MvmhX7S8OWq9FLM52bF7lnSUTTNZegQPsSheI37L2og,618
2
+ mesa/agent.py,sha256=xqpjpMHii82nts-gO5DPpcUjujHQoaJB2fKi0VrrWHs,13067
3
3
  mesa/batchrunner.py,sha256=92MabDDR38XGTZw_IB7nNDNH0PX7zL_jGyZJ2grisaY,6023
4
4
  mesa/datacollection.py,sha256=CQ2QsW-mkEVbDVTsOkLy8NAQEKeoILdLB0zWS2sxnyk,11444
5
5
  mesa/main.py,sha256=7MovfNz88VWNnfXP0kcERB6C3GfkVOh0hb0o32hM9LU,1602
6
- mesa/model.py,sha256=GqayRWhohSS96kMwHCNGI7XvEkwI8GHS2SRL6SZ9N5E,5810
7
- mesa/space.py,sha256=9eDEUQBcck8QYWvRn3fDw2zS2bO1Yjc7VjvvrMikzPE,62447
6
+ mesa/model.py,sha256=Jqj6ob-zuTwAWwYAkbEsoC-bTA7ptoAD9qDvPfIX-80,5761
7
+ mesa/space.py,sha256=zC96qoNjhLmBXY2ZaEQmF7w30WZAtPpEY4mwcet-Dio,62462
8
8
  mesa/time.py,sha256=9gNoyUqYkt_gUPFBMhm38pK87mcntwAZ1lJzxqW3BSA,15211
9
9
  mesa/cookiecutter-mesa/cookiecutter.json,sha256=tBSWli39fOWUXGfiDCTKd92M7uKaBIswXbkOdbUufYY,337
10
10
  mesa/cookiecutter-mesa/hooks/post_gen_project.py,sha256=8JoXZKIioRYEWJURC0udj8WS3rg0c4So62sOZSGbrMY,294
@@ -22,17 +22,17 @@ mesa/experimental/cell_space/discrete_space.py,sha256=ta__YojsrrhWL4DgMzUqZpSgbe
22
22
  mesa/experimental/cell_space/grid.py,sha256=gYDExuFBMF3OThUkhbXmolQFKBOqTukcibjfgXicP00,6948
23
23
  mesa/experimental/cell_space/network.py,sha256=mAaFHBdd4s9kxUWHbViovLW2-pU2yXH0dtY_vF8sCJg,1179
24
24
  mesa/experimental/devs/__init__.py,sha256=CWam15vCj-RD_biMyqv4sJfos1fsL823P7MDEGrbwW8,174
25
- mesa/experimental/devs/eventlist.py,sha256=AM-gpivXQ889Ewt66T_ai6Yy6ldx0G69Unu1lasSNxI,4907
25
+ mesa/experimental/devs/eventlist.py,sha256=nyUFNDWnnSPQnrMtj7Qj1PexxKyOwSJuIGBoxtSwVI0,5269
26
26
  mesa/experimental/devs/simulator.py,sha256=0SMC7daIOyL2rYfoQOOTaTOYDos0gLeBUbU1Krd42HA,9557
27
27
  mesa/experimental/devs/examples/epstein_civil_violence.py,sha256=KqH9KI-A_BYt7oWi9kaOhTzjrf2pETqzSpAQG8ewud0,9667
28
28
  mesa/experimental/devs/examples/wolf_sheep.py,sha256=h5z-eDqMpYeOjrq293N2BcQbs_LDVsgtg9vblXJM7XQ,7697
29
29
  mesa/visualization/UserParam.py,sha256=WgnY3Q0padtGqUCaezgYzd6cZ7LziuIQnGKP3DBuHZY,1641
30
30
  mesa/visualization/__init__.py,sha256=zsAzEY3-0O9CZUfiUL6p8zCR1mvvL5Sai2WzoiQ2pmY,127
31
- mesa/visualization/solara_viz.py,sha256=POus4i1k2Z8fJpEXiXQvGupRsrRLRiG5qndwkaEQ53Y,15085
31
+ mesa/visualization/solara_viz.py,sha256=GuhDFcsHp6rSjaYMM7lgB-sWJGfEqJrYnmMxncnZF8A,15192
32
32
  mesa/visualization/components/altair.py,sha256=V2CQ-Zr7PeijgWtYBNH3VklGVfrf1ee70XVh0DBBONQ,2366
33
- mesa/visualization/components/matplotlib.py,sha256=lB9QKo6i_mI2iKCksyakOStqY8I6B3sv8SXcpmPgWEc,4289
34
- mesa-3.0.0a1.dist-info/METADATA,sha256=QTL6KViiX07VnrkXi5hqG0nYYN_hyZaWo3_SckVvbIA,7771
35
- mesa-3.0.0a1.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
36
- mesa-3.0.0a1.dist-info/entry_points.txt,sha256=IOcQtetGF8l4wHpOs_hGb19Rz-FS__BMXOJR10IBPsA,39
37
- mesa-3.0.0a1.dist-info/licenses/LICENSE,sha256=OGUgret9fRrm8J3pdsPXETIjf0H8puK_Nmy970ZzT78,572
38
- mesa-3.0.0a1.dist-info/RECORD,,
33
+ mesa/visualization/components/matplotlib.py,sha256=gUTteu_VEA4J2je760aq_esYto0Uj1DZP9gjev8TlcU,5851
34
+ mesa-3.0.0a2.dist-info/METADATA,sha256=zAMPJhtCHtWiXvm6dl5iCqU048qhtZpBkGsKO6RtaEM,7862
35
+ mesa-3.0.0a2.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
36
+ mesa-3.0.0a2.dist-info/entry_points.txt,sha256=IOcQtetGF8l4wHpOs_hGb19Rz-FS__BMXOJR10IBPsA,39
37
+ mesa-3.0.0a2.dist-info/licenses/LICENSE,sha256=OGUgret9fRrm8J3pdsPXETIjf0H8puK_Nmy970ZzT78,572
38
+ mesa-3.0.0a2.dist-info/RECORD,,
File without changes