Mesa 3.0.0b0__py3-none-any.whl → 3.0.0b2__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 (85) hide show
  1. mesa/__init__.py +2 -1
  2. mesa/agent.py +37 -27
  3. mesa/examples/README.md +37 -0
  4. mesa/examples/__init__.py +21 -0
  5. mesa/examples/advanced/__init__.py +0 -0
  6. mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +116 -0
  7. mesa/examples/advanced/epstein_civil_violence/Readme.md +34 -0
  8. mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
  9. mesa/examples/advanced/epstein_civil_violence/agents.py +158 -0
  10. mesa/examples/advanced/epstein_civil_violence/app.py +72 -0
  11. mesa/examples/advanced/epstein_civil_violence/model.py +146 -0
  12. mesa/examples/advanced/pd_grid/Readme.md +43 -0
  13. mesa/examples/advanced/pd_grid/__init__.py +0 -0
  14. mesa/examples/advanced/pd_grid/agents.py +50 -0
  15. mesa/examples/advanced/pd_grid/analysis.ipynb +228 -0
  16. mesa/examples/advanced/pd_grid/app.py +50 -0
  17. mesa/examples/advanced/pd_grid/model.py +71 -0
  18. mesa/examples/advanced/sugarscape_g1mt/Readme.md +64 -0
  19. mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
  20. mesa/examples/advanced/sugarscape_g1mt/agents.py +344 -0
  21. mesa/examples/advanced/sugarscape_g1mt/app.py +70 -0
  22. mesa/examples/advanced/sugarscape_g1mt/model.py +180 -0
  23. mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +50 -0
  24. mesa/examples/advanced/sugarscape_g1mt/tests.py +69 -0
  25. mesa/examples/advanced/wolf_sheep/Readme.md +57 -0
  26. mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
  27. mesa/examples/advanced/wolf_sheep/agents.py +102 -0
  28. mesa/examples/advanced/wolf_sheep/app.py +77 -0
  29. mesa/examples/advanced/wolf_sheep/model.py +137 -0
  30. mesa/examples/basic/__init__.py +0 -0
  31. mesa/examples/basic/boid_flockers/Readme.md +22 -0
  32. mesa/examples/basic/boid_flockers/__init__.py +0 -0
  33. mesa/examples/basic/boid_flockers/agents.py +71 -0
  34. mesa/examples/basic/boid_flockers/app.py +58 -0
  35. mesa/examples/basic/boid_flockers/model.py +69 -0
  36. mesa/examples/basic/boltzmann_wealth_model/Readme.md +56 -0
  37. mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
  38. mesa/examples/basic/boltzmann_wealth_model/agents.py +31 -0
  39. mesa/examples/basic/boltzmann_wealth_model/app.py +65 -0
  40. mesa/examples/basic/boltzmann_wealth_model/model.py +43 -0
  41. mesa/examples/basic/boltzmann_wealth_model/st_app.py +115 -0
  42. mesa/examples/basic/conways_game_of_life/Readme.md +39 -0
  43. mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
  44. mesa/examples/basic/conways_game_of_life/agents.py +47 -0
  45. mesa/examples/basic/conways_game_of_life/app.py +39 -0
  46. mesa/examples/basic/conways_game_of_life/model.py +31 -0
  47. mesa/examples/basic/conways_game_of_life/st_app.py +72 -0
  48. mesa/examples/basic/schelling/Readme.md +40 -0
  49. mesa/examples/basic/schelling/__init__.py +0 -0
  50. mesa/examples/basic/schelling/agents.py +26 -0
  51. mesa/examples/basic/schelling/analysis.ipynb +205 -0
  52. mesa/examples/basic/schelling/app.py +42 -0
  53. mesa/examples/basic/schelling/model.py +59 -0
  54. mesa/examples/basic/virus_on_network/Readme.md +61 -0
  55. mesa/examples/basic/virus_on_network/__init__.py +0 -0
  56. mesa/examples/basic/virus_on_network/agents.py +69 -0
  57. mesa/examples/basic/virus_on_network/app.py +136 -0
  58. mesa/examples/basic/virus_on_network/model.py +96 -0
  59. mesa/experimental/__init__.py +8 -2
  60. mesa/experimental/cell_space/cell.py +9 -0
  61. mesa/experimental/cell_space/discrete_space.py +13 -1
  62. mesa/experimental/cell_space/grid.py +13 -0
  63. mesa/experimental/cell_space/network.py +3 -0
  64. mesa/experimental/devs/eventlist.py +6 -0
  65. mesa/model.py +76 -12
  66. mesa/space.py +70 -5
  67. mesa/time.py +5 -3
  68. mesa/visualization/components/altair.py +87 -19
  69. mesa/visualization/components/matplotlib.py +65 -16
  70. mesa/visualization/solara_viz.py +13 -58
  71. {mesa-3.0.0b0.dist-info → mesa-3.0.0b2.dist-info}/METADATA +1 -3
  72. mesa-3.0.0b2.dist-info/RECORD +93 -0
  73. mesa/cookiecutter-mesa/cookiecutter.json +0 -8
  74. mesa/cookiecutter-mesa/hooks/post_gen_project.py +0 -13
  75. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +0 -4
  76. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate +0 -27
  77. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +0 -11
  78. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/__init__.py +0 -1
  79. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +0 -60
  80. mesa/main.py +0 -65
  81. mesa-3.0.0b0.dist-info/RECORD +0 -45
  82. {mesa-3.0.0b0.dist-info → mesa-3.0.0b2.dist-info}/WHEEL +0 -0
  83. {mesa-3.0.0b0.dist-info → mesa-3.0.0b2.dist-info}/entry_points.txt +0 -0
  84. {mesa-3.0.0b0.dist-info → mesa-3.0.0b2.dist-info}/licenses/LICENSE +0 -0
  85. {mesa-3.0.0b0.dist-info → mesa-3.0.0b2.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,344 @@
1
+ import math
2
+
3
+ from mesa.experimental.cell_space import CellAgent, FixedAgent
4
+
5
+
6
+ # Helper function
7
+ def get_distance(cell_1, cell_2):
8
+ """
9
+ Calculate the Euclidean distance between two positions
10
+
11
+ used in trade.move()
12
+ """
13
+
14
+ x1, y1 = cell_1.coordinate
15
+ x2, y2 = cell_2.coordinate
16
+ dx = x1 - x2
17
+ dy = y1 - y2
18
+ return math.sqrt(dx**2 + dy**2)
19
+
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
+ class Trader(CellAgent):
47
+ """
48
+ Trader:
49
+ - has a metabolism of sugar and spice
50
+ - harvest and trade sugar and spice to survive
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ model,
56
+ cell,
57
+ sugar=0,
58
+ spice=0,
59
+ metabolism_sugar=0,
60
+ metabolism_spice=0,
61
+ vision=0,
62
+ ):
63
+ super().__init__(model)
64
+ self.cell = cell
65
+ self.sugar = sugar
66
+ self.spice = spice
67
+ self.metabolism_sugar = metabolism_sugar
68
+ self.metabolism_spice = metabolism_spice
69
+ self.vision = vision
70
+ self.prices = []
71
+ self.trade_partners = []
72
+
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
+ def get_trader(self, cell):
80
+ """
81
+ helper function used in self.trade_with_neighbors()
82
+ """
83
+
84
+ for agent in cell.agents:
85
+ if isinstance(agent, Trader):
86
+ return agent
87
+
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
+ def calculate_welfare(self, sugar, spice):
100
+ """
101
+ helper function
102
+
103
+ part 2 self.move()
104
+ self.trade()
105
+ """
106
+
107
+ # calculate total resources
108
+ m_total = self.metabolism_sugar + self.metabolism_spice
109
+ # Cobb-Douglas functional form; starting on p. 97
110
+ # on Growing Artificial Societies
111
+ return sugar ** (self.metabolism_sugar / m_total) * spice ** (
112
+ self.metabolism_spice / m_total
113
+ )
114
+
115
+ def is_starved(self):
116
+ """
117
+ Helper function for self.maybe_die()
118
+ """
119
+
120
+ return (self.sugar <= 0) or (self.spice <= 0)
121
+
122
+ def calculate_MRS(self, sugar, spice):
123
+ """
124
+ Helper function for
125
+ - self.trade()
126
+ - self.maybe_self_spice()
127
+
128
+ Determines what trader agent needs and can give up
129
+ """
130
+
131
+ return (spice / self.metabolism_spice) / (sugar / self.metabolism_sugar)
132
+
133
+ def calculate_sell_spice_amount(self, price):
134
+ """
135
+ helper function for self.maybe_sell_spice() which is called from
136
+ self.trade()
137
+ """
138
+
139
+ if price >= 1:
140
+ sugar = 1
141
+ spice = int(price)
142
+ else:
143
+ sugar = int(1 / price)
144
+ spice = 1
145
+ return sugar, spice
146
+
147
+ def sell_spice(self, other, sugar, spice):
148
+ """
149
+ used in self.maybe_sell_spice()
150
+
151
+ exchanges sugar and spice between traders
152
+ """
153
+
154
+ self.sugar += sugar
155
+ other.sugar -= sugar
156
+ self.spice -= spice
157
+ other.spice += spice
158
+
159
+ def maybe_sell_spice(self, other, price, welfare_self, welfare_other):
160
+ """
161
+ helper function for self.trade()
162
+ """
163
+
164
+ sugar_exchanged, spice_exchanged = self.calculate_sell_spice_amount(price)
165
+
166
+ # Assess new sugar and spice amount - what if change did occur
167
+ self_sugar = self.sugar + sugar_exchanged
168
+ other_sugar = other.sugar - sugar_exchanged
169
+ self_spice = self.spice - spice_exchanged
170
+ other_spice = other.spice + spice_exchanged
171
+
172
+ # double check to ensure agents have resources
173
+
174
+ if (
175
+ (self_sugar <= 0)
176
+ or (other_sugar <= 0)
177
+ or (self_spice <= 0)
178
+ or (other_spice <= 0)
179
+ ):
180
+ return False
181
+
182
+ # trade criteria #1 - are both agents better off?
183
+ both_agents_better_off = (
184
+ welfare_self < self.calculate_welfare(self_sugar, self_spice)
185
+ ) and (welfare_other < other.calculate_welfare(other_sugar, other_spice))
186
+
187
+ # trade criteria #2 is their mrs crossing with potential trade
188
+ mrs_not_crossing = self.calculate_MRS(
189
+ self_sugar, self_spice
190
+ ) > other.calculate_MRS(other_sugar, other_spice)
191
+
192
+ if not (both_agents_better_off and mrs_not_crossing):
193
+ return False
194
+
195
+ # criteria met, execute trade
196
+ self.sell_spice(other, sugar_exchanged, spice_exchanged)
197
+
198
+ return True
199
+
200
+ def trade(self, other):
201
+ """
202
+ helper function used in trade_with_neighbors()
203
+
204
+ other is a trader agent object
205
+ """
206
+
207
+ # sanity check to verify code is working as expected
208
+ assert self.sugar > 0
209
+ assert self.spice > 0
210
+ assert other.sugar > 0
211
+ assert other.spice > 0
212
+
213
+ # calculate marginal rate of substitution in Growing Artificial Societies p. 101
214
+ mrs_self = self.calculate_MRS(self.sugar, self.spice)
215
+ mrs_other = other.calculate_MRS(other.sugar, other.spice)
216
+
217
+ # calculate each agents welfare
218
+ welfare_self = self.calculate_welfare(self.sugar, self.spice)
219
+ welfare_other = other.calculate_welfare(other.sugar, other.spice)
220
+
221
+ if math.isclose(mrs_self, mrs_other):
222
+ return
223
+
224
+ # calculate price
225
+ price = math.sqrt(mrs_self * mrs_other)
226
+
227
+ if mrs_self > mrs_other:
228
+ # self is a sugar buyer, spice seller
229
+ sold = self.maybe_sell_spice(other, price, welfare_self, welfare_other)
230
+ # no trade - criteria not met
231
+ if not sold:
232
+ return
233
+ else:
234
+ # self is a spice buyer, sugar seller
235
+ sold = other.maybe_sell_spice(self, price, welfare_other, welfare_self)
236
+ # no trade - criteria not met
237
+ if not sold:
238
+ return
239
+
240
+ # Capture data
241
+ self.prices.append(price)
242
+ self.trade_partners.append(other.unique_id)
243
+
244
+ # continue trading
245
+ self.trade(other)
246
+
247
+ ######################################################################
248
+ # #
249
+ # MAIN TRADE FUNCTIONS #
250
+ # #
251
+ ######################################################################
252
+
253
+ def move(self):
254
+ """
255
+ Function for trader agent to identify optimal move for each step in 4 parts
256
+ 1 - identify all possible moves
257
+ 2 - determine which move maximizes welfare
258
+ 3 - find closest best option
259
+ 4 - move
260
+ """
261
+
262
+ # 1. identify all possible moves
263
+
264
+ neighboring_cells = [
265
+ cell
266
+ for cell in self.cell.get_neighborhood(self.vision, include_center=True)
267
+ if not self.is_occupied_by_other(cell)
268
+ ]
269
+
270
+ # 2. determine which move maximizes welfare
271
+
272
+ welfares = [
273
+ self.calculate_welfare(
274
+ self.sugar + self.get_resource(cell).sugar_amount,
275
+ self.spice + self.get_resource(cell).spice_amount,
276
+ )
277
+ for cell in neighboring_cells
278
+ ]
279
+
280
+ # 3. Find closest best option
281
+
282
+ # find the highest welfare in welfares
283
+ max_welfare = max(welfares)
284
+ # get the index of max welfare cells
285
+ candidate_indices = [
286
+ i for i in range(len(welfares)) if math.isclose(welfares[i], max_welfare)
287
+ ]
288
+
289
+ # convert index to positions of those cells
290
+ candidates = [neighboring_cells[i] for i in candidate_indices]
291
+
292
+ min_dist = min(get_distance(self.cell, cell) for cell in candidates)
293
+
294
+ final_candidates = [
295
+ cell
296
+ for cell in candidates
297
+ if math.isclose(get_distance(self.cell, cell), min_dist, rel_tol=1e-02)
298
+ ]
299
+ # 4. Move Agent
300
+ self.cell = self.random.choice(final_candidates)
301
+
302
+ 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
307
+ self.sugar -= self.metabolism_sugar
308
+
309
+ if patch.spice_amount > 0:
310
+ self.spice += patch.spice_amount
311
+ patch.spice_amount = 0
312
+ self.spice -= self.metabolism_spice
313
+
314
+ def maybe_die(self):
315
+ """
316
+ Function to remove Traders who have consumed all their sugar or spice
317
+ """
318
+
319
+ if self.is_starved():
320
+ self.remove()
321
+
322
+ def trade_with_neighbors(self):
323
+ """
324
+ Function for trader agents to decide who to trade with in three parts
325
+
326
+ 1- identify neighbors who can trade
327
+ 2- trade (2 sessions)
328
+ 3- collect data
329
+ """
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:
342
+ self.trade(a)
343
+
344
+ return
@@ -0,0 +1,70 @@
1
+ import os.path
2
+ import sys
3
+
4
+ sys.path.insert(
5
+ 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../"))
6
+ )
7
+
8
+
9
+ import numpy as np
10
+ import solara
11
+ from matplotlib.figure import Figure
12
+ from sugarscape_g1mt.model import SugarscapeG1mt
13
+ from sugarscape_g1mt.trader_agents import Trader
14
+
15
+ from mesa.visualization import SolaraViz, make_plot_measure
16
+
17
+
18
+ def SpaceDrawer(model):
19
+ def portray(g):
20
+ layers = {
21
+ "sugar": [[np.nan for j in range(g.height)] for i in range(g.width)],
22
+ "spice": [[np.nan for j in range(g.height)] for i in range(g.width)],
23
+ "trader": {"x": [], "y": [], "c": "tab:red", "marker": "o", "s": 10},
24
+ }
25
+
26
+ for agent in g.all_cells.agents:
27
+ i, j = agent.cell.coordinate
28
+ if isinstance(agent, Trader):
29
+ layers["trader"]["x"].append(i)
30
+ layers["trader"]["y"].append(j)
31
+ else:
32
+ # Don't visualize resource with value <= 1.
33
+ layers["sugar"][i][j] = (
34
+ agent.sugar_amount if agent.sugar_amount > 1 else np.nan
35
+ )
36
+ layers["spice"][i][j] = (
37
+ agent.spice_amount if agent.spice_amount > 1 else np.nan
38
+ )
39
+ return layers
40
+
41
+ fig = Figure()
42
+ ax = fig.subplots()
43
+ out = portray(model.grid)
44
+ # Sugar
45
+ # Important note: imshow by default draws from upper left. You have to
46
+ # always explicitly specify origin="lower".
47
+ im = ax.imshow(out["sugar"], cmap="spring", origin="lower")
48
+ fig.colorbar(im, orientation="vertical")
49
+ # Spice
50
+ ax.imshow(out["spice"], cmap="winter", origin="lower")
51
+ # Trader
52
+ ax.scatter(**out["trader"])
53
+ ax.set_axis_off()
54
+ return solara.FigureMatplotlib(fig)
55
+
56
+
57
+ model_params = {
58
+ "width": 50,
59
+ "height": 50,
60
+ }
61
+
62
+ model1 = SugarscapeG1mt(50, 50)
63
+
64
+ page = SolaraViz(
65
+ model1,
66
+ components=[SpaceDrawer, make_plot_measure(["Trader", "Price"])],
67
+ name="Sugarscape {G1, M, T}",
68
+ play_interval=1500,
69
+ )
70
+ page # noqa
@@ -0,0 +1,180 @@
1
+ from pathlib import Path
2
+
3
+ import numpy as np
4
+
5
+ import mesa
6
+ from mesa.examples.advanced.sugarscape_g1mt.agents import Resource, Trader
7
+ from mesa.experimental.cell_space import OrthogonalVonNeumannGrid
8
+
9
+
10
+ # Helper Functions
11
+ def flatten(list_of_lists):
12
+ """
13
+ helper function for model datacollector for trade price
14
+ collapses agent price list into one list
15
+ """
16
+ return [item for sublist in list_of_lists for item in sublist]
17
+
18
+
19
+ def geometric_mean(list_of_prices):
20
+ """
21
+ find the geometric mean of a list of prices
22
+ """
23
+ return np.exp(np.log(list_of_prices).mean())
24
+
25
+
26
+ def get_trade(agent):
27
+ """
28
+ For agent reporters in data collector
29
+
30
+ return list of trade partners and None for other agents
31
+ """
32
+ if isinstance(agent, Trader):
33
+ return agent.trade_partners
34
+ else:
35
+ return None
36
+
37
+
38
+ class SugarscapeG1mt(mesa.Model):
39
+ """
40
+ Manager class to run Sugarscape with Traders
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ width=50,
46
+ height=50,
47
+ initial_population=200,
48
+ endowment_min=25,
49
+ endowment_max=50,
50
+ metabolism_min=1,
51
+ metabolism_max=5,
52
+ vision_min=1,
53
+ vision_max=5,
54
+ enable_trade=True,
55
+ seed=None,
56
+ ):
57
+ super().__init__(seed=seed)
58
+ # Initiate width and height of sugarscape
59
+ self.width = width
60
+ self.height = height
61
+ # 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
+ self.enable_trade = enable_trade
70
+ self.running = True
71
+
72
+ # initiate mesa grid class
73
+ self.grid = OrthogonalVonNeumannGrid((self.width, self.height), torus=False)
74
+ # initiate datacollector
75
+ self.datacollector = mesa.DataCollector(
76
+ model_reporters={
77
+ "Trader": lambda m: len(m.agents_by_type[Trader]),
78
+ "Trade Volume": lambda m: sum(
79
+ len(a.trade_partners) for a in m.agents_by_type[Trader]
80
+ ),
81
+ "Price": lambda m: geometric_mean(
82
+ flatten([a.prices for a in m.agents_by_type[Trader]])
83
+ ),
84
+ },
85
+ agent_reporters={"Trade Network": lambda a: get_trade(a)},
86
+ )
87
+
88
+ # read in landscape file from supplmentary material
89
+ sugar_distribution = np.genfromtxt(Path(__file__).parent / "sugar-map.txt")
90
+ spice_distribution = np.flip(sugar_distribution, 1)
91
+
92
+ for cell in self.grid.all_cells:
93
+ max_sugar = sugar_distribution[cell.coordinate]
94
+ max_spice = spice_distribution[cell.coordinate]
95
+ Resource(self, max_sugar, max_spice, cell)
96
+
97
+ for _ in range(self.initial_population):
98
+ # get agent position
99
+ x = self.random.randrange(self.width)
100
+ y = self.random.randrange(self.height)
101
+ # see Growing Artificial Societies p. 108 for initialization
102
+ # give agents initial endowment
103
+ sugar = int(self.random.uniform(self.endowment_min, self.endowment_max + 1))
104
+ spice = int(self.random.uniform(self.endowment_min, self.endowment_max + 1))
105
+ # give agents initial metabolism
106
+ metabolism_sugar = int(
107
+ self.random.uniform(self.metabolism_min, self.metabolism_max + 1)
108
+ )
109
+ metabolism_spice = int(
110
+ self.random.uniform(self.metabolism_min, self.metabolism_max + 1)
111
+ )
112
+ # give agents vision
113
+ vision = int(self.random.uniform(self.vision_min, self.vision_max + 1))
114
+
115
+ cell = self.grid[(x, y)]
116
+ # create Trader object
117
+ Trader(
118
+ self,
119
+ cell,
120
+ sugar=sugar,
121
+ spice=spice,
122
+ metabolism_sugar=metabolism_sugar,
123
+ metabolism_spice=metabolism_spice,
124
+ vision=vision,
125
+ )
126
+
127
+ def step(self):
128
+ """
129
+ Unique step function that does staged activation of sugar and spice
130
+ and then randomly activates traders
131
+ """
132
+ # step Resource agents
133
+ self.agents_by_type[Resource].do("step")
134
+
135
+ # step trader agents
136
+ # to account for agent death and removal we need a separate data structure to
137
+ # iterate
138
+ trader_shuffle = self.agents_by_type[Trader].shuffle()
139
+
140
+ for agent in trader_shuffle:
141
+ agent.prices = []
142
+ agent.trade_partners = []
143
+ agent.move()
144
+ agent.eat()
145
+ agent.maybe_die()
146
+
147
+ if not self.enable_trade:
148
+ # If trade is not enabled, return early
149
+ self.datacollector.collect(self)
150
+ return
151
+
152
+ trader_shuffle = self.agents_by_type[Trader].shuffle()
153
+
154
+ for agent in trader_shuffle:
155
+ agent.trade_with_neighbors()
156
+
157
+ # collect model level data
158
+ self.datacollector.collect(self)
159
+ """
160
+ Mesa is working on updating datacollector agent reporter
161
+ so it can collect information on specific agents from
162
+ mesa.time.RandomActivationByType.
163
+
164
+ Please see issue #1419 at
165
+ https://github.com/projectmesa/mesa/issues/1419
166
+ (contributions welcome)
167
+
168
+ Below is one way to update agent_records to get specific Trader agent data
169
+ """
170
+ # Need to remove excess data
171
+ # Create local variable to store trade data
172
+ agent_trades = self.datacollector._agent_records[self.steps]
173
+ # Get rid of all None to reduce data storage needs
174
+ agent_trades = [agent for agent in agent_trades if agent[2] is not None]
175
+ # Reassign the dictionary value with lean trade data
176
+ self.datacollector._agent_records[self.steps] = agent_trades
177
+
178
+ def run_model(self, step_count=1000):
179
+ for _ in range(step_count):
180
+ self.step()
@@ -0,0 +1,50 @@
1
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 2 2 2 2 2 2 2 2
2
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2
3
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2
4
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2
5
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2
6
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 3 3 3 3 3 3 3 2 2
7
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 3 3 3 3 3 3 2
8
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 2
9
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3
10
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3
11
+ 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3
12
+ 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3
13
+ 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3
14
+ 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3
15
+ 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 2
16
+ 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 3 3 3 3 3 3 2
17
+ 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 4 4 4 4 3 3 3 3 3 3 3 2 2
18
+ 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2
19
+ 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2
20
+ 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2
21
+ 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2
22
+ 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2
23
+ 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1
24
+ 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1
25
+ 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1
26
+ 1 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1
27
+ 1 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1
28
+ 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1
29
+ 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1
30
+ 2 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1
31
+ 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 0 0 0
32
+ 2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0
33
+ 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0
34
+ 2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0
35
+ 2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0
36
+ 2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0
37
+ 2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 2 2 2 2 2 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
38
+ 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 2 2 2 2 2 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0
39
+ 2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0
40
+ 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 3 3 3 3 3 3 2 2 2 2 2 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0
41
+ 2 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 3 3 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0
42
+ 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0
43
+ 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0
44
+ 1 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0
45
+ 1 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0
46
+ 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0
47
+ 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
48
+ 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
49
+ 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
50
+ 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0