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.
- mesa/__init__.py +3 -3
- mesa/agent.py +48 -0
- mesa/batchrunner.py +14 -1
- mesa/datacollection.py +1 -6
- mesa/examples/__init__.py +2 -2
- mesa/examples/advanced/epstein_civil_violence/app.py +5 -0
- mesa/examples/advanced/pd_grid/agents.py +2 -1
- mesa/examples/advanced/pd_grid/analysis.ipynb +44 -89
- mesa/examples/advanced/pd_grid/app.py +5 -0
- mesa/examples/advanced/pd_grid/model.py +3 -5
- mesa/examples/advanced/sugarscape_g1mt/agents.py +12 -65
- mesa/examples/advanced/sugarscape_g1mt/app.py +24 -19
- mesa/examples/advanced/sugarscape_g1mt/model.py +45 -52
- mesa/examples/advanced/wolf_sheep/agents.py +36 -2
- mesa/examples/advanced/wolf_sheep/model.py +17 -16
- mesa/examples/basic/boid_flockers/app.py +5 -0
- mesa/examples/basic/boltzmann_wealth_model/app.py +8 -5
- mesa/examples/basic/boltzmann_wealth_model/st_app.py +1 -1
- mesa/examples/basic/conways_game_of_life/app.py +5 -0
- mesa/examples/basic/conways_game_of_life/st_app.py +2 -2
- mesa/examples/basic/schelling/agents.py +11 -5
- mesa/examples/basic/schelling/analysis.ipynb +42 -36
- mesa/examples/basic/schelling/app.py +6 -1
- mesa/examples/basic/schelling/model.py +3 -3
- mesa/examples/basic/virus_on_network/app.py +5 -0
- mesa/experimental/__init__.py +17 -10
- mesa/experimental/cell_space/__init__.py +19 -7
- mesa/experimental/cell_space/cell.py +22 -37
- mesa/experimental/cell_space/cell_agent.py +12 -1
- mesa/experimental/cell_space/cell_collection.py +18 -3
- mesa/experimental/cell_space/discrete_space.py +15 -64
- mesa/experimental/cell_space/grid.py +74 -4
- mesa/experimental/cell_space/network.py +13 -1
- mesa/experimental/cell_space/property_layer.py +444 -0
- mesa/experimental/cell_space/voronoi.py +13 -1
- mesa/experimental/devs/__init__.py +20 -2
- mesa/experimental/devs/eventlist.py +19 -1
- mesa/experimental/devs/simulator.py +24 -8
- mesa/experimental/mesa_signals/__init__.py +23 -0
- mesa/experimental/mesa_signals/mesa_signal.py +485 -0
- mesa/experimental/mesa_signals/observable_collections.py +133 -0
- mesa/experimental/mesa_signals/signals_util.py +52 -0
- mesa/mesa_logging.py +190 -0
- mesa/model.py +17 -23
- mesa/visualization/__init__.py +2 -2
- mesa/visualization/mpl_space_drawing.py +8 -6
- mesa/visualization/solara_viz.py +49 -11
- {mesa-3.1.0.dev0.dist-info → mesa-3.1.2.dist-info}/METADATA +4 -2
- mesa-3.1.2.dist-info/RECORD +94 -0
- {mesa-3.1.0.dev0.dist-info → mesa-3.1.2.dist-info}/WHEEL +1 -1
- mesa/experimental/UserParam.py +0 -67
- mesa/experimental/components/altair.py +0 -81
- mesa/experimental/components/matplotlib.py +0 -242
- mesa/experimental/devs/examples/epstein_civil_violence.py +0 -305
- mesa/experimental/devs/examples/wolf_sheep.py +0 -250
- mesa/experimental/solara_viz.py +0 -453
- mesa-3.1.0.dev0.dist-info/RECORD +0 -94
- {mesa-3.1.0.dev0.dist-info → mesa-3.1.2.dist-info}/entry_points.txt +0 -0
- {mesa-3.1.0.dev0.dist-info → mesa-3.1.2.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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
|
|
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 +
|
|
275
|
-
self.spice +
|
|
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
|
-
|
|
304
|
-
|
|
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
|
-
|
|
310
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
70
|
+
model = SugarscapeG1mt()
|
|
70
71
|
|
|
71
72
|
page = SolaraViz(
|
|
72
|
-
|
|
73
|
-
components=[
|
|
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
|
|
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
|
-
"
|
|
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.
|
|
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
|
|
91
|
-
sugar_distribution = np.genfromtxt(Path(__file__).parent / "sugar-map.txt")
|
|
92
|
-
spice_distribution = np.flip(sugar_distribution, 1)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
sugar
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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.
|
|
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__(
|
|
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.
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
# Create
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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:
|
|
@@ -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 =
|
|
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)
|
|
@@ -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 =
|
|
52
|
+
placeholder.text(f"Step = {i}")
|
|
53
53
|
for contents, (x, y) in model.grid.coord_iter():
|
|
54
|
-
# print(
|
|
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.
|
|
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
|
-
|
|
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
|
-
#
|
|
27
|
-
if
|
|
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
|