Skip to content

Commit 965eede

Browse files
authored
Switch sugarscape to using property layers (#2546)
1 parent e4e92f4 commit 965eede

File tree

3 files changed

+58
-97
lines changed

3 files changed

+58
-97
lines changed

mesa/examples/advanced/sugarscape_g1mt/agents.py

Lines changed: 12 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import math
22

3-
from mesa.experimental.cell_space import CellAgent, FixedAgent
3+
from mesa.experimental.cell_space import CellAgent
44

55

66
# Helper function
@@ -18,31 +18,6 @@ def get_distance(cell_1, cell_2):
1818
return math.sqrt(dx**2 + dy**2)
1919

2020

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-
4621
class Trader(CellAgent):
4722
"""
4823
Trader:
@@ -70,12 +45,6 @@ def __init__(
7045
self.prices = []
7146
self.trade_partners = []
7247

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-
7948
def get_trader(self, cell):
8049
"""
8150
helper function used in self.trade_with_neighbors()
@@ -85,17 +54,6 @@ def get_trader(self, cell):
8554
if isinstance(agent, Trader):
8655
return agent
8756

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-
9957
def calculate_welfare(self, sugar, spice):
10058
"""
10159
helper function
@@ -264,15 +222,15 @@ def move(self):
264222
neighboring_cells = [
265223
cell
266224
for cell in self.cell.get_neighborhood(self.vision, include_center=True)
267-
if not self.is_occupied_by_other(cell)
225+
if cell.is_empty
268226
]
269227

270228
# 2. determine which move maximizes welfare
271229

272230
welfares = [
273231
self.calculate_welfare(
274-
self.sugar + self.get_resource(cell).sugar_amount,
275-
self.spice + self.get_resource(cell).spice_amount,
232+
self.sugar + cell.sugar,
233+
self.spice + cell.spice,
276234
)
277235
for cell in neighboring_cells
278236
]
@@ -282,6 +240,7 @@ def move(self):
282240
# find the highest welfare in welfares
283241
max_welfare = max(welfares)
284242
# get the index of max welfare cells
243+
# fixme: rewrite using enumerate and single loop
285244
candidate_indices = [
286245
i for i in range(len(welfares)) if math.isclose(welfares[i], max_welfare)
287246
]
@@ -296,19 +255,17 @@ def move(self):
296255
for cell in candidates
297256
if math.isclose(get_distance(self.cell, cell), min_dist, rel_tol=1e-02)
298257
]
258+
299259
# 4. Move Agent
300260
self.cell = self.random.choice(final_candidates)
301261

302262
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
263+
self.sugar += self.cell.sugar
264+
self.cell.sugar = 0
307265
self.sugar -= self.metabolism_sugar
308266

309-
if patch.spice_amount > 0:
310-
self.spice += patch.spice_amount
311-
patch.spice_amount = 0
267+
self.spice += self.cell.spice
268+
self.cell.spice = 0
312269
self.spice -= self.metabolism_spice
313270

314271
def maybe_die(self):
@@ -327,18 +284,8 @@ def trade_with_neighbors(self):
327284
2- trade (2 sessions)
328285
3- collect data
329286
"""
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:
287+
# iterate through traders in neighboring cells and trade
288+
for a in self.cell.get_neighborhood(radius=self.vision).agents:
342289
self.trade(a)
343290

344291
return

mesa/examples/advanced/sugarscape_g1mt/app.py

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,27 @@
1+
import os
2+
import sys
3+
4+
sys.path.insert(0, os.path.abspath("../../../.."))
5+
6+
17
import numpy as np
28
import solara
39
from matplotlib.figure import Figure
410

5-
from mesa.examples.advanced.sugarscape_g1mt.agents import Trader
611
from mesa.examples.advanced.sugarscape_g1mt.model import SugarscapeG1mt
712
from mesa.visualization import Slider, SolaraViz, make_plot_component
813

914

1015
def SpaceDrawer(model):
1116
def portray(g):
1217
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)],
1518
"trader": {"x": [], "y": [], "c": "tab:red", "marker": "o", "s": 10},
1619
}
1720

1821
for agent in g.all_cells.agents:
1922
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-
)
23+
layers["trader"]["x"].append(i)
24+
layers["trader"]["y"].append(j)
3125
return layers
3226

3327
fig = Figure()
@@ -36,10 +30,18 @@ def portray(g):
3630
# Sugar
3731
# Important note: imshow by default draws from upper left. You have to
3832
# always explicitly specify origin="lower".
39-
im = ax.imshow(out["sugar"], cmap="spring", origin="lower")
33+
im = ax.imshow(
34+
np.ma.masked_where(model.grid.sugar.data <= 1, model.grid.sugar.data),
35+
cmap="spring",
36+
origin="lower",
37+
)
4038
fig.colorbar(im, orientation="vertical")
4139
# Spice
42-
ax.imshow(out["spice"], cmap="winter", origin="lower")
40+
ax.imshow(
41+
np.ma.masked_where(model.grid.spice.data <= 1, model.grid.spice.data),
42+
cmap="winter",
43+
origin="lower",
44+
)
4345
# Trader
4446
ax.scatter(**out["trader"])
4547
ax.set_axis_off()
@@ -75,7 +77,11 @@ def portray(g):
7577

7678
page = SolaraViz(
7779
model,
78-
components=[SpaceDrawer, make_plot_component(["Trader", "Price"])],
80+
components=[
81+
SpaceDrawer,
82+
make_plot_component("#Traders"),
83+
make_plot_component("Price"),
84+
],
7985
model_params=model_params,
8086
name="Sugarscape {G1, M, T}",
8187
play_interval=150,

mesa/examples/advanced/sugarscape_g1mt/model.py

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import numpy as np
44

55
import mesa
6-
from mesa.examples.advanced.sugarscape_g1mt.agents import Resource, Trader
6+
from mesa.examples.advanced.sugarscape_g1mt.agents import Trader
77
from mesa.experimental.cell_space import OrthogonalVonNeumannGrid
8+
from mesa.experimental.cell_space.property_layer import PropertyLayer
89

910

1011
# Helper Functions
@@ -58,8 +59,8 @@ def __init__(
5859
# Initiate width and height of sugarscape
5960
self.width = width
6061
self.height = height
61-
# Initiate population attributes
6262

63+
# Initiate population attributes
6364
self.enable_trade = enable_trade
6465
self.running = True
6566

@@ -70,25 +71,25 @@ def __init__(
7071
# initiate datacollector
7172
self.datacollector = mesa.DataCollector(
7273
model_reporters={
73-
"Trader": lambda m: len(m.agents_by_type[Trader]),
74-
"Trade Volume": lambda m: sum(
75-
len(a.trade_partners) for a in m.agents_by_type[Trader]
76-
),
74+
"#Traders": lambda m: len(m.agents),
75+
"Trade Volume": lambda m: sum(len(a.trade_partners) for a in m.agents),
7776
"Price": lambda m: geometric_mean(
78-
flatten([a.prices for a in m.agents_by_type[Trader]])
77+
flatten([a.prices for a in m.agents])
7978
),
8079
},
8180
agent_reporters={"Trade Network": lambda a: get_trade(a)},
8281
)
8382

84-
# read in landscape file from supplmentary material
85-
sugar_distribution = np.genfromtxt(Path(__file__).parent / "sugar-map.txt")
86-
spice_distribution = np.flip(sugar_distribution, 1)
83+
# read in landscape file from supplementary material
84+
self.sugar_distribution = np.genfromtxt(Path(__file__).parent / "sugar-map.txt")
85+
self.spice_distribution = np.flip(self.sugar_distribution, 1)
8786

88-
for cell in self.grid.all_cells:
89-
max_sugar = sugar_distribution[cell.coordinate]
90-
max_spice = spice_distribution[cell.coordinate]
91-
Resource(self, max_sugar, max_spice, cell)
87+
self.grid.add_property_layer(
88+
PropertyLayer.from_data("sugar", self.sugar_distribution)
89+
)
90+
self.grid.add_property_layer(
91+
PropertyLayer.from_data("spice", self.spice_distribution)
92+
)
9293

9394
Trader.create_agents(
9495
self,
@@ -117,7 +118,12 @@ def step(self):
117118
and then randomly activates traders
118119
"""
119120
# step Resource agents
120-
self.agents_by_type[Resource].do("step")
121+
self.grid.sugar.data = np.minimum(
122+
self.grid.sugar.data + 1, self.sugar_distribution
123+
)
124+
self.grid.spice.data = np.minimum(
125+
self.grid.spice.data + 1, self.spice_distribution
126+
)
121127

122128
# step trader agents
123129
# to account for agent death and removal we need a separate data structure to
@@ -142,6 +148,8 @@ def step(self):
142148
agent.trade_with_neighbors()
143149

144150
# collect model level data
151+
# fixme we can already collect agent class data
152+
# fixme, we don't have resource agents anymore so this can be done simpler
145153
self.datacollector.collect(self)
146154
"""
147155
Mesa is working on updating datacollector agent reporter

0 commit comments

Comments
 (0)