Mesa 3.1.0__py3-none-any.whl → 3.1.1__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
@@ -22,7 +22,7 @@ __all__ = [
22
22
  ]
23
23
 
24
24
  __title__ = "mesa"
25
- __version__ = "3.1.0"
25
+ __version__ = "3.1.1"
26
26
  __license__ = "Apache 2.0"
27
27
  _this_year = datetime.datetime.now(tz=datetime.UTC).date().year
28
28
  __copyright__ = f"Copyright {_this_year} Project Mesa Team"
@@ -4,7 +4,7 @@ from mesa.experimental.cell_space import CellAgent
4
4
  class PDAgent(CellAgent):
5
5
  """Agent member of the iterated, spatial prisoner's dilemma model."""
6
6
 
7
- def __init__(self, model, starting_move=None):
7
+ def __init__(self, model, starting_move=None, cell=None):
8
8
  """
9
9
  Create a new Prisoner's Dilemma agent.
10
10
 
@@ -15,6 +15,7 @@ class PDAgent(CellAgent):
15
15
  """
16
16
  super().__init__(model)
17
17
  self.score = 0
18
+ self.cell = cell
18
19
  if starting_move:
19
20
  self.move = starting_move
20
21
  else:
@@ -32,11 +32,9 @@ class PdGrid(mesa.Model):
32
32
  if payoffs is not None:
33
33
  self.payoff = payoffs
34
34
 
35
- # Create agents
36
- for x in range(width):
37
- for y in range(height):
38
- agent = PDAgent(self)
39
- agent.cell = self.grid[(x, y)]
35
+ PDAgent.create_agents(
36
+ self, len(self.grid.all_cells.cells), cell=self.grid.all_cells.cells
37
+ )
40
38
 
41
39
  self.datacollector = mesa.DataCollector(
42
40
  {
@@ -1,6 +1,6 @@
1
1
  import math
2
2
 
3
- from mesa.experimental.cell_space import CellAgent, FixedAgent
3
+ from mesa.experimental.cell_space import CellAgent
4
4
 
5
5
 
6
6
  # Helper function
@@ -18,31 +18,6 @@ def get_distance(cell_1, cell_2):
18
18
  return math.sqrt(dx**2 + dy**2)
19
19
 
20
20
 
21
- class Resource(FixedAgent):
22
- """
23
- Resource:
24
- - contains an amount of sugar and spice
25
- - grows 1 amount of sugar at each turn
26
- - grows 1 amount of spice at each turn
27
- """
28
-
29
- def __init__(self, model, max_sugar, max_spice, cell):
30
- super().__init__(model)
31
- self.sugar_amount = max_sugar
32
- self.max_sugar = max_sugar
33
- self.spice_amount = max_spice
34
- self.max_spice = max_spice
35
- self.cell = cell
36
-
37
- def step(self):
38
- """
39
- Growth function, adds one unit of sugar and spice each step up to
40
- max amount
41
- """
42
- self.sugar_amount = min([self.max_sugar, self.sugar_amount + 1])
43
- self.spice_amount = min([self.max_spice, self.spice_amount + 1])
44
-
45
-
46
21
  class Trader(CellAgent):
47
22
  """
48
23
  Trader:
@@ -70,12 +45,6 @@ class Trader(CellAgent):
70
45
  self.prices = []
71
46
  self.trade_partners = []
72
47
 
73
- def get_resource(self, cell):
74
- for agent in cell.agents:
75
- if isinstance(agent, Resource):
76
- return agent
77
- raise Exception(f"Resource agent not found in the position {cell.coordinate}")
78
-
79
48
  def get_trader(self, cell):
80
49
  """
81
50
  helper function used in self.trade_with_neighbors()
@@ -85,17 +54,6 @@ class Trader(CellAgent):
85
54
  if isinstance(agent, Trader):
86
55
  return agent
87
56
 
88
- def is_occupied_by_other(self, cell):
89
- """
90
- helper function part 1 of self.move()
91
- """
92
-
93
- if cell is self.cell:
94
- # agent's position is considered unoccupied as agent can stay there
95
- return False
96
- # get contents of each cell in neighborhood
97
- return any(isinstance(a, Trader) for a in cell.agents)
98
-
99
57
  def calculate_welfare(self, sugar, spice):
100
58
  """
101
59
  helper function
@@ -264,15 +222,15 @@ class Trader(CellAgent):
264
222
  neighboring_cells = [
265
223
  cell
266
224
  for cell in self.cell.get_neighborhood(self.vision, include_center=True)
267
- if not self.is_occupied_by_other(cell)
225
+ if cell.is_empty
268
226
  ]
269
227
 
270
228
  # 2. determine which move maximizes welfare
271
229
 
272
230
  welfares = [
273
231
  self.calculate_welfare(
274
- self.sugar + self.get_resource(cell).sugar_amount,
275
- self.spice + self.get_resource(cell).spice_amount,
232
+ self.sugar + cell.sugar,
233
+ self.spice + cell.spice,
276
234
  )
277
235
  for cell in neighboring_cells
278
236
  ]
@@ -282,6 +240,7 @@ class Trader(CellAgent):
282
240
  # find the highest welfare in welfares
283
241
  max_welfare = max(welfares)
284
242
  # get the index of max welfare cells
243
+ # fixme: rewrite using enumerate and single loop
285
244
  candidate_indices = [
286
245
  i for i in range(len(welfares)) if math.isclose(welfares[i], max_welfare)
287
246
  ]
@@ -296,19 +255,17 @@ class Trader(CellAgent):
296
255
  for cell in candidates
297
256
  if math.isclose(get_distance(self.cell, cell), min_dist, rel_tol=1e-02)
298
257
  ]
258
+
299
259
  # 4. Move Agent
300
260
  self.cell = self.random.choice(final_candidates)
301
261
 
302
262
  def eat(self):
303
- patch = self.get_resource(self.cell)
304
- if patch.sugar_amount > 0:
305
- self.sugar += patch.sugar_amount
306
- patch.sugar_amount = 0
263
+ self.sugar += self.cell.sugar
264
+ self.cell.sugar = 0
307
265
  self.sugar -= self.metabolism_sugar
308
266
 
309
- if patch.spice_amount > 0:
310
- self.spice += patch.spice_amount
311
- patch.spice_amount = 0
267
+ self.spice += self.cell.spice
268
+ self.cell.spice = 0
312
269
  self.spice -= self.metabolism_spice
313
270
 
314
271
  def maybe_die(self):
@@ -327,18 +284,8 @@ class Trader(CellAgent):
327
284
  2- trade (2 sessions)
328
285
  3- collect data
329
286
  """
330
-
331
- neighbor_agents = [
332
- self.get_trader(cell)
333
- for cell in self.cell.get_neighborhood(radius=self.vision)
334
- if self.is_occupied_by_other(cell)
335
- ]
336
-
337
- if len(neighbor_agents) == 0:
338
- return
339
-
340
- # iterate through traders in neighboring cells and trade
341
- for a in neighbor_agents:
287
+ # iterate through traders in neighboring cells and trade
288
+ for a in self.cell.get_neighborhood(radius=self.vision).agents:
342
289
  self.trade(a)
343
290
 
344
291
  return
@@ -2,7 +2,6 @@ import numpy as np
2
2
  import solara
3
3
  from matplotlib.figure import Figure
4
4
 
5
- from mesa.examples.advanced.sugarscape_g1mt.agents import Trader
6
5
  from mesa.examples.advanced.sugarscape_g1mt.model import SugarscapeG1mt
7
6
  from mesa.visualization import Slider, SolaraViz, make_plot_component
8
7
 
@@ -10,24 +9,13 @@ from mesa.visualization import Slider, SolaraViz, make_plot_component
10
9
  def SpaceDrawer(model):
11
10
  def portray(g):
12
11
  layers = {
13
- "sugar": [[np.nan for j in range(g.height)] for i in range(g.width)],
14
- "spice": [[np.nan for j in range(g.height)] for i in range(g.width)],
15
12
  "trader": {"x": [], "y": [], "c": "tab:red", "marker": "o", "s": 10},
16
13
  }
17
14
 
18
15
  for agent in g.all_cells.agents:
19
16
  i, j = agent.cell.coordinate
20
- if isinstance(agent, Trader):
21
- layers["trader"]["x"].append(i)
22
- layers["trader"]["y"].append(j)
23
- else:
24
- # Don't visualize resource with value <= 1.
25
- layers["sugar"][i][j] = (
26
- agent.sugar_amount if agent.sugar_amount > 1 else np.nan
27
- )
28
- layers["spice"][i][j] = (
29
- agent.spice_amount if agent.spice_amount > 1 else np.nan
30
- )
17
+ layers["trader"]["x"].append(i)
18
+ layers["trader"]["y"].append(j)
31
19
  return layers
32
20
 
33
21
  fig = Figure()
@@ -36,10 +24,18 @@ def SpaceDrawer(model):
36
24
  # Sugar
37
25
  # Important note: imshow by default draws from upper left. You have to
38
26
  # always explicitly specify origin="lower".
39
- im = ax.imshow(out["sugar"], cmap="spring", origin="lower")
27
+ im = ax.imshow(
28
+ np.ma.masked_where(model.grid.sugar.data <= 1, model.grid.sugar.data),
29
+ cmap="spring",
30
+ origin="lower",
31
+ )
40
32
  fig.colorbar(im, orientation="vertical")
41
33
  # Spice
42
- ax.imshow(out["spice"], cmap="winter", origin="lower")
34
+ ax.imshow(
35
+ np.ma.masked_where(model.grid.spice.data <= 1, model.grid.spice.data),
36
+ cmap="winter",
37
+ origin="lower",
38
+ )
43
39
  # Trader
44
40
  ax.scatter(**out["trader"])
45
41
  ax.set_axis_off()
@@ -75,7 +71,11 @@ model = SugarscapeG1mt()
75
71
 
76
72
  page = SolaraViz(
77
73
  model,
78
- components=[SpaceDrawer, make_plot_component(["Trader", "Price"])],
74
+ components=[
75
+ SpaceDrawer,
76
+ make_plot_component("#Traders"),
77
+ make_plot_component("Price"),
78
+ ],
79
79
  model_params=model_params,
80
80
  name="Sugarscape {G1, M, T}",
81
81
  play_interval=150,
@@ -3,8 +3,9 @@ from pathlib import Path
3
3
  import numpy as np
4
4
 
5
5
  import mesa
6
- from mesa.examples.advanced.sugarscape_g1mt.agents import Resource, Trader
6
+ from mesa.examples.advanced.sugarscape_g1mt.agents import Trader
7
7
  from mesa.experimental.cell_space import OrthogonalVonNeumannGrid
8
+ from mesa.experimental.cell_space.property_layer import PropertyLayer
8
9
 
9
10
 
10
11
  # Helper Functions
@@ -58,14 +59,8 @@ class SugarscapeG1mt(mesa.Model):
58
59
  # Initiate width and height of sugarscape
59
60
  self.width = width
60
61
  self.height = height
62
+
61
63
  # Initiate population attributes
62
- self.initial_population = initial_population
63
- self.endowment_min = endowment_min
64
- self.endowment_max = endowment_max
65
- self.metabolism_min = metabolism_min
66
- self.metabolism_max = metabolism_max
67
- self.vision_min = vision_min
68
- self.vision_max = vision_max
69
64
  self.enable_trade = enable_trade
70
65
  self.running = True
71
66
 
@@ -76,55 +71,46 @@ class SugarscapeG1mt(mesa.Model):
76
71
  # initiate datacollector
77
72
  self.datacollector = mesa.DataCollector(
78
73
  model_reporters={
79
- "Trader": lambda m: len(m.agents_by_type[Trader]),
80
- "Trade Volume": lambda m: sum(
81
- len(a.trade_partners) for a in m.agents_by_type[Trader]
82
- ),
74
+ "#Traders": lambda m: len(m.agents),
75
+ "Trade Volume": lambda m: sum(len(a.trade_partners) for a in m.agents),
83
76
  "Price": lambda m: geometric_mean(
84
- flatten([a.prices for a in m.agents_by_type[Trader]])
77
+ flatten([a.prices for a in m.agents])
85
78
  ),
86
79
  },
87
80
  agent_reporters={"Trade Network": lambda a: get_trade(a)},
88
81
  )
89
82
 
90
- # read in landscape file from supplmentary material
91
- sugar_distribution = np.genfromtxt(Path(__file__).parent / "sugar-map.txt")
92
- spice_distribution = np.flip(sugar_distribution, 1)
93
-
94
- for cell in self.grid.all_cells:
95
- max_sugar = sugar_distribution[cell.coordinate]
96
- max_spice = spice_distribution[cell.coordinate]
97
- Resource(self, max_sugar, max_spice, cell)
98
-
99
- for _ in range(self.initial_population):
100
- # get agent position
101
- x = self.random.randrange(self.width)
102
- y = self.random.randrange(self.height)
103
- # see Growing Artificial Societies p. 108 for initialization
104
- # give agents initial endowment
105
- sugar = int(self.random.uniform(self.endowment_min, self.endowment_max + 1))
106
- spice = int(self.random.uniform(self.endowment_min, self.endowment_max + 1))
107
- # give agents initial metabolism
108
- metabolism_sugar = int(
109
- self.random.uniform(self.metabolism_min, self.metabolism_max + 1)
110
- )
111
- metabolism_spice = int(
112
- self.random.uniform(self.metabolism_min, self.metabolism_max + 1)
113
- )
114
- # give agents vision
115
- vision = int(self.random.uniform(self.vision_min, self.vision_max + 1))
116
-
117
- cell = self.grid[(x, y)]
118
- # create Trader object
119
- Trader(
120
- self,
121
- cell,
122
- sugar=sugar,
123
- spice=spice,
124
- metabolism_sugar=metabolism_sugar,
125
- metabolism_spice=metabolism_spice,
126
- vision=vision,
127
- )
83
+ # read in landscape file from supplementary material
84
+ self.sugar_distribution = np.genfromtxt(Path(__file__).parent / "sugar-map.txt")
85
+ self.spice_distribution = np.flip(self.sugar_distribution, 1)
86
+
87
+ self.grid.add_property_layer(
88
+ PropertyLayer.from_data("sugar", self.sugar_distribution)
89
+ )
90
+ self.grid.add_property_layer(
91
+ PropertyLayer.from_data("spice", self.spice_distribution)
92
+ )
93
+
94
+ Trader.create_agents(
95
+ self,
96
+ initial_population,
97
+ self.random.choices(self.grid.all_cells.cells, k=initial_population),
98
+ sugar=self.rng.integers(
99
+ endowment_min, endowment_max, (initial_population,), endpoint=True
100
+ ),
101
+ spice=self.rng.integers(
102
+ endowment_min, endowment_max, (initial_population,), endpoint=True
103
+ ),
104
+ metabolism_sugar=self.rng.integers(
105
+ metabolism_min, metabolism_max, (initial_population,), endpoint=True
106
+ ),
107
+ metabolism_spice=self.rng.integers(
108
+ metabolism_min, metabolism_max, (initial_population,), endpoint=True
109
+ ),
110
+ vision=self.rng.integers(
111
+ vision_min, vision_max, (initial_population,), endpoint=True
112
+ ),
113
+ )
128
114
 
129
115
  def step(self):
130
116
  """
@@ -132,7 +118,12 @@ class SugarscapeG1mt(mesa.Model):
132
118
  and then randomly activates traders
133
119
  """
134
120
  # step Resource agents
135
- self.agents_by_type[Resource].do("step")
121
+ self.grid.sugar.data = np.minimum(
122
+ self.grid.sugar.data + 1, self.sugar_distribution
123
+ )
124
+ self.grid.spice.data = np.minimum(
125
+ self.grid.spice.data + 1, self.spice_distribution
126
+ )
136
127
 
137
128
  # step trader agents
138
129
  # to account for agent death and removal we need a separate data structure to
@@ -157,6 +148,8 @@ class SugarscapeG1mt(mesa.Model):
157
148
  agent.trade_with_neighbors()
158
149
 
159
150
  # collect model level data
151
+ # fixme we can already collect agent class data
152
+ # fixme, we don't have resource agents anymore so this can be done simpler
160
153
  self.datacollector.collect(self)
161
154
  """
162
155
  Mesa is working on updating datacollector agent reporter
@@ -4,7 +4,9 @@ from mesa.experimental.cell_space import CellAgent, FixedAgent
4
4
  class Animal(CellAgent):
5
5
  """The base animal class."""
6
6
 
7
- def __init__(self, model, energy, p_reproduce, energy_from_food, cell):
7
+ def __init__(
8
+ self, model, energy=8, p_reproduce=0.04, energy_from_food=4, cell=None
9
+ ):
8
10
  """Initialize an animal.
9
11
 
10
12
  Args:
@@ -90,22 +90,23 @@ class WolfSheep(Model):
90
90
  self.datacollector = DataCollector(model_reporters)
91
91
 
92
92
  # Create sheep:
93
- for _ in range(initial_sheep):
94
- pos = (
95
- self.random.randrange(width),
96
- self.random.randrange(height),
97
- )
98
- energy = self.random.randrange(2 * sheep_gain_from_food)
99
- Sheep(self, energy, sheep_reproduce, sheep_gain_from_food, self.grid[pos])
100
-
101
- # Create wolves
102
- for _ in range(initial_wolves):
103
- pos = (
104
- self.random.randrange(width),
105
- self.random.randrange(height),
106
- )
107
- energy = self.random.randrange(2 * wolf_gain_from_food)
108
- Wolf(self, energy, wolf_reproduce, wolf_gain_from_food, self.grid[pos])
93
+ Sheep.create_agents(
94
+ self,
95
+ initial_sheep,
96
+ energy=self.rng.random((initial_sheep,)) * 2 * sheep_gain_from_food,
97
+ p_reproduce=sheep_reproduce,
98
+ energy_from_food=sheep_gain_from_food,
99
+ cell=self.random.choices(self.grid.all_cells.cells, k=initial_sheep),
100
+ )
101
+ # Create Wolves:
102
+ Wolf.create_agents(
103
+ self,
104
+ initial_wolves,
105
+ energy=self.rng.random((initial_wolves,)) * 2 * wolf_gain_from_food,
106
+ p_reproduce=wolf_reproduce,
107
+ energy_from_food=wolf_gain_from_food,
108
+ cell=self.random.choices(self.grid.all_cells.cells, k=initial_wolves),
109
+ )
109
110
 
110
111
  # Create grass patches if enabled
111
112
  if grass:
@@ -6,7 +6,6 @@ class SchellingAgent(Agent):
6
6
 
7
7
  def __init__(self, model, agent_type: int) -> None:
8
8
  """Create a new Schelling agent.
9
-
10
9
  Args:
11
10
  model: The model instance the agent belongs to
12
11
  agent_type: Indicator for the agent's type (minority=1, majority=0)
@@ -16,15 +15,22 @@ class SchellingAgent(Agent):
16
15
 
17
16
  def step(self) -> None:
18
17
  """Determine if agent is happy and move if necessary."""
19
- neighbors = self.model.grid.iter_neighbors(
18
+ neighbors = self.model.grid.get_neighbors(
20
19
  self.pos, moore=True, radius=self.model.radius
21
20
  )
22
21
 
23
22
  # Count similar neighbors
24
- similar = sum(neighbor.type == self.type for neighbor in neighbors)
23
+ similar_neighbors = len([n for n in neighbors if n.type == self.type])
24
+
25
+ # Calculate the fraction of similar neighbors
26
+ if (valid_neighbors := len(neighbors)) > 0:
27
+ similarity_fraction = similar_neighbors / valid_neighbors
28
+ else:
29
+ # If there are no neighbors, the similarity fraction is 0
30
+ similarity_fraction = 0.0
25
31
 
26
- # If unhappy, move to a random empty cell:
27
- if similar < self.model.homophily:
32
+ # Move if unhappy
33
+ if similarity_fraction < self.model.homophily:
28
34
  self.model.grid.move_to_empty(self)
29
35
  else:
30
36
  self.model.happy += 1
@@ -26,7 +26,7 @@ model_params = {
26
26
  },
27
27
  "density": Slider("Agent density", 0.8, 0.1, 1.0, 0.1),
28
28
  "minority_pc": Slider("Fraction minority", 0.2, 0.0, 1.0, 0.05),
29
- "homophily": Slider("Homophily", 3, 0, 8, 1),
29
+ "homophily": Slider("Homophily", 0.3, 0.0, 0.8, 0.1),
30
30
  "width": 20,
31
31
  "height": 20,
32
32
  }
@@ -182,7 +182,10 @@ def draw_property_layers(
182
182
 
183
183
  for layer_name, portrayal in propertylayer_portrayal.items():
184
184
  layer = property_layers.get(layer_name, None)
185
- if not isinstance(layer, PropertyLayer):
185
+ if not isinstance(
186
+ layer,
187
+ PropertyLayer | mesa.experimental.cell_space.property_layer.PropertyLayer,
188
+ ):
186
189
  continue
187
190
 
188
191
  data = layer.data.astype(float) if layer.data.dtype == bool else layer.data
@@ -212,7 +215,7 @@ def draw_property_layers(
212
215
  layer_name, [(0, 0, 0, 0), (*rgba_color[:3], alpha)]
213
216
  )
214
217
  im = ax.imshow(
215
- rgba_data.transpose(1, 0, 2),
218
+ rgba_data,
216
219
  origin="lower",
217
220
  )
218
221
  if colorbar:
@@ -226,7 +229,7 @@ def draw_property_layers(
226
229
  if isinstance(cmap, list):
227
230
  cmap = LinearSegmentedColormap.from_list(layer_name, cmap)
228
231
  im = ax.imshow(
229
- data.T,
232
+ data,
230
233
  cmap=cmap,
231
234
  alpha=alpha,
232
235
  vmin=vmin,
@@ -102,24 +102,33 @@ def SolaraViz(
102
102
 
103
103
  # set up reactive model_parameters shared by ModelCreator and ModelController
104
104
  reactive_model_parameters = solara.use_reactive({})
105
+ reactive_play_interval = solara.use_reactive(play_interval)
105
106
 
106
107
  with solara.AppBar():
107
108
  solara.AppBarTitle(name if name else model.value.__class__.__name__)
108
109
 
109
110
  with solara.Sidebar(), solara.Column():
110
111
  with solara.Card("Controls"):
112
+ solara.SliderInt(
113
+ label="Play Interval (ms)",
114
+ value=reactive_play_interval,
115
+ on_value=lambda v: reactive_play_interval.set(v),
116
+ min=1,
117
+ max=500,
118
+ step=10,
119
+ )
111
120
  if not isinstance(simulator, Simulator):
112
121
  ModelController(
113
122
  model,
114
123
  model_parameters=reactive_model_parameters,
115
- play_interval=play_interval,
124
+ play_interval=reactive_play_interval,
116
125
  )
117
126
  else:
118
127
  SimulatorController(
119
128
  model,
120
129
  simulator,
121
130
  model_parameters=reactive_model_parameters,
122
- play_interval=play_interval,
131
+ play_interval=reactive_play_interval,
123
132
  )
124
133
  with solara.Card("Model Parameters"):
125
134
  ModelCreator(
@@ -179,7 +188,7 @@ def ModelController(
179
188
  model: solara.Reactive[Model],
180
189
  *,
181
190
  model_parameters: dict | solara.Reactive[dict] = None,
182
- play_interval: int = 100,
191
+ play_interval: int | solara.Reactive[int] = 100,
183
192
  ):
184
193
  """Create controls for model execution (step, play, pause, reset).
185
194
 
@@ -197,7 +206,7 @@ def ModelController(
197
206
 
198
207
  async def step():
199
208
  while playing.value and running.value:
200
- await asyncio.sleep(play_interval / 1000)
209
+ await asyncio.sleep(play_interval.value / 1000)
201
210
  do_step()
202
211
 
203
212
  solara.lab.use_task(
@@ -249,7 +258,7 @@ def SimulatorController(
249
258
  simulator,
250
259
  *,
251
260
  model_parameters: dict | solara.Reactive[dict] = None,
252
- play_interval: int = 100,
261
+ play_interval: int | solara.Reactive[int] = 100,
253
262
  ):
254
263
  """Create controls for model execution (step, play, pause, reset).
255
264
 
@@ -268,7 +277,7 @@ def SimulatorController(
268
277
 
269
278
  async def step():
270
279
  while playing.value and running.value:
271
- await asyncio.sleep(play_interval / 1000)
280
+ await asyncio.sleep(play_interval.value / 1000)
272
281
  do_step()
273
282
 
274
283
  solara.lab.use_task(
@@ -423,6 +432,17 @@ def _check_model_params(init_func, model_params):
423
432
  ValueError: If a parameter is not valid for the model's initialization function
424
433
  """
425
434
  model_parameters = inspect.signature(init_func).parameters
435
+
436
+ has_var_positional = any(
437
+ param.kind == inspect.Parameter.VAR_POSITIONAL
438
+ for param in model_parameters.values()
439
+ )
440
+
441
+ if has_var_positional:
442
+ raise ValueError(
443
+ "Mesa's visualization requires the use of keyword arguments to ensure the parameters are passed to Solara correctly. Please ensure all model parameters are of form param=value"
444
+ )
445
+
426
446
  for name in model_parameters:
427
447
  if (
428
448
  model_parameters[name].default == inspect.Parameter.empty
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: Mesa
3
- Version: 3.1.0
3
+ Version: 3.1.1
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
@@ -1,4 +1,4 @@
1
- mesa/__init__.py,sha256=9xQiRsNGK_hXnSjJI0S4NHuTB8ai9XtY6Jp3R44YbTA,611
1
+ mesa/__init__.py,sha256=4-j7gALmGrWuSOiUaI2o6UN6TyHL1MPwaAUC9C7PP04,611
2
2
  mesa/agent.py,sha256=4CXMOFA9KhvTypaV_OHZGqxOR4GVwyX4x8DOtQENUQA,26130
3
3
  mesa/batchrunner.py,sha256=w8StV82F_7DAAVQc5V7_Ggp0EL1NYn__UcBE-Nwrgv4,7771
4
4
  mesa/datacollection.py,sha256=8loT4pQsXcHArxHSsbRc7HTc2GP5gsEIeKFKr3xya4I,15991
@@ -16,22 +16,22 @@ mesa/examples/advanced/epstein_civil_violence/app.py,sha256=fFlPijAUDLs_WfROGIlU
16
16
  mesa/examples/advanced/epstein_civil_violence/model.py,sha256=fcTkjCRhEhDerDC1W_lrezdoWD1y5xIublkGIhCak8w,3918
17
17
  mesa/examples/advanced/pd_grid/Readme.md,sha256=UVUQxZRFdfymCKDdQEn3ZEwgSqgp9cKJPsU8oZpLFnY,2367
18
18
  mesa/examples/advanced/pd_grid/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- mesa/examples/advanced/pd_grid/agents.py,sha256=DzGj4LZUjV0xMQAwkRsv2Aoap1cUvLscJswfA1PdVDs,1716
19
+ mesa/examples/advanced/pd_grid/agents.py,sha256=8JnezmnwxjcqAUAgXa1iWls0Nz4yBS-TJi1TSVcHbqM,1752
20
20
  mesa/examples/advanced/pd_grid/analysis.ipynb,sha256=ReYtRe2JVyCCXoMBONvynXDQ_eGtQSWhNcuJY3CYTTI,82323
21
21
  mesa/examples/advanced/pd_grid/app.py,sha256=-_fTP7f_oITKHt7dDVJou7Oa7u7v_C4Ml5yw7D1sbx8,1457
22
- mesa/examples/advanced/pd_grid/model.py,sha256=DiHGeX7fR1slvcAzbmDCxnmXJ2Txjmju_gII3e-EJgY,2345
22
+ mesa/examples/advanced/pd_grid/model.py,sha256=-ytTSW0a_TQGTQW02k7XKAMiMak_b2XkJUw5kkGNHMQ,2291
23
23
  mesa/examples/advanced/sugarscape_g1mt/Readme.md,sha256=x3kKw1Rre2FPkNhGDLtdzeThmH089mxsGYUPZUeu26k,3595
24
24
  mesa/examples/advanced/sugarscape_g1mt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- mesa/examples/advanced/sugarscape_g1mt/agents.py,sha256=zE7zbO2_OaQiFDq_-t-0hv4ButBWv26dnS1zaPc3_ZE,10183
26
- mesa/examples/advanced/sugarscape_g1mt/app.py,sha256=zXwD_zSGmE3M4JN4qexV7OfiF2rtd1rJYszSSpTiceA,2882
27
- mesa/examples/advanced/sugarscape_g1mt/model.py,sha256=NFQRqwuOwP3kz1fNNPEm2XkdbS0G3-TgneiJdQZHqck,6111
25
+ mesa/examples/advanced/sugarscape_g1mt/agents.py,sha256=ugTNvWjQNm_jMInv9d0sOIFaChIDs-TWazN78YrDP9g,8466
26
+ mesa/examples/advanced/sugarscape_g1mt/app.py,sha256=7BnjOEkzltfUSx_Hz8h7QXqWARrnAIULodReXd4joRo,2479
27
+ mesa/examples/advanced/sugarscape_g1mt/model.py,sha256=s4n9SRaaMNY9CXFtry83BELZU69UJOCa4DMrw_eehr4,5763
28
28
  mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt,sha256=zZtGYciBPT4miZVnbVuoQ5TugTmGrbDWV9yb5KH6tnU,5000
29
29
  mesa/examples/advanced/sugarscape_g1mt/tests.py,sha256=UNahmZTgLquSqmoi_9GcE3JP0qBHjkrHFZ15NMm0ce8,2517
30
30
  mesa/examples/advanced/wolf_sheep/Readme.md,sha256=6zrtCg4Fb-hgQxqdLMpTkIYMwD6owCv8BMz_qn0N98Q,3165
31
31
  mesa/examples/advanced/wolf_sheep/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
- mesa/examples/advanced/wolf_sheep/agents.py,sha256=SISizN_alheFAXEP3NaeQTx22SPs0v3pRYqXKRyuXCo,3801
32
+ mesa/examples/advanced/wolf_sheep/agents.py,sha256=QH0WsJbxcKjAeHY63j7dVXCUGIPc5G1vl7MaC-k8UZw,3829
33
33
  mesa/examples/advanced/wolf_sheep/app.py,sha256=IIl-gDh1O3oYIvrXXGmKHbW5Iw3ZpCn691dGwKgyI3E,2508
34
- mesa/examples/advanced/wolf_sheep/model.py,sha256=UYUY8GP6YA64mbJkWDr9eUSZlE8naxv8-a2qaGPdUXE,4531
34
+ mesa/examples/advanced/wolf_sheep/model.py,sha256=IUN1STm6jCGuzXo2sCF86r1U-dI63yhLhnI14tu9BSw,4567
35
35
  mesa/examples/basic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
36
  mesa/examples/basic/boid_flockers/Readme.md,sha256=4KJinsLPtUciQSMzvaX3tU5r1HTUg3AFOFDKy73W5RE,894
37
37
  mesa/examples/basic/boid_flockers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -52,9 +52,9 @@ mesa/examples/basic/conways_game_of_life/model.py,sha256=8L88m8F_YauwWeDef6UA2my
52
52
  mesa/examples/basic/conways_game_of_life/st_app.py,sha256=9qz3o0pOuvLZR-_aPPVHN-RIwFSNzvwWWfxCaD2K5cs,2402
53
53
  mesa/examples/basic/schelling/Readme.md,sha256=CRKBfYtnLJLlTKLsTRQ-7gsQRxVxDooOBN5uP8PEtaU,2296
54
54
  mesa/examples/basic/schelling/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
- mesa/examples/basic/schelling/agents.py,sha256=BzipyAU5d_H4DoTm3xkeZLDazsr49rbaVr_kyk1rDJA,937
55
+ mesa/examples/basic/schelling/agents.py,sha256=WdDM-BYeOUuIL_2U5rlZz4Rs_5orstHY2Z6G6zvQEyg,1224
56
56
  mesa/examples/basic/schelling/analysis.ipynb,sha256=JDJy6-U6eO-LrHWxZr1c3lkvtoY0DNHa-kJ4J-Z-wwo,5804
57
- mesa/examples/basic/schelling/app.py,sha256=dT0wfZVr2SbHrnlP_O0NNm3wKIYX-vtfD70L0blgils,1057
57
+ mesa/examples/basic/schelling/app.py,sha256=DOu7aWjDeFGjD1yXlBbQ9qCUHguDDkYh4bmFBjM0Vb8,1065
58
58
  mesa/examples/basic/schelling/model.py,sha256=ruqiMNBBsWV81srYZgdsfDVwgMWYuw2VAzSQhsK5E98,2762
59
59
  mesa/examples/basic/virus_on_network/Readme.md,sha256=qmXGx4Fo0tRBvJiiJ684bkWJPn2gcFaiikLwgc5APWI,2336
60
60
  mesa/examples/basic/virus_on_network/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -79,16 +79,16 @@ mesa/experimental/mesa_signals/mesa_signal.py,sha256=Vxo4gIV6a959MANL3RMANsGh0R9
79
79
  mesa/experimental/mesa_signals/observable_collections.py,sha256=rHEj6BYxLHFFGzSdoDKAdtzJ6y-IHHfcP3qEDJJsY6Y,3917
80
80
  mesa/experimental/mesa_signals/signals_util.py,sha256=fmq_FsIxsIvGjtmc4A9TGdBUtdliMHhEOpjRXivRDjA,1618
81
81
  mesa/visualization/__init__.py,sha256=YW-oHEOTjbtDKD_TylAMtVnt8mrsz1Fw7ifdc4WeHxA,743
82
- mesa/visualization/mpl_space_drawing.py,sha256=lFUQY5G7x2dNqxqTBfF6CjnGHMI_R7xGM1bskqPU68c,20066
83
- mesa/visualization/solara_viz.py,sha256=-OHf6E8sTrnCaPROo9_feK7e3dv8yl_SHzidlRhZ6P8,18519
82
+ mesa/visualization/mpl_space_drawing.py,sha256=ZqZ-THsmdyTcwaUMik_S8990K8_FqliLeQMZwY45waM,20140
83
+ mesa/visualization/solara_viz.py,sha256=BjhmH2FLlVc8rxfAze9Ex1wj_0jkVOH-_bXz2MYzd2A,19325
84
84
  mesa/visualization/user_param.py,sha256=Dl2WOwLYLf0pfLpabCZtIdFRyKZrK6Qtc3utZx5GPYg,2139
85
85
  mesa/visualization/utils.py,sha256=lJHgRKF5BHLf72Tw3YpwyiWuRoIimaTKQ7xBCw_Rx3A,146
86
86
  mesa/visualization/components/__init__.py,sha256=Bq3nrPikcaIo9BSs0O3zptWVLlUmAkLo3s0mEmpH1RE,3022
87
87
  mesa/visualization/components/altair_components.py,sha256=wotpFFQgMY-ZR3lNVm_fRos-iDg0Wjnj6Tk67_7f1SQ,5847
88
88
  mesa/visualization/components/matplotlib_components.py,sha256=xQETaFyHIfmL_9JwrLIgubuIQ7-pp7TMoXT1WMmozus,5441
89
- mesa-3.1.0.dist-info/METADATA,sha256=p5S8DvlFII035nqJgdSg_Iqc3tZVQfKzGWwtgXTWgkw,9906
90
- mesa-3.1.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
91
- mesa-3.1.0.dist-info/entry_points.txt,sha256=IOcQtetGF8l4wHpOs_hGb19Rz-FS__BMXOJR10IBPsA,39
92
- mesa-3.1.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
93
- mesa-3.1.0.dist-info/licenses/NOTICE,sha256=GbsWoK0QWv1JyZ_xer2s-jNilv0RtWl-0UrtlJANHPg,578
94
- mesa-3.1.0.dist-info/RECORD,,
89
+ mesa-3.1.1.dist-info/METADATA,sha256=s-5wsI5DiH4EK2qbn1UhNObxe-jB9hOoBvh5GwEU-eg,9906
90
+ mesa-3.1.1.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
91
+ mesa-3.1.1.dist-info/entry_points.txt,sha256=IOcQtetGF8l4wHpOs_hGb19Rz-FS__BMXOJR10IBPsA,39
92
+ mesa-3.1.1.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
93
+ mesa-3.1.1.dist-info/licenses/NOTICE,sha256=GbsWoK0QWv1JyZ_xer2s-jNilv0RtWl-0UrtlJANHPg,578
94
+ mesa-3.1.1.dist-info/RECORD,,
File without changes