Skip to content

Commit

Permalink
Switch sugarscape to using property layers (#2546)
Browse files Browse the repository at this point in the history
  • Loading branch information
quaquel authored Dec 13, 2024
1 parent e4e92f4 commit 965eede
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 97 deletions.
77 changes: 12 additions & 65 deletions mesa/examples/advanced/sugarscape_g1mt/agents.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import math

from mesa.experimental.cell_space import CellAgent, FixedAgent
from mesa.experimental.cell_space import CellAgent


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


class Resource(FixedAgent):
"""
Resource:
- contains an amount of sugar and spice
- grows 1 amount of sugar at each turn
- grows 1 amount of spice at each turn
"""

def __init__(self, model, max_sugar, max_spice, cell):
super().__init__(model)
self.sugar_amount = max_sugar
self.max_sugar = max_sugar
self.spice_amount = max_spice
self.max_spice = max_spice
self.cell = cell

def step(self):
"""
Growth function, adds one unit of sugar and spice each step up to
max amount
"""
self.sugar_amount = min([self.max_sugar, self.sugar_amount + 1])
self.spice_amount = min([self.max_spice, self.spice_amount + 1])


class Trader(CellAgent):
"""
Trader:
Expand Down Expand Up @@ -70,12 +45,6 @@ def __init__(
self.prices = []
self.trade_partners = []

def get_resource(self, cell):
for agent in cell.agents:
if isinstance(agent, Resource):
return agent
raise Exception(f"Resource agent not found in the position {cell.coordinate}")

def get_trader(self, cell):
"""
helper function used in self.trade_with_neighbors()
Expand All @@ -85,17 +54,6 @@ def get_trader(self, cell):
if isinstance(agent, Trader):
return agent

def is_occupied_by_other(self, cell):
"""
helper function part 1 of self.move()
"""

if cell is self.cell:
# agent's position is considered unoccupied as agent can stay there
return False
# get contents of each cell in neighborhood
return any(isinstance(a, Trader) for a in cell.agents)

def calculate_welfare(self, sugar, spice):
"""
helper function
Expand Down Expand Up @@ -264,15 +222,15 @@ def move(self):
neighboring_cells = [
cell
for cell in self.cell.get_neighborhood(self.vision, include_center=True)
if not self.is_occupied_by_other(cell)
if cell.is_empty
]

# 2. determine which move maximizes welfare

welfares = [
self.calculate_welfare(
self.sugar + self.get_resource(cell).sugar_amount,
self.spice + self.get_resource(cell).spice_amount,
self.sugar + cell.sugar,
self.spice + cell.spice,
)
for cell in neighboring_cells
]
Expand All @@ -282,6 +240,7 @@ def move(self):
# find the highest welfare in welfares
max_welfare = max(welfares)
# get the index of max welfare cells
# fixme: rewrite using enumerate and single loop
candidate_indices = [
i for i in range(len(welfares)) if math.isclose(welfares[i], max_welfare)
]
Expand All @@ -296,19 +255,17 @@ def move(self):
for cell in candidates
if math.isclose(get_distance(self.cell, cell), min_dist, rel_tol=1e-02)
]

# 4. Move Agent
self.cell = self.random.choice(final_candidates)

def eat(self):
patch = self.get_resource(self.cell)
if patch.sugar_amount > 0:
self.sugar += patch.sugar_amount
patch.sugar_amount = 0
self.sugar += self.cell.sugar
self.cell.sugar = 0
self.sugar -= self.metabolism_sugar

if patch.spice_amount > 0:
self.spice += patch.spice_amount
patch.spice_amount = 0
self.spice += self.cell.spice
self.cell.spice = 0
self.spice -= self.metabolism_spice

def maybe_die(self):
Expand All @@ -327,18 +284,8 @@ def trade_with_neighbors(self):
2- trade (2 sessions)
3- collect data
"""

neighbor_agents = [
self.get_trader(cell)
for cell in self.cell.get_neighborhood(radius=self.vision)
if self.is_occupied_by_other(cell)
]

if len(neighbor_agents) == 0:
return

# iterate through traders in neighboring cells and trade
for a in neighbor_agents:
# iterate through traders in neighboring cells and trade
for a in self.cell.get_neighborhood(radius=self.vision).agents:
self.trade(a)

return
40 changes: 23 additions & 17 deletions mesa/examples/advanced/sugarscape_g1mt/app.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,27 @@
import os
import sys

sys.path.insert(0, os.path.abspath("../../../.."))


import numpy as np
import solara
from matplotlib.figure import Figure

from mesa.examples.advanced.sugarscape_g1mt.agents import Trader
from mesa.examples.advanced.sugarscape_g1mt.model import SugarscapeG1mt
from mesa.visualization import Slider, SolaraViz, make_plot_component


def SpaceDrawer(model):
def portray(g):
layers = {
"sugar": [[np.nan for j in range(g.height)] for i in range(g.width)],
"spice": [[np.nan for j in range(g.height)] for i in range(g.width)],
"trader": {"x": [], "y": [], "c": "tab:red", "marker": "o", "s": 10},
}

for agent in g.all_cells.agents:
i, j = agent.cell.coordinate
if isinstance(agent, Trader):
layers["trader"]["x"].append(i)
layers["trader"]["y"].append(j)
else:
# Don't visualize resource with value <= 1.
layers["sugar"][i][j] = (
agent.sugar_amount if agent.sugar_amount > 1 else np.nan
)
layers["spice"][i][j] = (
agent.spice_amount if agent.spice_amount > 1 else np.nan
)
layers["trader"]["x"].append(i)
layers["trader"]["y"].append(j)
return layers

fig = Figure()
Expand All @@ -36,10 +30,18 @@ def portray(g):
# Sugar
# Important note: imshow by default draws from upper left. You have to
# always explicitly specify origin="lower".
im = ax.imshow(out["sugar"], cmap="spring", origin="lower")
im = ax.imshow(
np.ma.masked_where(model.grid.sugar.data <= 1, model.grid.sugar.data),
cmap="spring",
origin="lower",
)
fig.colorbar(im, orientation="vertical")
# Spice
ax.imshow(out["spice"], cmap="winter", origin="lower")
ax.imshow(
np.ma.masked_where(model.grid.spice.data <= 1, model.grid.spice.data),
cmap="winter",
origin="lower",
)
# Trader
ax.scatter(**out["trader"])
ax.set_axis_off()
Expand Down Expand Up @@ -75,7 +77,11 @@ def portray(g):

page = SolaraViz(
model,
components=[SpaceDrawer, make_plot_component(["Trader", "Price"])],
components=[
SpaceDrawer,
make_plot_component("#Traders"),
make_plot_component("Price"),
],
model_params=model_params,
name="Sugarscape {G1, M, T}",
play_interval=150,
Expand Down
38 changes: 23 additions & 15 deletions mesa/examples/advanced/sugarscape_g1mt/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import numpy as np

import mesa
from mesa.examples.advanced.sugarscape_g1mt.agents import Resource, Trader
from mesa.examples.advanced.sugarscape_g1mt.agents import Trader
from mesa.experimental.cell_space import OrthogonalVonNeumannGrid
from mesa.experimental.cell_space.property_layer import PropertyLayer


# Helper Functions
Expand Down Expand Up @@ -58,8 +59,8 @@ def __init__(
# Initiate width and height of sugarscape
self.width = width
self.height = height
# Initiate population attributes

# Initiate population attributes
self.enable_trade = enable_trade
self.running = True

Expand All @@ -70,25 +71,25 @@ def __init__(
# initiate datacollector
self.datacollector = mesa.DataCollector(
model_reporters={
"Trader": lambda m: len(m.agents_by_type[Trader]),
"Trade Volume": lambda m: sum(
len(a.trade_partners) for a in m.agents_by_type[Trader]
),
"#Traders": lambda m: len(m.agents),
"Trade Volume": lambda m: sum(len(a.trade_partners) for a in m.agents),
"Price": lambda m: geometric_mean(
flatten([a.prices for a in m.agents_by_type[Trader]])
flatten([a.prices for a in m.agents])
),
},
agent_reporters={"Trade Network": lambda a: get_trade(a)},
)

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

for cell in self.grid.all_cells:
max_sugar = sugar_distribution[cell.coordinate]
max_spice = spice_distribution[cell.coordinate]
Resource(self, max_sugar, max_spice, cell)
self.grid.add_property_layer(
PropertyLayer.from_data("sugar", self.sugar_distribution)
)
self.grid.add_property_layer(
PropertyLayer.from_data("spice", self.spice_distribution)
)

Trader.create_agents(
self,
Expand Down Expand Up @@ -117,7 +118,12 @@ def step(self):
and then randomly activates traders
"""
# step Resource agents
self.agents_by_type[Resource].do("step")
self.grid.sugar.data = np.minimum(
self.grid.sugar.data + 1, self.sugar_distribution
)
self.grid.spice.data = np.minimum(
self.grid.spice.data + 1, self.spice_distribution
)

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

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

0 comments on commit 965eede

Please sign in to comment.