diff --git a/mesa/examples/advanced/sugarscape_g1mt/agents.py b/mesa/examples/advanced/sugarscape_g1mt/agents.py index aa9e9adc2ee..d3cdb9d8117 100644 --- a/mesa/examples/advanced/sugarscape_g1mt/agents.py +++ b/mesa/examples/advanced/sugarscape_g1mt/agents.py @@ -1,6 +1,6 @@ import math -from mesa.experimental.cell_space import CellAgent, FixedAgent +from mesa.experimental.cell_space import CellAgent # Helper function @@ -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: @@ -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() @@ -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 @@ -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 ] @@ -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) ] @@ -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): @@ -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 diff --git a/mesa/examples/advanced/sugarscape_g1mt/app.py b/mesa/examples/advanced/sugarscape_g1mt/app.py index 4903943671f..20b16673225 100644 --- a/mesa/examples/advanced/sugarscape_g1mt/app.py +++ b/mesa/examples/advanced/sugarscape_g1mt/app.py @@ -1,8 +1,13 @@ +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 @@ -10,24 +15,13 @@ 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() @@ -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() @@ -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, diff --git a/mesa/examples/advanced/sugarscape_g1mt/model.py b/mesa/examples/advanced/sugarscape_g1mt/model.py index c116de0fd84..8c139eb8ad1 100644 --- a/mesa/examples/advanced/sugarscape_g1mt/model.py +++ b/mesa/examples/advanced/sugarscape_g1mt/model.py @@ -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 @@ -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 @@ -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, @@ -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 @@ -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