Mesa 3.1.0.dev0__py3-none-any.whl → 3.1.2__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.

Files changed (60) hide show
  1. mesa/__init__.py +3 -3
  2. mesa/agent.py +48 -0
  3. mesa/batchrunner.py +14 -1
  4. mesa/datacollection.py +1 -6
  5. mesa/examples/__init__.py +2 -2
  6. mesa/examples/advanced/epstein_civil_violence/app.py +5 -0
  7. mesa/examples/advanced/pd_grid/agents.py +2 -1
  8. mesa/examples/advanced/pd_grid/analysis.ipynb +44 -89
  9. mesa/examples/advanced/pd_grid/app.py +5 -0
  10. mesa/examples/advanced/pd_grid/model.py +3 -5
  11. mesa/examples/advanced/sugarscape_g1mt/agents.py +12 -65
  12. mesa/examples/advanced/sugarscape_g1mt/app.py +24 -19
  13. mesa/examples/advanced/sugarscape_g1mt/model.py +45 -52
  14. mesa/examples/advanced/wolf_sheep/agents.py +36 -2
  15. mesa/examples/advanced/wolf_sheep/model.py +17 -16
  16. mesa/examples/basic/boid_flockers/app.py +5 -0
  17. mesa/examples/basic/boltzmann_wealth_model/app.py +8 -5
  18. mesa/examples/basic/boltzmann_wealth_model/st_app.py +1 -1
  19. mesa/examples/basic/conways_game_of_life/app.py +5 -0
  20. mesa/examples/basic/conways_game_of_life/st_app.py +2 -2
  21. mesa/examples/basic/schelling/agents.py +11 -5
  22. mesa/examples/basic/schelling/analysis.ipynb +42 -36
  23. mesa/examples/basic/schelling/app.py +6 -1
  24. mesa/examples/basic/schelling/model.py +3 -3
  25. mesa/examples/basic/virus_on_network/app.py +5 -0
  26. mesa/experimental/__init__.py +17 -10
  27. mesa/experimental/cell_space/__init__.py +19 -7
  28. mesa/experimental/cell_space/cell.py +22 -37
  29. mesa/experimental/cell_space/cell_agent.py +12 -1
  30. mesa/experimental/cell_space/cell_collection.py +18 -3
  31. mesa/experimental/cell_space/discrete_space.py +15 -64
  32. mesa/experimental/cell_space/grid.py +74 -4
  33. mesa/experimental/cell_space/network.py +13 -1
  34. mesa/experimental/cell_space/property_layer.py +444 -0
  35. mesa/experimental/cell_space/voronoi.py +13 -1
  36. mesa/experimental/devs/__init__.py +20 -2
  37. mesa/experimental/devs/eventlist.py +19 -1
  38. mesa/experimental/devs/simulator.py +24 -8
  39. mesa/experimental/mesa_signals/__init__.py +23 -0
  40. mesa/experimental/mesa_signals/mesa_signal.py +485 -0
  41. mesa/experimental/mesa_signals/observable_collections.py +133 -0
  42. mesa/experimental/mesa_signals/signals_util.py +52 -0
  43. mesa/mesa_logging.py +190 -0
  44. mesa/model.py +17 -23
  45. mesa/visualization/__init__.py +2 -2
  46. mesa/visualization/mpl_space_drawing.py +8 -6
  47. mesa/visualization/solara_viz.py +49 -11
  48. {mesa-3.1.0.dev0.dist-info → mesa-3.1.2.dist-info}/METADATA +4 -2
  49. mesa-3.1.2.dist-info/RECORD +94 -0
  50. {mesa-3.1.0.dev0.dist-info → mesa-3.1.2.dist-info}/WHEEL +1 -1
  51. mesa/experimental/UserParam.py +0 -67
  52. mesa/experimental/components/altair.py +0 -81
  53. mesa/experimental/components/matplotlib.py +0 -242
  54. mesa/experimental/devs/examples/epstein_civil_violence.py +0 -305
  55. mesa/experimental/devs/examples/wolf_sheep.py +0 -250
  56. mesa/experimental/solara_viz.py +0 -453
  57. mesa-3.1.0.dev0.dist-info/RECORD +0 -94
  58. {mesa-3.1.0.dev0.dist-info → mesa-3.1.2.dist-info}/entry_points.txt +0 -0
  59. {mesa-3.1.0.dev0.dist-info → mesa-3.1.2.dist-info}/licenses/LICENSE +0 -0
  60. {mesa-3.1.0.dev0.dist-info → mesa-3.1.2.dist-info}/licenses/NOTICE +0 -0
@@ -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()
@@ -47,6 +43,11 @@ def SpaceDrawer(model):
47
43
 
48
44
 
49
45
  model_params = {
46
+ "seed": {
47
+ "type": "InputText",
48
+ "value": 42,
49
+ "label": "Random Seed",
50
+ },
50
51
  "width": 50,
51
52
  "height": 50,
52
53
  # Population parameters
@@ -66,11 +67,15 @@ model_params = {
66
67
  "enable_trade": {"type": "Checkbox", "value": True, "label": "Enable Trading"},
67
68
  }
68
69
 
69
- model1 = SugarscapeG1mt()
70
+ model = SugarscapeG1mt()
70
71
 
71
72
  page = SolaraViz(
72
- model1,
73
- components=[SpaceDrawer, make_plot_component(["Trader", "Price"])],
73
+ model,
74
+ components=[
75
+ SpaceDrawer,
76
+ make_plot_component("#Traders"),
77
+ make_plot_component("Price"),
78
+ ],
74
79
  model_params=model_params,
75
80
  name="Sugarscape {G1, M, T}",
76
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:
@@ -37,7 +39,8 @@ class Animal(CellAgent):
37
39
  def step(self):
38
40
  """Execute one step of the animal's behavior."""
39
41
  # Move to random neighboring cell
40
- self.cell = self.cell.neighborhood.select_random_cell()
42
+ self.move()
43
+
41
44
  self.energy -= 1
42
45
 
43
46
  # Try to feed
@@ -62,6 +65,27 @@ class Sheep(Animal):
62
65
  self.energy += self.energy_from_food
63
66
  grass_patch.fully_grown = False
64
67
 
68
+ def move(self):
69
+ """Move towards a cell where there isn't a wolf, and preferably with grown grass."""
70
+ cells_without_wolves = self.cell.neighborhood.select(
71
+ lambda cell: not any(isinstance(obj, Wolf) for obj in cell.agents)
72
+ )
73
+ # If all surrounding cells have wolves, stay put
74
+ if len(cells_without_wolves) == 0:
75
+ return
76
+
77
+ # Among safe cells, prefer those with grown grass
78
+ cells_with_grass = cells_without_wolves.select(
79
+ lambda cell: any(
80
+ isinstance(obj, GrassPatch) and obj.fully_grown for obj in cell.agents
81
+ )
82
+ )
83
+ # Move to a cell with grass if available, otherwise move to any safe cell
84
+ target_cells = (
85
+ cells_with_grass if len(cells_with_grass) > 0 else cells_without_wolves
86
+ )
87
+ self.cell = target_cells.select_random_cell()
88
+
65
89
 
66
90
  class Wolf(Animal):
67
91
  """A wolf that walks around, reproduces (asexually) and eats sheep."""
@@ -74,6 +98,16 @@ class Wolf(Animal):
74
98
  self.energy += self.energy_from_food
75
99
  sheep_to_eat.remove()
76
100
 
101
+ def move(self):
102
+ """Move to a neighboring cell, preferably one with sheep."""
103
+ cells_with_sheep = self.cell.neighborhood.select(
104
+ lambda cell: any(isinstance(obj, Sheep) for obj in cell.agents)
105
+ )
106
+ target_cells = (
107
+ cells_with_sheep if len(cells_with_sheep) > 0 else self.cell.neighborhood
108
+ )
109
+ self.cell = target_cells.select_random_cell()
110
+
77
111
 
78
112
  class GrassPatch(FixedAgent):
79
113
  """A patch of grass that grows at a fixed rate and can be eaten by sheep."""
@@ -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:
@@ -12,6 +12,11 @@ def boid_draw(agent):
12
12
 
13
13
 
14
14
  model_params = {
15
+ "seed": {
16
+ "type": "InputText",
17
+ "value": 42,
18
+ "label": "Random Seed",
19
+ },
15
20
  "population": Slider(
16
21
  label="Number of boids",
17
22
  value=100,
@@ -1,10 +1,13 @@
1
1
  from mesa.examples.basic.boltzmann_wealth_model.model import BoltzmannWealth
2
+ from mesa.mesa_logging import DEBUG, log_to_stderr
2
3
  from mesa.visualization import (
3
4
  SolaraViz,
4
5
  make_plot_component,
5
6
  make_space_component,
6
7
  )
7
8
 
9
+ log_to_stderr(DEBUG)
10
+
8
11
 
9
12
  def agent_portrayal(agent):
10
13
  color = agent.wealth # we are using a colormap to translate wealth to color
@@ -12,6 +15,11 @@ def agent_portrayal(agent):
12
15
 
13
16
 
14
17
  model_params = {
18
+ "seed": {
19
+ "type": "InputText",
20
+ "value": 42,
21
+ "label": "Random Seed",
22
+ },
15
23
  "n": {
16
24
  "type": "SliderInt",
17
25
  "value": 50,
@@ -20,11 +28,6 @@ model_params = {
20
28
  "max": 100,
21
29
  "step": 1,
22
30
  },
23
- "seed": {
24
- "type": "InputText",
25
- "value": 42,
26
- "label": "Random Seed",
27
- },
28
31
  "width": 10,
29
32
  "height": 10,
30
33
  }
@@ -68,7 +68,7 @@ if run:
68
68
  for i in range(num_ticks):
69
69
  model.step()
70
70
  my_bar.progress((i / num_ticks), text="Simulation progress")
71
- placeholder.text("Step = %d" % i)
71
+ placeholder.text(f"Step = {i}")
72
72
  for cell in model.grid.coord_iter():
73
73
  cell_content, (x, y) = cell
74
74
  agent_count = len(cell_content)
@@ -20,6 +20,11 @@ def post_process(ax):
20
20
 
21
21
 
22
22
  model_params = {
23
+ "seed": {
24
+ "type": "InputText",
25
+ "value": 42,
26
+ "label": "Random Seed",
27
+ },
23
28
  "width": {
24
29
  "type": "SliderInt",
25
30
  "value": 50,
@@ -49,9 +49,9 @@ if run:
49
49
  for i in range(num_ticks):
50
50
  model.step()
51
51
  my_bar.progress((i / num_ticks), text="Simulation progress")
52
- placeholder.text("Step = %d" % i)
52
+ placeholder.text(f"Step = {i}")
53
53
  for contents, (x, y) in model.grid.coord_iter():
54
- # print('x:',x,'y:',y, 'state:',contents)
54
+ # print(f"x: {x}, y: {y}, state: {contents}")
55
55
  selected_row = df_grid[(df_grid["x"] == x) & (df_grid["y"] == y)]
56
56
  df_grid.loc[selected_row.index, "state"] = (
57
57
  contents.state
@@ -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