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 +1 -1
- mesa/examples/advanced/pd_grid/agents.py +2 -1
- 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 +17 -17
- mesa/examples/advanced/sugarscape_g1mt/model.py +45 -52
- mesa/examples/advanced/wolf_sheep/agents.py +3 -1
- mesa/examples/advanced/wolf_sheep/model.py +17 -16
- mesa/examples/basic/schelling/agents.py +11 -5
- mesa/examples/basic/schelling/app.py +1 -1
- mesa/visualization/mpl_space_drawing.py +6 -3
- mesa/visualization/solara_viz.py +26 -6
- {mesa-3.1.0.dist-info → mesa-3.1.1.dist-info}/METADATA +1 -1
- {mesa-3.1.0.dist-info → mesa-3.1.1.dist-info}/RECORD +18 -18
- {mesa-3.1.0.dist-info → mesa-3.1.1.dist-info}/WHEEL +0 -0
- {mesa-3.1.0.dist-info → mesa-3.1.1.dist-info}/entry_points.txt +0 -0
- {mesa-3.1.0.dist-info → mesa-3.1.1.dist-info}/licenses/LICENSE +0 -0
- {mesa-3.1.0.dist-info → mesa-3.1.1.dist-info}/licenses/NOTICE +0 -0
mesa/__init__.py
CHANGED
|
@@ -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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
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()
|
|
@@ -75,7 +71,11 @@ model = SugarscapeG1mt()
|
|
|
75
71
|
|
|
76
72
|
page = SolaraViz(
|
|
77
73
|
model,
|
|
78
|
-
components=[
|
|
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
|
|
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:
|
|
@@ -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:
|
|
@@ -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
|
|
@@ -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(
|
|
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
|
|
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
|
|
232
|
+
data,
|
|
230
233
|
cmap=cmap,
|
|
231
234
|
alpha=alpha,
|
|
232
235
|
vmin=vmin,
|
mesa/visualization/solara_viz.py
CHANGED
|
@@ -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=
|
|
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=
|
|
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,4 +1,4 @@
|
|
|
1
|
-
mesa/__init__.py,sha256=
|
|
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=
|
|
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
|
|
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=
|
|
26
|
-
mesa/examples/advanced/sugarscape_g1mt/app.py,sha256=
|
|
27
|
-
mesa/examples/advanced/sugarscape_g1mt/model.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
83
|
-
mesa/visualization/solara_viz.py,sha256
|
|
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.
|
|
90
|
-
mesa-3.1.
|
|
91
|
-
mesa-3.1.
|
|
92
|
-
mesa-3.1.
|
|
93
|
-
mesa-3.1.
|
|
94
|
-
mesa-3.1.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|