Mesa 3.0.0__py3-none-any.whl → 3.0.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of Mesa might be problematic. Click here for more details.
- mesa/__init__.py +3 -3
- mesa/agent.py +114 -406
- mesa/batchrunner.py +27 -54
- mesa/cookiecutter-mesa/cookiecutter.json +8 -0
- mesa/cookiecutter-mesa/hooks/post_gen_project.py +11 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +4 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate +27 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +11 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +60 -0
- mesa/datacollection.py +29 -140
- mesa/experimental/__init__.py +1 -11
- mesa/experimental/cell_space/__init__.py +1 -16
- mesa/experimental/cell_space/cell.py +23 -93
- mesa/experimental/cell_space/cell_agent.py +21 -117
- mesa/experimental/cell_space/cell_collection.py +17 -54
- mesa/experimental/cell_space/discrete_space.py +8 -92
- mesa/experimental/cell_space/grid.py +8 -32
- mesa/experimental/cell_space/network.py +7 -12
- mesa/experimental/devs/__init__.py +0 -2
- mesa/experimental/devs/eventlist.py +14 -52
- mesa/experimental/devs/examples/epstein_civil_violence.py +39 -71
- mesa/experimental/devs/examples/wolf_sheep.py +45 -45
- mesa/experimental/devs/simulator.py +15 -55
- mesa/main.py +63 -0
- mesa/model.py +83 -211
- mesa/space.py +149 -215
- mesa/time.py +77 -62
- mesa/{experimental → visualization}/UserParam.py +6 -17
- mesa/visualization/__init__.py +2 -25
- mesa/{experimental → visualization}/components/altair.py +0 -10
- mesa/visualization/components/matplotlib.py +134 -0
- mesa/visualization/solara_viz.py +266 -267
- {mesa-3.0.0.dist-info → mesa-3.0.0a1.dist-info}/METADATA +13 -65
- mesa-3.0.0a1.dist-info/RECORD +38 -0
- mesa-3.0.0.dist-info/licenses/NOTICE → mesa-3.0.0a1.dist-info/licenses/LICENSE +2 -2
- mesa/examples/README.md +0 -37
- mesa/examples/__init__.py +0 -21
- mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +0 -116
- mesa/examples/advanced/epstein_civil_violence/Readme.md +0 -34
- mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
- mesa/examples/advanced/epstein_civil_violence/agents.py +0 -164
- mesa/examples/advanced/epstein_civil_violence/app.py +0 -73
- mesa/examples/advanced/epstein_civil_violence/model.py +0 -114
- mesa/examples/advanced/pd_grid/Readme.md +0 -43
- mesa/examples/advanced/pd_grid/__init__.py +0 -0
- mesa/examples/advanced/pd_grid/agents.py +0 -50
- mesa/examples/advanced/pd_grid/analysis.ipynb +0 -228
- mesa/examples/advanced/pd_grid/app.py +0 -54
- mesa/examples/advanced/pd_grid/model.py +0 -71
- mesa/examples/advanced/sugarscape_g1mt/Readme.md +0 -64
- mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
- mesa/examples/advanced/sugarscape_g1mt/agents.py +0 -344
- mesa/examples/advanced/sugarscape_g1mt/app.py +0 -62
- mesa/examples/advanced/sugarscape_g1mt/model.py +0 -180
- mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +0 -50
- mesa/examples/advanced/sugarscape_g1mt/tests.py +0 -69
- mesa/examples/advanced/wolf_sheep/Readme.md +0 -57
- mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
- mesa/examples/advanced/wolf_sheep/agents.py +0 -102
- mesa/examples/advanced/wolf_sheep/app.py +0 -84
- mesa/examples/advanced/wolf_sheep/model.py +0 -137
- mesa/examples/basic/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/Readme.md +0 -22
- mesa/examples/basic/boid_flockers/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/agents.py +0 -71
- mesa/examples/basic/boid_flockers/app.py +0 -58
- mesa/examples/basic/boid_flockers/model.py +0 -69
- mesa/examples/basic/boltzmann_wealth_model/Readme.md +0 -56
- mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
- mesa/examples/basic/boltzmann_wealth_model/agents.py +0 -31
- mesa/examples/basic/boltzmann_wealth_model/app.py +0 -74
- mesa/examples/basic/boltzmann_wealth_model/model.py +0 -43
- mesa/examples/basic/boltzmann_wealth_model/st_app.py +0 -115
- mesa/examples/basic/conways_game_of_life/Readme.md +0 -39
- mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
- mesa/examples/basic/conways_game_of_life/agents.py +0 -47
- mesa/examples/basic/conways_game_of_life/app.py +0 -51
- mesa/examples/basic/conways_game_of_life/model.py +0 -31
- mesa/examples/basic/conways_game_of_life/st_app.py +0 -72
- mesa/examples/basic/schelling/Readme.md +0 -40
- mesa/examples/basic/schelling/__init__.py +0 -0
- mesa/examples/basic/schelling/agents.py +0 -26
- mesa/examples/basic/schelling/analysis.ipynb +0 -205
- mesa/examples/basic/schelling/app.py +0 -42
- mesa/examples/basic/schelling/model.py +0 -59
- mesa/examples/basic/virus_on_network/Readme.md +0 -61
- mesa/examples/basic/virus_on_network/__init__.py +0 -0
- mesa/examples/basic/virus_on_network/agents.py +0 -69
- mesa/examples/basic/virus_on_network/app.py +0 -114
- mesa/examples/basic/virus_on_network/model.py +0 -96
- mesa/experimental/cell_space/voronoi.py +0 -257
- mesa/experimental/components/matplotlib.py +0 -242
- mesa/experimental/solara_viz.py +0 -453
- mesa/visualization/components/__init__.py +0 -83
- mesa/visualization/components/altair_components.py +0 -188
- mesa/visualization/components/matplotlib_components.py +0 -175
- mesa/visualization/mpl_space_drawing.py +0 -593
- mesa/visualization/user_param.py +0 -69
- mesa/visualization/utils.py +0 -9
- mesa-3.0.0.dist-info/RECORD +0 -95
- mesa-3.0.0.dist-info/licenses/LICENSE +0 -202
- /mesa/{examples/advanced → cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}}/__init__.py +0 -0
- {mesa-3.0.0.dist-info → mesa-3.0.0a1.dist-info}/WHEEL +0 -0
- {mesa-3.0.0.dist-info → mesa-3.0.0a1.dist-info}/entry_points.txt +0 -0
|
@@ -1,344 +0,0 @@
|
|
|
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
|
|
@@ -1,62 +0,0 @@
|
|
|
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
|
|
@@ -1,180 +0,0 @@
|
|
|
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()
|
|
@@ -1,50 +0,0 @@
|
|
|
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
|