diff --git a/mesa/examples/advanced/sugarscape_g1mt/Readme.md b/mesa/examples/advanced/sugarscape_g1mt/Readme.md index 5a658cecc5d..2759450b959 100644 --- a/mesa/examples/advanced/sugarscape_g1mt/Readme.md +++ b/mesa/examples/advanced/sugarscape_g1mt/Readme.md @@ -34,51 +34,28 @@ cross over one another otherwise *end*. (Epstein and Axtell, 1996, p. 105) The model demonstrates several Mesa concepts and features: - - MultiGrid + - OrthogonalMooreGrid - Multiple agent types (traders, sugar, spice) - Dynamically removing agents from the grid and schedule when they die - Data Collection at the model and agent level - - Batchrunner (i.e. parameter sweeps) + - custom solara matplotlib space visualization -## Installation - -To install the dependencies use pip and the requirements.txt in this directory. e.g. - -``` - $ pip install -r requirements.txt -``` ## How to Run - -To run the model a single instance of the model: - -``` - $ python run.py -s -``` - -To run the model with BatchRunner: - -``` - $ python run.py -b -``` - To run the model interactively: ``` - $ mesa runserver + $ solara run app.py ``` Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run. ## Files -* `sugarscape_g1mt/trader_agents.py`: Defines the Trader agent class. -* `sugarscape_g1mt/resource_agents.py`: Defines the Resource agent class which contains an amount of sugar and spice. -* `sugarscape_g1mt/model.py`: Manages the Sugarscape Constant Growback with Traders model. -* `sugarscape_g1mt/sugar_map.txt`: Provides sugar and spice landscape in raster type format. -* `server.py`: Sets up an interactive visualization server. -* `run.py`: Runs Server, Single Run or Batch Run with data collection and basic analysis. +* `model.py`: The Sugarscape Constant Growback with Traders model. +* `agents.py`: Defines the Trader agent class and the Resource agent class which contains an amount of sugar and spice. * `app.py`: Runs a visualization server via Solara (`solara run app.py`). +* `sugar_map.txt`: Provides sugar and spice landscape in raster type format. * `tests.py`: Has tests to ensure that the model reproduces the results in shown in Growing Artificial Societies. ## Additional Resources diff --git a/mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/__init__.py b/mesa/examples/advanced/sugarscape_g1mt/__init__.py similarity index 100% rename from mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/__init__.py rename to mesa/examples/advanced/sugarscape_g1mt/__init__.py diff --git a/mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py b/mesa/examples/advanced/sugarscape_g1mt/agents.py similarity index 92% rename from mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py rename to mesa/examples/advanced/sugarscape_g1mt/agents.py index 579f3470978..74b33e91c85 100644 --- a/mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py +++ b/mesa/examples/advanced/sugarscape_g1mt/agents.py @@ -1,8 +1,8 @@ import math from mesa.experimental.cell_space import CellAgent +from mesa.experimental.cell_space import FixedAgent -from .resource_agents import Resource # Helper function @@ -18,6 +18,32 @@ def get_distance(cell_1, cell_2): dx = x1 - x2 dy = y1 - y2 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): diff --git a/mesa/examples/advanced/sugarscape_g1mt/app.py b/mesa/examples/advanced/sugarscape_g1mt/app.py index 146d3d5c51f..78327721650 100644 --- a/mesa/examples/advanced/sugarscape_g1mt/app.py +++ b/mesa/examples/advanced/sugarscape_g1mt/app.py @@ -1,7 +1,13 @@ +import sys +import os.path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../'))) + + import numpy as np import solara from matplotlib.figure import Figure from mesa.visualization import SolaraViz, make_plot_measure + from sugarscape_g1mt.model import SugarscapeG1mt from sugarscape_g1mt.trader_agents import Trader @@ -14,19 +20,19 @@ def portray(g): "trader": {"x": [], "y": [], "c": "tab:red", "marker": "o", "s": 10}, } - for content, (i, j) in g.coord_iter(): - for agent in content: - 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 - ) + 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 + ) return layers fig = Figure() diff --git a/mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/mesa/examples/advanced/sugarscape_g1mt/model.py similarity index 97% rename from mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py rename to mesa/examples/advanced/sugarscape_g1mt/model.py index 35e6d9e0e7b..f90cd3334cb 100644 --- a/mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/mesa/examples/advanced/sugarscape_g1mt/model.py @@ -4,8 +4,8 @@ import numpy as np from mesa.experimental.cell_space import OrthogonalVonNeumannGrid -from .resource_agents import Resource -from .trader_agents import Trader +from mesa.examples.advanced.sugarscape_g1mt.agents import Resource, Trader + # Helper Functions @@ -53,8 +53,9 @@ def __init__( vision_min=1, vision_max=5, enable_trade=True, + seed=None ): - super().__init__() + super().__init__(seed=seed) # Initiate width and height of sugarscape self.width = width self.height = height diff --git a/mesa/examples/advanced/sugarscape_g1mt/requirements.txt b/mesa/examples/advanced/sugarscape_g1mt/requirements.txt deleted file mode 100644 index 14c03478da9..00000000000 --- a/mesa/examples/advanced/sugarscape_g1mt/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -jupyter -mesa~=2.0 -numpy -matplotlib -networkx -pandas diff --git a/mesa/examples/advanced/sugarscape_g1mt/run.py b/mesa/examples/advanced/sugarscape_g1mt/run.py deleted file mode 100644 index f1056fa4b8f..00000000000 --- a/mesa/examples/advanced/sugarscape_g1mt/run.py +++ /dev/null @@ -1,105 +0,0 @@ -import sys - -import matplotlib.pyplot as plt -import mesa -import networkx as nx -import pandas as pd -from sugarscape_g1mt.model import SugarscapeG1mt -from sugarscape_g1mt.server import server - - -# Analysis -def assess_results(results, single_agent): - # Make dataframe of results - results_df = pd.DataFrame(results) - # Plot and show mean price - plt.scatter(results_df["Step"], results_df["Price"], s=0.75) - plt.show() - - if single_agent is not None: - plt.plot(results_df["Step"], results_df["Trader"]) - plt.show() - else: - n = max(results_df["RunId"]) - # Plot number of Traders - for i in range(n): - results_explore = results_df[results_df["RunId"] == i] - plt.plot(results_explore["Step"], results_explore["Trader"]) - plt.show() - - if single_agent is not None: - results_df = single_agent - - # Show Trade Networks - # create graph object - print("Making Network") - G = nx.Graph() - trade = results_df.dropna(subset=["Trade Network"]) - # add agent keys to make initial node set - G.add_nodes_from(list(trade["AgentID"].unique())) - - # create edge list - for idx, row in trade.iterrows(): - if len(row["Trade Network"]) > 0: - for agent in row["Trade Network"]: - G.add_edge(row["AgentID"], agent) - - # Get Basic Network Statistics - print(f"Node Connectivity {nx.node_connectivity(G)}") - print(f"Average Clustering {nx.average_clustering(G)}") - print(f"Global Efficiency {nx.global_efficiency(G)}") - - # Plot histogram of degree distribution - degree_sequence = sorted((d for n, d in G.degree()), reverse=True) - degree_sequence = [d for n, d in G.degree()] - plt.hist(degree_sequence) - plt.show() - - # Plot network - nx.draw(G) - plt.show() - - -# Run the model -def main(): - args = sys.argv[1:] - - if len(args) == 0: - server.launch() - - elif args[0] == "-s": - print("Running Single Model") - model = SugarscapeG1mt() - model.run_model() - model_results = model.datacollector.get_model_vars_dataframe() - model_results["Step"] = model_results.index - agent_results = model.datacollector.get_agent_vars_dataframe() - agent_results = agent_results.reset_index() - assess_results(model_results, agent_results) - - elif args[0] == "-b": - print("Conducting a Batch Run") - params = { - "width": 50, - "height": 50, - "vision_min": range(1, 4), - "metabolism_max": [2, 3, 4, 5], - } - - results_batch = mesa.batch_run( - SugarscapeG1mt, - parameters=params, - iterations=1, - number_processes=1, - data_collection_period=1, - display_progress=True, - ) - - assess_results(results_batch, None) - - else: - raise Exception("Option not found") - - -if __name__ == "__main__": - main() diff --git a/mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/sugar-map.txt b/mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt similarity index 100% rename from mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/sugar-map.txt rename to mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt diff --git a/mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py b/mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py deleted file mode 100644 index d9f276948c1..00000000000 --- a/mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py +++ /dev/null @@ -1,26 +0,0 @@ -from mesa.experimental.cell_space import FixedAgent - - -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]) diff --git a/mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py b/mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py deleted file mode 100644 index 3ef0066883f..00000000000 --- a/mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py +++ /dev/null @@ -1,61 +0,0 @@ -import mesa - -from .model import SugarscapeG1mt -from .resource_agents import Resource -from .trader_agents import Trader - -sugar_dic = {4: "#005C00", 3: "#008300", 2: "#00AA00", 1: "#00F800"} -spice_dic = {4: "#acac00", 3: "#c5c500", 2: "#dfdf00", 1: "#f8f800"} - - -def Agent_portrayal(agent): - if agent is None: - return - - if isinstance(agent, Trader): - return { - "Shape": "circle", - "Filled": "true", - "r": 0.5, - "Layer": 0, - "Color": "#FF0A01", - } - - elif isinstance(agent, Resource): - resource_type = "sugar" if agent.max_sugar > agent.max_spice else "spice" - if resource_type == "sugar": - color = ( - sugar_dic[agent.sugar_amount] if agent.sugar_amount != 0 else "#D6F5D6" - ) - layer = 1 if agent.sugar_amount > 2 else 0 - else: - color = ( - spice_dic[agent.spice_amount] if agent.spice_amount != 0 else "#D6F5D6" - ) - layer = 1 if agent.spice_amount > 2 else 0 - return { - "Color": color, - "Shape": "rect", - "Filled": "true", - "Layer": layer, - "w": 1, - "h": 1, - } - - return {} - - -canvas_element = mesa.visualization.CanvasGrid(Agent_portrayal, 50, 50, 500, 500) -chart_element = mesa.visualization.ChartModule( - [{"Label": "Trader", "Color": "#AA0000"}] -) -chart_element2 = mesa.visualization.ChartModule( - [{"Label": "Price", "Color": "#000000"}] -) - -server = mesa.visualization.ModularServer( - SugarscapeG1mt, - [canvas_element, chart_element, chart_element2], - "Sugarscape with Traders", -) -# server.launch() diff --git a/mesa/examples/advanced/sugarscape_g1mt/tests.py b/mesa/examples/advanced/sugarscape_g1mt/tests.py index 274afa6bb89..d570ce42161 100644 --- a/mesa/examples/advanced/sugarscape_g1mt/tests.py +++ b/mesa/examples/advanced/sugarscape_g1mt/tests.py @@ -1,11 +1,7 @@ -import random - import numpy as np from scipy import stats -from sugarscape_g1mt.model import SugarscapeG1mt, flatten -from sugarscape_g1mt.trader_agents import Trader - -random.seed(1) +from .model import SugarscapeG1mt, flatten +from .agents import Trader def check_slope(y, increasing): @@ -19,7 +15,7 @@ def check_slope(y, increasing): def test_decreasing_price_variance(): # The variance of the average trade price should decrease over time (figure IV-3) # See Growing Artificial Societies p. 109. - model = SugarscapeG1mt() + model = SugarscapeG1mt(42) model.datacollector._new_model_reporter( "price_variance", lambda m: np.var( diff --git a/mesa/examples/advanced/wolf_sheep/wolf_sheep/agents.py b/mesa/examples/advanced/wolf_sheep/agents.py similarity index 100% rename from mesa/examples/advanced/wolf_sheep/wolf_sheep/agents.py rename to mesa/examples/advanced/wolf_sheep/agents.py diff --git a/mesa/examples/advanced/wolf_sheep/app.py b/mesa/examples/advanced/wolf_sheep/app.py new file mode 100644 index 00000000000..426610d6899 --- /dev/null +++ b/mesa/examples/advanced/wolf_sheep/app.py @@ -0,0 +1,82 @@ +from mesa.examples.advanced.wolf_sheep.agents import Wolf, Sheep, GrassPatch +from mesa.examples.advanced.wolf_sheep.model import WolfSheep +from mesa.visualization import ( + Slider, + SolaraViz, + make_plot_measure, + make_space_matplotlib, +) + +WOLF_COLOR = "#000000" +SHEEP_COLOR = "#648FFF" + + +def wolf_sheep_portrayal(agent): + if agent is None: + return + + portrayal = { + "size": 25, + "shape": "s", # square marker + } + + if isinstance(agent, Wolf): + portrayal["color"] = WOLF_COLOR + portrayal["Layer"] = 3 + elif isinstance(agent, Sheep): + portrayal["color"] = SHEEP_COLOR + portrayal["Layer"] = 2 + elif isinstance(agent, GrassPatch): + if agent.fully_grown: + portrayal["color"] = "#00FF00" + else: + portrayal["color"] = "#84e184" + # portrayal["shape"] = "rect" + # portrayal["Filled"] = "true" + portrayal["Layer"] = 1 + + return portrayal + +model_params = { + # The following line is an example to showcase StaticText. + "grass": { + "type": "Select", + "value": True, + "values": [True, False], + "label": "grass regrowth enabled?" + }, + "grass_regrowth_time": Slider("Grass Regrowth Time", 20, 1, 50), + "initial_sheep": Slider( + "Initial Sheep Population", 100, 10, 300 + ), + "sheep_reproduce": Slider( + "Sheep Reproduction Rate", 0.04, 0.01, 1.0, 0.01 + ), + "initial_wolves": Slider("Initial Wolf Population", 10, 5, 100), + "wolf_reproduce": Slider( + "Wolf Reproduction Rate", + 0.05, + 0.01, + 1.0, + 0.01, + ), + "wolf_gain_from_food": Slider( + "Wolf Gain From Food Rate", 20, 1, 50 + ), + "sheep_gain_from_food": Slider("Sheep Gain From Food", 4, 1, 10), +} + + +space_component = make_space_matplotlib(wolf_sheep_portrayal) +lineplot_component = make_plot_measure(["Wolves", "Sheep", "Grass"]) + +model = WolfSheep() + + +page = SolaraViz( + model, + components=[space_component, lineplot_component], + model_params=model_params, + name="Wolf Sheep", +) +page # noqa diff --git a/mesa/examples/advanced/wolf_sheep/wolf_sheep/model.py b/mesa/examples/advanced/wolf_sheep/model.py similarity index 94% rename from mesa/examples/advanced/wolf_sheep/wolf_sheep/model.py rename to mesa/examples/advanced/wolf_sheep/model.py index 5b43b7912e1..37ea8e4ac8a 100644 --- a/mesa/examples/advanced/wolf_sheep/wolf_sheep/model.py +++ b/mesa/examples/advanced/wolf_sheep/model.py @@ -12,7 +12,7 @@ import mesa from mesa.experimental.cell_space import OrthogonalMooreGrid -from .agents import GrassPatch, Sheep, Wolf +from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf class WolfSheep(mesa.Model): @@ -67,7 +67,7 @@ def __init__( once it is eaten sheep_gain_from_food: Energy sheep gain from grass, if enabled. """ - super().__init__(seed=None) + super().__init__(seed=seed) # Set parameters self.width = width self.height = height @@ -81,11 +81,9 @@ def __init__( collectors = { "Wolves": lambda m: len(m.agents_by_type[Wolf]), "Sheep": lambda m: len(m.agents_by_type[Sheep]), + "Grass": lambda m: len(m.agents_by_type[GrassPatch].select(lambda a:a.fully_grown)) if m.grass else -1, } - if grass: - collectors["Grass"] = lambda m: len(m.agents_by_type[GrassPatch]) - self.datacollector = mesa.DataCollector(collectors) # Create sheep: diff --git a/mesa/examples/advanced/wolf_sheep/requirements.txt b/mesa/examples/advanced/wolf_sheep/requirements.txt deleted file mode 100644 index 25d263f4e84..00000000000 --- a/mesa/examples/advanced/wolf_sheep/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -mesa~=2.0 diff --git a/mesa/examples/advanced/wolf_sheep/run.py b/mesa/examples/advanced/wolf_sheep/run.py deleted file mode 100644 index 89e3b5488df..00000000000 --- a/mesa/examples/advanced/wolf_sheep/run.py +++ /dev/null @@ -1,3 +0,0 @@ -from wolf_sheep.server import server - -server.launch(open_browser=True) diff --git a/mesa/examples/advanced/wolf_sheep/wolf_sheep/__init__.py b/mesa/examples/advanced/wolf_sheep/wolf_sheep/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/mesa/examples/advanced/wolf_sheep/wolf_sheep/resources/sheep.png b/mesa/examples/advanced/wolf_sheep/wolf_sheep/resources/sheep.png deleted file mode 100644 index dfb81b0e5d7..00000000000 Binary files a/mesa/examples/advanced/wolf_sheep/wolf_sheep/resources/sheep.png and /dev/null differ diff --git a/mesa/examples/advanced/wolf_sheep/wolf_sheep/resources/wolf.png b/mesa/examples/advanced/wolf_sheep/wolf_sheep/resources/wolf.png deleted file mode 100644 index 5357b855197..00000000000 Binary files a/mesa/examples/advanced/wolf_sheep/wolf_sheep/resources/wolf.png and /dev/null differ diff --git a/mesa/examples/advanced/wolf_sheep/wolf_sheep/server.py b/mesa/examples/advanced/wolf_sheep/wolf_sheep/server.py deleted file mode 100644 index 112c1a2dfda..00000000000 --- a/mesa/examples/advanced/wolf_sheep/wolf_sheep/server.py +++ /dev/null @@ -1,78 +0,0 @@ -import mesa -from wolf_sheep.agents import GrassPatch, Sheep, Wolf -from wolf_sheep.model import WolfSheep - - -def wolf_sheep_portrayal(agent): - if agent is None: - return - - portrayal = {} - - if type(agent) is Sheep: - portrayal["Shape"] = "wolf_sheep/resources/sheep.png" - # https://icons8.com/web-app/433/sheep - portrayal["scale"] = 0.9 - portrayal["Layer"] = 1 - - elif type(agent) is Wolf: - portrayal["Shape"] = "wolf_sheep/resources/wolf.png" - # https://icons8.com/web-app/36821/German-Shepherd - portrayal["scale"] = 0.9 - portrayal["Layer"] = 2 - portrayal["text"] = round(agent.energy, 1) - portrayal["text_color"] = "White" - - elif type(agent) is GrassPatch: - if agent.fully_grown: - portrayal["Color"] = ["#00FF00", "#00CC00", "#009900"] - else: - portrayal["Color"] = ["#84e184", "#adebad", "#d6f5d6"] - portrayal["Shape"] = "rect" - portrayal["Filled"] = "true" - portrayal["Layer"] = 0 - portrayal["w"] = 1 - portrayal["h"] = 1 - - return portrayal - - -canvas_element = mesa.visualization.CanvasGrid(wolf_sheep_portrayal, 20, 20, 500, 500) -chart_element = mesa.visualization.ChartModule( - [ - {"Label": "Wolves", "Color": "#AA0000"}, - {"Label": "Sheep", "Color": "#666666"}, - {"Label": "Grass", "Color": "#00AA00"}, - ] -) - -model_params = { - # The following line is an example to showcase StaticText. - "title": mesa.visualization.StaticText("Parameters:"), - "grass": mesa.visualization.Checkbox("Grass Enabled", True), - "grass_regrowth_time": mesa.visualization.Slider("Grass Regrowth Time", 20, 1, 50), - "initial_sheep": mesa.visualization.Slider( - "Initial Sheep Population", 100, 10, 300 - ), - "sheep_reproduce": mesa.visualization.Slider( - "Sheep Reproduction Rate", 0.04, 0.01, 1.0, 0.01 - ), - "initial_wolves": mesa.visualization.Slider("Initial Wolf Population", 50, 10, 300), - "wolf_reproduce": mesa.visualization.Slider( - "Wolf Reproduction Rate", - 0.05, - 0.01, - 1.0, - 0.01, - description="The rate at which wolf agents reproduce.", - ), - "wolf_gain_from_food": mesa.visualization.Slider( - "Wolf Gain From Food Rate", 20, 1, 50 - ), - "sheep_gain_from_food": mesa.visualization.Slider("Sheep Gain From Food", 4, 1, 10), -} - -server = mesa.visualization.ModularServer( - WolfSheep, [canvas_element, chart_element], "Wolf Sheep Predation", model_params -) -server.port = 8521