Mesa 2.4.0__py3-none-any.whl → 3.0.0__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 (110) hide show
  1. mesa/__init__.py +3 -5
  2. mesa/agent.py +105 -92
  3. mesa/batchrunner.py +55 -31
  4. mesa/datacollection.py +10 -14
  5. mesa/examples/README.md +37 -0
  6. mesa/examples/__init__.py +21 -0
  7. mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +116 -0
  8. mesa/examples/advanced/epstein_civil_violence/Readme.md +34 -0
  9. mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
  10. mesa/examples/advanced/epstein_civil_violence/agents.py +164 -0
  11. mesa/examples/advanced/epstein_civil_violence/app.py +73 -0
  12. mesa/examples/advanced/epstein_civil_violence/model.py +114 -0
  13. mesa/examples/advanced/pd_grid/Readme.md +43 -0
  14. mesa/examples/advanced/pd_grid/__init__.py +0 -0
  15. mesa/examples/advanced/pd_grid/agents.py +50 -0
  16. mesa/examples/advanced/pd_grid/analysis.ipynb +228 -0
  17. mesa/examples/advanced/pd_grid/app.py +54 -0
  18. mesa/examples/advanced/pd_grid/model.py +71 -0
  19. mesa/examples/advanced/sugarscape_g1mt/Readme.md +64 -0
  20. mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
  21. mesa/examples/advanced/sugarscape_g1mt/agents.py +344 -0
  22. mesa/examples/advanced/sugarscape_g1mt/app.py +62 -0
  23. mesa/examples/advanced/sugarscape_g1mt/model.py +180 -0
  24. mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +50 -0
  25. mesa/examples/advanced/sugarscape_g1mt/tests.py +69 -0
  26. mesa/examples/advanced/wolf_sheep/Readme.md +57 -0
  27. mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
  28. mesa/examples/advanced/wolf_sheep/agents.py +102 -0
  29. mesa/examples/advanced/wolf_sheep/app.py +84 -0
  30. mesa/examples/advanced/wolf_sheep/model.py +137 -0
  31. mesa/examples/basic/__init__.py +0 -0
  32. mesa/examples/basic/boid_flockers/Readme.md +22 -0
  33. mesa/examples/basic/boid_flockers/__init__.py +0 -0
  34. mesa/examples/basic/boid_flockers/agents.py +71 -0
  35. mesa/examples/basic/boid_flockers/app.py +58 -0
  36. mesa/examples/basic/boid_flockers/model.py +69 -0
  37. mesa/examples/basic/boltzmann_wealth_model/Readme.md +56 -0
  38. mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
  39. mesa/examples/basic/boltzmann_wealth_model/agents.py +31 -0
  40. mesa/examples/basic/boltzmann_wealth_model/app.py +74 -0
  41. mesa/examples/basic/boltzmann_wealth_model/model.py +43 -0
  42. mesa/examples/basic/boltzmann_wealth_model/st_app.py +115 -0
  43. mesa/examples/basic/conways_game_of_life/Readme.md +39 -0
  44. mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
  45. mesa/examples/basic/conways_game_of_life/agents.py +47 -0
  46. mesa/examples/basic/conways_game_of_life/app.py +51 -0
  47. mesa/examples/basic/conways_game_of_life/model.py +31 -0
  48. mesa/examples/basic/conways_game_of_life/st_app.py +72 -0
  49. mesa/examples/basic/schelling/Readme.md +40 -0
  50. mesa/examples/basic/schelling/__init__.py +0 -0
  51. mesa/examples/basic/schelling/agents.py +26 -0
  52. mesa/examples/basic/schelling/analysis.ipynb +205 -0
  53. mesa/examples/basic/schelling/app.py +42 -0
  54. mesa/examples/basic/schelling/model.py +59 -0
  55. mesa/examples/basic/virus_on_network/Readme.md +61 -0
  56. mesa/examples/basic/virus_on_network/__init__.py +0 -0
  57. mesa/examples/basic/virus_on_network/agents.py +69 -0
  58. mesa/examples/basic/virus_on_network/app.py +114 -0
  59. mesa/examples/basic/virus_on_network/model.py +96 -0
  60. mesa/experimental/UserParam.py +18 -7
  61. mesa/experimental/__init__.py +10 -2
  62. mesa/experimental/cell_space/__init__.py +16 -1
  63. mesa/experimental/cell_space/cell.py +93 -23
  64. mesa/experimental/cell_space/cell_agent.py +117 -21
  65. mesa/experimental/cell_space/cell_collection.py +56 -19
  66. mesa/experimental/cell_space/discrete_space.py +92 -8
  67. mesa/experimental/cell_space/grid.py +33 -9
  68. mesa/experimental/cell_space/network.py +15 -10
  69. mesa/experimental/cell_space/voronoi.py +257 -0
  70. mesa/experimental/components/altair.py +11 -2
  71. mesa/experimental/components/matplotlib.py +132 -26
  72. mesa/experimental/devs/__init__.py +2 -0
  73. mesa/experimental/devs/eventlist.py +54 -15
  74. mesa/experimental/devs/examples/epstein_civil_violence.py +69 -38
  75. mesa/experimental/devs/examples/wolf_sheep.py +42 -43
  76. mesa/experimental/devs/simulator.py +57 -16
  77. mesa/experimental/{jupyter_viz.py → solara_viz.py} +151 -99
  78. mesa/model.py +136 -78
  79. mesa/space.py +208 -148
  80. mesa/time.py +63 -80
  81. mesa/visualization/__init__.py +25 -6
  82. mesa/visualization/components/__init__.py +83 -0
  83. mesa/visualization/components/altair_components.py +188 -0
  84. mesa/visualization/components/matplotlib_components.py +175 -0
  85. mesa/visualization/mpl_space_drawing.py +593 -0
  86. mesa/visualization/solara_viz.py +458 -0
  87. mesa/visualization/user_param.py +69 -0
  88. mesa/visualization/utils.py +9 -0
  89. {mesa-2.4.0.dist-info → mesa-3.0.0.dist-info}/METADATA +62 -17
  90. mesa-3.0.0.dist-info/RECORD +95 -0
  91. mesa-3.0.0.dist-info/licenses/LICENSE +202 -0
  92. mesa-2.4.0.dist-info/licenses/LICENSE → mesa-3.0.0.dist-info/licenses/NOTICE +2 -2
  93. mesa/cookiecutter-mesa/cookiecutter.json +0 -8
  94. mesa/cookiecutter-mesa/hooks/post_gen_project.py +0 -11
  95. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +0 -4
  96. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/run.pytemplate +0 -3
  97. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +0 -11
  98. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +0 -60
  99. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/server.pytemplate +0 -36
  100. mesa/flat/__init__.py +0 -6
  101. mesa/flat/visualization.py +0 -5
  102. mesa/main.py +0 -63
  103. mesa/visualization/ModularVisualization.py +0 -1
  104. mesa/visualization/TextVisualization.py +0 -1
  105. mesa/visualization/UserParam.py +0 -1
  106. mesa/visualization/modules.py +0 -1
  107. mesa-2.4.0.dist-info/RECORD +0 -45
  108. /mesa/{cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}} → examples/advanced}/__init__.py +0 -0
  109. {mesa-2.4.0.dist-info → mesa-3.0.0.dist-info}/WHEEL +0 -0
  110. {mesa-2.4.0.dist-info → mesa-3.0.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,54 @@
1
+ """
2
+ Solara-based visualization for the Spatial Prisoner's Dilemma Model.
3
+ """
4
+
5
+ from mesa.examples.advanced.pd_grid.model import PdGrid
6
+ from mesa.visualization import (
7
+ Slider,
8
+ SolaraViz,
9
+ make_plot_component,
10
+ make_space_component,
11
+ )
12
+
13
+
14
+ def pd_agent_portrayal(agent):
15
+ """
16
+ Portrayal function for rendering PD agents in the visualization.
17
+ """
18
+ return {
19
+ "color": "blue" if agent.move == "C" else "red",
20
+ "marker": "s", # square marker
21
+ "size": 25,
22
+ }
23
+
24
+
25
+ # Model parameters
26
+ model_params = {
27
+ "width": Slider("Grid Width", value=50, min=10, max=100, step=1),
28
+ "height": Slider("Grid Height", value=50, min=10, max=100, step=1),
29
+ "activation_order": {
30
+ "type": "Select",
31
+ "value": "Random",
32
+ "values": PdGrid.activation_regimes,
33
+ "label": "Activation Regime",
34
+ },
35
+ }
36
+
37
+
38
+ # Create grid visualization component using Altair
39
+ grid_viz = make_space_component(agent_portrayal=pd_agent_portrayal)
40
+
41
+ # Create plot for tracking cooperating agents over time
42
+ plot_component = make_plot_component("Cooperating_Agents")
43
+
44
+ # Initialize model
45
+ initial_model = PdGrid()
46
+
47
+ # Create visualization with all components
48
+ page = SolaraViz(
49
+ model=initial_model,
50
+ components=[grid_viz, plot_component],
51
+ model_params=model_params,
52
+ name="Spatial Prisoner's Dilemma",
53
+ )
54
+ page # noqa B018
@@ -0,0 +1,71 @@
1
+ import mesa
2
+ from mesa.examples.advanced.pd_grid.agents import PDAgent
3
+ from mesa.experimental.cell_space import OrthogonalMooreGrid
4
+
5
+
6
+ class PdGrid(mesa.Model):
7
+ """Model class for iterated, spatial prisoner's dilemma model."""
8
+
9
+ activation_regimes = ["Sequential", "Random", "Simultaneous"]
10
+
11
+ # This dictionary holds the payoff for this agent,
12
+ # keyed on: (my_move, other_move)
13
+
14
+ payoff = {("C", "C"): 1, ("C", "D"): 0, ("D", "C"): 1.6, ("D", "D"): 0}
15
+
16
+ def __init__(
17
+ self, width=50, height=50, activation_order="Random", payoffs=None, seed=None
18
+ ):
19
+ """
20
+ Create a new Spatial Prisoners' Dilemma Model.
21
+
22
+ Args:
23
+ width, height: Grid size. There will be one agent per grid cell.
24
+ activation_order: Can be "Sequential", "Random", or "Simultaneous".
25
+ Determines the agent activation regime.
26
+ payoffs: (optional) Dictionary of (move, neighbor_move) payoffs.
27
+ """
28
+ super().__init__(seed=seed)
29
+ self.activation_order = activation_order
30
+ self.grid = OrthogonalMooreGrid((width, height), torus=True)
31
+
32
+ if payoffs is not None:
33
+ self.payoff = payoffs
34
+
35
+ # Create agents
36
+ for x in range(width):
37
+ for y in range(height):
38
+ agent = PDAgent(self)
39
+ agent.cell = self.grid[(x, y)]
40
+
41
+ self.datacollector = mesa.DataCollector(
42
+ {
43
+ "Cooperating_Agents": lambda m: len(
44
+ [a for a in m.agents if a.move == "C"]
45
+ )
46
+ }
47
+ )
48
+
49
+ self.running = True
50
+ self.datacollector.collect(self)
51
+
52
+ def step(self):
53
+ # Activate all agents, based on the activation regime
54
+ match self.activation_order:
55
+ case "Sequential":
56
+ self.agents.do("step")
57
+ case "Random":
58
+ self.agents.shuffle_do("step")
59
+ case "Simultaneous":
60
+ self.agents.do("step")
61
+ self.agents.do("advance")
62
+ case _:
63
+ raise ValueError(f"Unknown activation order: {self.activation_order}")
64
+
65
+ # Collect data
66
+ self.datacollector.collect(self)
67
+
68
+ def run(self, n):
69
+ """Run the model for n steps."""
70
+ for _ in range(n):
71
+ self.step()
@@ -0,0 +1,64 @@
1
+ # Sugarscape Constant Growback Model with Traders
2
+
3
+ ## Summary
4
+
5
+ This is Epstein & Axtell's Sugarscape model with Traders, a detailed description is in Chapter four of
6
+ *Growing Artificial Societies: Social Science from the Bottom Up (1996)*. The model shows an emergent price equilibrium can happen via a decentralized dynamics.
7
+
8
+ This code generally matches the code in the Complexity Explorer Tutorial, but in `.py` instead of `.ipynb` format.
9
+
10
+ ### Agents:
11
+
12
+ - **Resource**: Resource agents grow back at one unit of sugar and spice per time step up to a specified max amount and can be harvested and traded by the trader agents.
13
+ (if you do the interactive run, the color will be green if the resource agent has a bigger amount of sugar, or yellow if it has a bigger amount of spice)
14
+ - **Traders**: Trader agents have the following attributes: (1) metabolism for sugar, (2) metabolism for spice, (3) vision,
15
+ (4) initial sugar endowment and (5) initial spice endowment. The traverse the landscape harvesting sugar and spice and
16
+ trading with other agents. If they run out of sugar or spice then they are removed from the model. (red circle if you do the interactive run)
17
+
18
+ The trader agents traverse the landscape according to rule **M**:
19
+ - Look out as far as vision permits in the four principal lattice directions and identify the unoccupied site(s).
20
+ - Considering only unoccupied sites find the nearest position that produces the most welfare using the Cobb-Douglas function.
21
+ - Move to the new position
22
+ - Collect all the resources (sugar and spice) at that location
23
+ (Epstein and Axtell, 1996, p. 99)
24
+
25
+ The traders trade according to rule **T**:
26
+ - Agents and potential trade partner compute their marginal rates of substitution (MRS), if they are equal *end*.
27
+ - Exchange resources, with spice flowing from the agent with the higher MRS to the agent with the lower MRS and sugar
28
+ flowing the opposite direction.
29
+ - The price (p) is calculated by taking the geometric mean of the agents' MRS.
30
+ - If p > 1 then p units of spice are traded for 1 unit of sugar; if p < 1 then 1/p units of sugar for 1 unit of spice
31
+ - The trade occurs if it will (a) make both agent better off (increases MRS) and (b) does not cause the agents' MRS to
32
+ cross over one another otherwise *end*.
33
+ - This process then repeats until an *end* condition is met.
34
+ (Epstein and Axtell, 1996, p. 105)
35
+
36
+ The model demonstrates several Mesa concepts and features:
37
+ - OrthogonalMooreGrid
38
+ - Multiple agent types (traders, sugar, spice)
39
+ - Dynamically removing agents from the grid and schedule when they die
40
+ - Data Collection at the model and agent level
41
+ - custom solara matplotlib space visualization
42
+
43
+
44
+ ## How to Run
45
+ To run the model interactively:
46
+
47
+ ```
48
+ $ solara run app.py
49
+ ```
50
+
51
+ Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run.
52
+
53
+ ## Files
54
+
55
+ * `model.py`: The Sugarscape Constant Growback with Traders model.
56
+ * `agents.py`: Defines the Trader agent class and the Resource agent class which contains an amount of sugar and spice.
57
+ * `app.py`: Runs a visualization server via Solara (`solara run app.py`).
58
+ * `sugar_map.txt`: Provides sugar and spice landscape in raster type format.
59
+ * `tests.py`: Has tests to ensure that the model reproduces the results in shown in Growing Artificial Societies.
60
+
61
+ ## Additional Resources
62
+
63
+ - [Growing Artificial Societies](https://mitpress.mit.edu/9780262550253/growing-artificial-societies/)
64
+ - [Complexity Explorer Sugarscape with Traders Tutorial](https://www.complexityexplorer.org/courses/172-agent-based-models-with-python-an-introduction-to-mesa)
File without changes
@@ -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,62 @@
1
+ import numpy as np
2
+ import solara
3
+ from matplotlib.figure import Figure
4
+
5
+ from mesa.examples.advanced.sugarscape_g1mt.agents import Trader
6
+ from mesa.examples.advanced.sugarscape_g1mt.model import SugarscapeG1mt
7
+ from mesa.visualization import SolaraViz, make_plot_component
8
+
9
+
10
+ def SpaceDrawer(model):
11
+ def portray(g):
12
+ 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
+ "trader": {"x": [], "y": [], "c": "tab:red", "marker": "o", "s": 10},
16
+ }
17
+
18
+ for agent in g.all_cells.agents:
19
+ 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
+ )
31
+ return layers
32
+
33
+ fig = Figure()
34
+ ax = fig.subplots()
35
+ out = portray(model.grid)
36
+ # Sugar
37
+ # Important note: imshow by default draws from upper left. You have to
38
+ # always explicitly specify origin="lower".
39
+ im = ax.imshow(out["sugar"], cmap="spring", origin="lower")
40
+ fig.colorbar(im, orientation="vertical")
41
+ # Spice
42
+ ax.imshow(out["spice"], cmap="winter", origin="lower")
43
+ # Trader
44
+ ax.scatter(**out["trader"])
45
+ ax.set_axis_off()
46
+ return solara.FigureMatplotlib(fig)
47
+
48
+
49
+ model_params = {
50
+ "width": 50,
51
+ "height": 50,
52
+ }
53
+
54
+ model1 = SugarscapeG1mt(50, 50)
55
+
56
+ page = SolaraViz(
57
+ model1,
58
+ components=[SpaceDrawer, make_plot_component(["Trader", "Price"])],
59
+ name="Sugarscape {G1, M, T}",
60
+ play_interval=150,
61
+ )
62
+ page # noqa