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,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
@@ -0,0 +1,69 @@
1
+ import numpy as np
2
+ from scipy import stats
3
+
4
+ from .agents import Trader
5
+ from .model import SugarscapeG1mt, flatten
6
+
7
+
8
+ def check_slope(y, increasing):
9
+ x = range(len(y))
10
+ slope, intercept, _, p_value, _ = stats.linregress(x, y)
11
+ result = (slope > 0) if increasing else (slope < 0)
12
+ # p_value for significance.
13
+ assert result and p_value < 0.05, (slope, p_value)
14
+
15
+
16
+ def test_decreasing_price_variance():
17
+ # The variance of the average trade price should decrease over time (figure IV-3)
18
+ # See Growing Artificial Societies p. 109.
19
+ model = SugarscapeG1mt(42)
20
+ model.datacollector._new_model_reporter(
21
+ "price_variance",
22
+ lambda m: np.var(
23
+ flatten([a.prices for a in m.agents_by_type[Trader].values()])
24
+ ),
25
+ )
26
+ model.run_model(step_count=50)
27
+
28
+ df_model = model.datacollector.get_model_vars_dataframe()
29
+
30
+ check_slope(df_model.price_variance, increasing=False)
31
+
32
+
33
+ def test_carrying_capacity():
34
+ def calculate_carrying_capacities(enable_trade):
35
+ carrying_capacities = []
36
+ visions = range(1, 10)
37
+ for vision_max in visions:
38
+ model = SugarscapeG1mt(vision_max=vision_max, enable_trade=enable_trade)
39
+ model.run_model(step_count=50)
40
+ carrying_capacities.append(len(model.agents_by_type[Trader]))
41
+ return carrying_capacities
42
+
43
+ # Carrying capacity should increase over mean vision (figure IV-6).
44
+ # See Growing Artificial Societies p. 112.
45
+ carrying_capacities_with_trade = calculate_carrying_capacities(True)
46
+ check_slope(
47
+ carrying_capacities_with_trade,
48
+ increasing=True,
49
+ )
50
+ # Carrying capacity should be higher when trade is enabled (figure IV-6).
51
+ carrying_capacities_no_trade = calculate_carrying_capacities(False)
52
+ check_slope(
53
+ carrying_capacities_no_trade,
54
+ increasing=True,
55
+ )
56
+
57
+ t_statistic, p_value = stats.ttest_rel(
58
+ carrying_capacities_with_trade, carrying_capacities_no_trade
59
+ )
60
+ # t_statistic > 0 means carrying_capacities_with_trade has larger values
61
+ # than carrying_capacities_no_trade.
62
+ # p_value for significance.
63
+ assert t_statistic > 0 and p_value < 0.05
64
+
65
+
66
+ # TODO:
67
+ # 1. Reproduce figure IV-12 that the log of average price should decrease over average agent age
68
+ # 2. Reproduce figure IV-13 that the gini coefficient on trade should decrease over mean vision, and should be higher with trade
69
+ # 3. a stricter test would be to ensure the amount of variance of the trade price matches figure IV-3
@@ -0,0 +1,57 @@
1
+ # Wolf-Sheep Predation Model
2
+
3
+ ## Summary
4
+
5
+ A simple ecological model, consisting of three agent types: wolves, sheep, and grass. The wolves and the sheep wander around the grid at random. Wolves and sheep both expend energy moving around, and replenish it by eating. Sheep eat grass, and wolves eat sheep if they end up on the same grid cell.
6
+
7
+ If wolves and sheep have enough energy, they reproduce, creating a new wolf or sheep (in this simplified model, only one parent is needed for reproduction). The grass on each cell regrows at a constant rate. If any wolves and sheep run out of energy, they die.
8
+
9
+ The model is tests and demonstrates several Mesa concepts and features:
10
+ - MultiGrid
11
+ - Multiple agent types (wolves, sheep, grass)
12
+ - Overlay arbitrary text (wolf's energy) on agent's shapes while drawing on CanvasGrid
13
+ - Agents inheriting a behavior (random movement) from an abstract parent
14
+ - Writing a model composed of multiple files.
15
+ - Dynamically adding and removing agents from the schedule
16
+
17
+ ## Installation
18
+
19
+ To install the dependencies use pip and the requirements.txt in this directory. e.g.
20
+
21
+ ```
22
+ # First, we clone the Mesa repo
23
+ $ git clone https://github.com/projectmesa/mesa.git
24
+ $ cd mesa
25
+ # Then we cd to the example directory
26
+ $ cd examples/wolf_sheep
27
+ $ pip install -r requirements.txt
28
+ ```
29
+
30
+ ## How to Run
31
+
32
+ To run the model interactively, run ``mesa runserver`` in this directory. e.g.
33
+
34
+ ```
35
+ $ mesa runserver
36
+ ```
37
+
38
+ Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run.
39
+
40
+ ## Files
41
+
42
+ * ``wolf_sheep/random_walk.py``: This defines the ``RandomWalker`` agent, which implements the behavior of moving randomly across a grid, one cell at a time. Both the Wolf and Sheep agents will inherit from it.
43
+ * ``wolf_sheep/test_random_walk.py``: Defines a simple model and a text-only visualization intended to make sure the RandomWalk class was working as expected. This doesn't actually model anything, but serves as an ad-hoc unit test. To run it, ``cd`` into the ``wolf_sheep`` directory and run ``python test_random_walk.py``. You'll see a series of ASCII grids, one per model step, with each cell showing a count of the number of agents in it.
44
+ * ``wolf_sheep/agents.py``: Defines the Wolf, Sheep, and GrassPatch agent classes.
45
+ * ``wolf_sheep/scheduler.py``: Defines a custom variant on the RandomActivationByType scheduler, where we can define filters for the `get_type_count` function.
46
+ * ``wolf_sheep/model.py``: Defines the Wolf-Sheep Predation model itself
47
+ * ``wolf_sheep/server.py``: Sets up the interactive visualization server
48
+ * ``run.py``: Launches a model visualization server.
49
+
50
+ ## Further Reading
51
+
52
+ This model is closely based on the NetLogo Wolf-Sheep Predation Model:
53
+
54
+ Wilensky, U. (1997). NetLogo Wolf Sheep Predation model. http://ccl.northwestern.edu/netlogo/models/WolfSheepPredation. Center for Connected Learning and Computer-Based Modeling, Northwestern University, Evanston, IL.
55
+
56
+ See also the [Lotka–Volterra equations
57
+ ](https://en.wikipedia.org/wiki/Lotka%E2%80%93Volterra_equations) for an example of a classic differential-equation model with similar dynamics.
File without changes
@@ -0,0 +1,102 @@
1
+ from mesa.experimental.cell_space import CellAgent, FixedAgent
2
+
3
+
4
+ class Animal(CellAgent):
5
+ """The base animal class."""
6
+
7
+ def __init__(self, model, energy, p_reproduce, energy_from_food, cell):
8
+ """Initializes an animal.
9
+
10
+ Args:
11
+ model: a model instance
12
+ energy: starting amount of energy
13
+ p_reproduce: probability of sexless reproduction
14
+ energy_from_food: energy obtained from 1 unit of food
15
+ cell: the cell in which the animal starts
16
+ """
17
+ super().__init__(model)
18
+ self.energy = energy
19
+ self.p_reproduce = p_reproduce
20
+ self.energy_from_food = energy_from_food
21
+ self.cell = cell
22
+
23
+ def spawn_offspring(self):
24
+ """Create offspring."""
25
+ self.energy /= 2
26
+ self.__class__(
27
+ self.model,
28
+ self.energy,
29
+ self.p_reproduce,
30
+ self.energy_from_food,
31
+ self.cell,
32
+ )
33
+
34
+ def feed(self): ...
35
+
36
+ def step(self):
37
+ """One step of the agent."""
38
+ self.cell = self.cell.neighborhood.select_random_cell()
39
+ self.energy -= 1
40
+
41
+ self.feed()
42
+
43
+ if self.energy < 0:
44
+ self.remove()
45
+ elif self.random.random() < self.p_reproduce:
46
+ self.spawn_offspring()
47
+
48
+
49
+ class Sheep(Animal):
50
+ """A sheep that walks around, reproduces (asexually) and gets eaten."""
51
+
52
+ def feed(self):
53
+ """If possible eat the food in the current location."""
54
+ # If there is grass available, eat it
55
+ if self.model.grass:
56
+ grass_patch = next(
57
+ obj for obj in self.cell.agents if isinstance(obj, GrassPatch)
58
+ )
59
+ if grass_patch.fully_grown:
60
+ self.energy += self.energy_from_food
61
+ grass_patch.fully_grown = False
62
+
63
+
64
+ class Wolf(Animal):
65
+ """A wolf that walks around, reproduces (asexually) and eats sheep."""
66
+
67
+ def feed(self):
68
+ """If possible eat the food in the current location."""
69
+ sheep = [obj for obj in self.cell.agents if isinstance(obj, Sheep)]
70
+ if len(sheep) > 0:
71
+ sheep_to_eat = self.random.choice(sheep)
72
+ self.energy += self.energy_from_food
73
+
74
+ # Kill the sheep
75
+ sheep_to_eat.remove()
76
+
77
+
78
+ class GrassPatch(FixedAgent):
79
+ """
80
+ A patch of grass that grows at a fixed rate and it is eaten by sheep
81
+ """
82
+
83
+ def __init__(self, model, fully_grown, countdown):
84
+ """
85
+ Creates a new patch of grass
86
+
87
+ Args:
88
+ grown: (boolean) Whether the patch of grass is fully grown or not
89
+ countdown: Time for the patch of grass to be fully grown again
90
+ """
91
+ super().__init__(model)
92
+ self.fully_grown = fully_grown
93
+ self.countdown = countdown
94
+
95
+ def step(self):
96
+ if not self.fully_grown:
97
+ if self.countdown <= 0:
98
+ # Set as fully grown
99
+ self.fully_grown = True
100
+ self.countdown = self.model.grass_regrowth_time
101
+ else:
102
+ self.countdown -= 1
@@ -0,0 +1,84 @@
1
+ from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf
2
+ from mesa.examples.advanced.wolf_sheep.model import WolfSheep
3
+ from mesa.visualization import (
4
+ Slider,
5
+ SolaraViz,
6
+ make_plot_component,
7
+ make_space_component,
8
+ )
9
+
10
+
11
+ def wolf_sheep_portrayal(agent):
12
+ if agent is None:
13
+ return
14
+
15
+ portrayal = {
16
+ "size": 25,
17
+ }
18
+
19
+ if isinstance(agent, Wolf):
20
+ portrayal["color"] = "tab:red"
21
+ portrayal["marker"] = "o"
22
+ portrayal["zorder"] = 2
23
+ elif isinstance(agent, Sheep):
24
+ portrayal["color"] = "tab:cyan"
25
+ portrayal["marker"] = "o"
26
+ portrayal["zorder"] = 2
27
+ elif isinstance(agent, GrassPatch):
28
+ if agent.fully_grown:
29
+ portrayal["color"] = "tab:green"
30
+ else:
31
+ portrayal["color"] = "tab:brown"
32
+ portrayal["marker"] = "s"
33
+ portrayal["size"] = 75
34
+
35
+ return portrayal
36
+
37
+
38
+ model_params = {
39
+ # The following line is an example to showcase StaticText.
40
+ "grass": {
41
+ "type": "Select",
42
+ "value": True,
43
+ "values": [True, False],
44
+ "label": "grass regrowth enabled?",
45
+ },
46
+ "grass_regrowth_time": Slider("Grass Regrowth Time", 20, 1, 50),
47
+ "initial_sheep": Slider("Initial Sheep Population", 100, 10, 300),
48
+ "sheep_reproduce": Slider("Sheep Reproduction Rate", 0.04, 0.01, 1.0, 0.01),
49
+ "initial_wolves": Slider("Initial Wolf Population", 10, 5, 100),
50
+ "wolf_reproduce": Slider(
51
+ "Wolf Reproduction Rate",
52
+ 0.05,
53
+ 0.01,
54
+ 1.0,
55
+ 0.01,
56
+ ),
57
+ "wolf_gain_from_food": Slider("Wolf Gain From Food Rate", 20, 1, 50),
58
+ "sheep_gain_from_food": Slider("Sheep Gain From Food", 4, 1, 10),
59
+ }
60
+
61
+
62
+ def post_process(ax):
63
+ ax.set_aspect("equal")
64
+ ax.set_xticks([])
65
+ ax.set_yticks([])
66
+
67
+
68
+ space_component = make_space_component(
69
+ wolf_sheep_portrayal, draw_grid=False, post_process=post_process
70
+ )
71
+ lineplot_component = make_plot_component(
72
+ {"Wolves": "tab:orange", "Sheep": "tab:cyan", "Grass": "tab:green"}
73
+ )
74
+
75
+ model = WolfSheep(grass=True)
76
+
77
+
78
+ page = SolaraViz(
79
+ model,
80
+ components=[space_component, lineplot_component],
81
+ model_params=model_params,
82
+ name="Wolf Sheep",
83
+ )
84
+ page # noqa
@@ -0,0 +1,137 @@
1
+ """
2
+ Wolf-Sheep Predation Model
3
+ ================================
4
+
5
+ Replication of the model found in NetLogo:
6
+ Wilensky, U. (1997). NetLogo Wolf Sheep Predation model.
7
+ http://ccl.northwestern.edu/netlogo/models/WolfSheepPredation.
8
+ Center for Connected Learning and Computer-Based Modeling,
9
+ Northwestern University, Evanston, IL.
10
+ """
11
+
12
+ import mesa
13
+ from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf
14
+ from mesa.experimental.cell_space import OrthogonalMooreGrid
15
+
16
+
17
+ class WolfSheep(mesa.Model):
18
+ """
19
+ Wolf-Sheep Predation Model
20
+ """
21
+
22
+ height = 20
23
+ width = 20
24
+
25
+ initial_sheep = 100
26
+ initial_wolves = 50
27
+
28
+ sheep_reproduce = 0.04
29
+ wolf_reproduce = 0.05
30
+
31
+ wolf_gain_from_food = 20
32
+
33
+ grass = False
34
+ grass_regrowth_time = 30
35
+ sheep_gain_from_food = 4
36
+
37
+ description = (
38
+ "A model for simulating wolf and sheep (predator-prey) ecosystem modelling."
39
+ )
40
+
41
+ def __init__(
42
+ self,
43
+ width=20,
44
+ height=20,
45
+ initial_sheep=100,
46
+ initial_wolves=50,
47
+ sheep_reproduce=0.04,
48
+ wolf_reproduce=0.05,
49
+ wolf_gain_from_food=20,
50
+ grass=False,
51
+ grass_regrowth_time=30,
52
+ sheep_gain_from_food=4,
53
+ seed=None,
54
+ ):
55
+ """
56
+ Create a new Wolf-Sheep model with the given parameters.
57
+
58
+ Args:
59
+ initial_sheep: Number of sheep to start with
60
+ initial_wolves: Number of wolves to start with
61
+ sheep_reproduce: Probability of each sheep reproducing each step
62
+ wolf_reproduce: Probability of each wolf reproducing each step
63
+ wolf_gain_from_food: Energy a wolf gains from eating a sheep
64
+ grass: Whether to have the sheep eat grass for energy
65
+ grass_regrowth_time: How long it takes for a grass patch to regrow
66
+ once it is eaten
67
+ sheep_gain_from_food: Energy sheep gain from grass, if enabled.
68
+ """
69
+ super().__init__(seed=seed)
70
+ # Set parameters
71
+ self.width = width
72
+ self.height = height
73
+ self.initial_sheep = initial_sheep
74
+ self.initial_wolves = initial_wolves
75
+ self.grass = grass
76
+ self.grass_regrowth_time = grass_regrowth_time
77
+
78
+ self.grid = OrthogonalMooreGrid((self.width, self.height), torus=True)
79
+
80
+ collectors = {
81
+ "Wolves": lambda m: len(m.agents_by_type[Wolf]),
82
+ "Sheep": lambda m: len(m.agents_by_type[Sheep]),
83
+ "Grass": lambda m: len(
84
+ m.agents_by_type[GrassPatch].select(lambda a: a.fully_grown)
85
+ )
86
+ if m.grass
87
+ else -1,
88
+ }
89
+
90
+ self.datacollector = mesa.DataCollector(collectors)
91
+
92
+ # Create sheep:
93
+ for _ in range(self.initial_sheep):
94
+ x = self.random.randrange(self.width)
95
+ y = self.random.randrange(self.height)
96
+ energy = self.random.randrange(2 * self.sheep_gain_from_food)
97
+ Sheep(
98
+ self, energy, sheep_reproduce, sheep_gain_from_food, self.grid[(x, y)]
99
+ )
100
+
101
+ # Create wolves
102
+ for _ in range(self.initial_wolves):
103
+ x = self.random.randrange(self.width)
104
+ y = self.random.randrange(self.height)
105
+ energy = self.random.randrange(2 * self.wolf_gain_from_food)
106
+ Wolf(self, energy, wolf_reproduce, wolf_gain_from_food, self.grid[(x, y)])
107
+
108
+ # Create grass patches
109
+ if self.grass:
110
+ for cell in self.grid.all_cells:
111
+ fully_grown = self.random.choice([True, False])
112
+
113
+ if fully_grown:
114
+ countdown = self.grass_regrowth_time
115
+ else:
116
+ countdown = self.random.randrange(self.grass_regrowth_time)
117
+
118
+ patch = GrassPatch(self, fully_grown, countdown)
119
+ patch.cell = cell
120
+
121
+ self.running = True
122
+ self.datacollector.collect(self)
123
+
124
+ def step(self):
125
+ # This replicated the behavior of the old RandomActivationByType scheduler
126
+ # when using step(shuffle_types=True, shuffle_agents=True).
127
+ # Conceptually, it can be argued that this should be modelled differently.
128
+ self.random.shuffle(self.agent_types)
129
+ for agent_type in self.agent_types:
130
+ self.agents_by_type[agent_type].shuffle_do("step")
131
+
132
+ # collect data
133
+ self.datacollector.collect(self)
134
+
135
+ def run_model(self, step_count=200):
136
+ for _ in range(step_count):
137
+ self.step()
File without changes