From b3ad1d0f3ae46489e41659d8e9233393be47ef48 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Thu, 24 Oct 2024 11:56:01 +0200 Subject: [PATCH 1/6] reorganization of sugarscape model --- .../advanced/sugarscape_g1mt/Readme.md | 35 +----- .../{sugarscape_g1mt => }/__init__.py | 0 .../trader_agents.py => agents.py} | 29 ++++- mesa/examples/advanced/sugarscape_g1mt/app.py | 32 +++--- .../{sugarscape_g1mt => }/model.py | 7 +- .../advanced/sugarscape_g1mt/requirements.txt | 6 - mesa/examples/advanced/sugarscape_g1mt/run.py | 105 ------------------ .../{sugarscape_g1mt => }/sugar-map.txt | 0 .../sugarscape_g1mt/resource_agents.py | 26 ----- .../sugarscape_g1mt/sugarscape_g1mt/server.py | 61 ---------- .../advanced/sugarscape_g1mt/tests.py | 10 +- 11 files changed, 60 insertions(+), 251 deletions(-) rename mesa/examples/advanced/sugarscape_g1mt/{sugarscape_g1mt => }/__init__.py (100%) rename mesa/examples/advanced/sugarscape_g1mt/{sugarscape_g1mt/trader_agents.py => agents.py} (92%) rename mesa/examples/advanced/sugarscape_g1mt/{sugarscape_g1mt => }/model.py (97%) delete mode 100644 mesa/examples/advanced/sugarscape_g1mt/requirements.txt delete mode 100644 mesa/examples/advanced/sugarscape_g1mt/run.py rename mesa/examples/advanced/sugarscape_g1mt/{sugarscape_g1mt => }/sugar-map.txt (100%) delete mode 100644 mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py delete mode 100644 mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py diff --git a/mesa/examples/advanced/sugarscape_g1mt/Readme.md b/mesa/examples/advanced/sugarscape_g1mt/Readme.md index 5a658cecc5d..c5ad388b571 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. +* `agent.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..f4e6c95f349 100644 --- a/mesa/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py +++ b/mesa/examples/advanced/sugarscape_g1mt/agents.py @@ -1,8 +1,9 @@ + 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 +19,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( From 4b63da5bf5f5d4f851c5146ea19d091378fa50d4 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Thu, 24 Oct 2024 15:42:06 +0200 Subject: [PATCH 2/6] reorganize wolf_sheep and add solara interface --- mesa/examples/advanced/wolf_sheep/agents.py | 102 +++++++++++++ mesa/examples/advanced/wolf_sheep/app.py | 88 ++++++++++++ mesa/examples/advanced/wolf_sheep/model.py | 134 ++++++++++++++++++ .../advanced/wolf_sheep/requirements.txt | 1 - mesa/examples/advanced/wolf_sheep/run.py | 3 - 5 files changed, 324 insertions(+), 4 deletions(-) create mode 100644 mesa/examples/advanced/wolf_sheep/agents.py create mode 100644 mesa/examples/advanced/wolf_sheep/app.py create mode 100644 mesa/examples/advanced/wolf_sheep/model.py delete mode 100644 mesa/examples/advanced/wolf_sheep/requirements.txt delete mode 100644 mesa/examples/advanced/wolf_sheep/run.py diff --git a/mesa/examples/advanced/wolf_sheep/agents.py b/mesa/examples/advanced/wolf_sheep/agents.py new file mode 100644 index 00000000000..8e71988bc9a --- /dev/null +++ b/mesa/examples/advanced/wolf_sheep/agents.py @@ -0,0 +1,102 @@ +from mesa.experimental.cell_space import CellAgent, FixedAgent + + +class Animal(CellAgent): + """The base animal class.""" + + def __init__(self, model, energy, p_reproduce, energy_from_food, cell): + """Initializes an animal. + + Args: + model: a model instance + energy: starting amount of energy + p_reproduce: probability of sexless reproduction + energy_from_food: energy obtained from 1 unit of food + cell: the cell in which the animal starts + """ + super().__init__(model) + self.energy = energy + self.p_reproduce = p_reproduce + self.energy_from_food = energy_from_food + self.cell = cell + + def spawn_offspring(self): + """Create offspring.""" + self.energy /= 2 + self.__class__( + self.model, + self.energy, + self.p_reproduce, + self.energy_from_food, + self.cell, + ) + + def feed(self): ... + + def step(self): + """One step of the agent.""" + self.cell = self.cell.neighborhood.select_random_cell() + self.energy -= 1 + + self.feed() + + if self.energy < 0: + self.remove() + elif self.random.random() < self.p_reproduce: + self.spawn_offspring() + + +class Sheep(Animal): + """A sheep that walks around, reproduces (asexually) and gets eaten.""" + + def feed(self): + """If possible eat the food in the current location.""" + # If there is grass available, eat it + if self.model.grass: + grass_patch = next( + obj for obj in self.cell.agents if isinstance(obj, GrassPatch) + ) + if grass_patch.fully_grown: + self.energy += self.energy_from_food + grass_patch.fully_grown = False + + +class Wolf(Animal): + """A wolf that walks around, reproduces (asexually) and eats sheep.""" + + def feed(self): + """If possible eat the food in the current location.""" + sheep = [obj for obj in self.cell.agents if isinstance(obj, Sheep)] + if len(sheep) > 0: + sheep_to_eat = self.random.choice(sheep) + self.energy += self.energy_from_food + + # Kill the sheep + sheep_to_eat.remove() + + +class GrassPatch(FixedAgent): + """ + A patch of grass that grows at a fixed rate and it is eaten by sheep + """ + + def __init__(self, model, fully_grown, countdown): + """ + Creates a new patch of grass + + Args: + grown: (boolean) Whether the patch of grass is fully grown or not + countdown: Time for the patch of grass to be fully grown again + """ + super().__init__(model) + self.fully_grown = fully_grown + self.countdown = countdown + + def step(self): + if not self.fully_grown: + if self.countdown <= 0: + # Set as fully grown + self.fully_grown = True + self.countdown = self.model.grass_regrowth_time + else: + self.countdown -= 1 diff --git a/mesa/examples/advanced/wolf_sheep/app.py b/mesa/examples/advanced/wolf_sheep/app.py new file mode 100644 index 00000000000..ccd49a2a216 --- /dev/null +++ b/mesa/examples/advanced/wolf_sheep/app.py @@ -0,0 +1,88 @@ +import sys +import os.path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../'))) + + + +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/model.py b/mesa/examples/advanced/wolf_sheep/model.py new file mode 100644 index 00000000000..37ea8e4ac8a --- /dev/null +++ b/mesa/examples/advanced/wolf_sheep/model.py @@ -0,0 +1,134 @@ +""" +Wolf-Sheep Predation Model +================================ + +Replication of the model found in NetLogo: + Wilensky, U. (1997). NetLogo Wolf Sheep Predation model. + http://ccl.northwestern.edu/netlogo/models/WolfSheepPredation. + Center for Connected Learning and Computer-Based Modeling, + Northwestern University, Evanston, IL. +""" + +import mesa +from mesa.experimental.cell_space import OrthogonalMooreGrid + +from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf + + +class WolfSheep(mesa.Model): + """ + Wolf-Sheep Predation Model + """ + + height = 20 + width = 20 + + initial_sheep = 100 + initial_wolves = 50 + + sheep_reproduce = 0.04 + wolf_reproduce = 0.05 + + wolf_gain_from_food = 20 + + grass = False + grass_regrowth_time = 30 + sheep_gain_from_food = 4 + + description = ( + "A model for simulating wolf and sheep (predator-prey) ecosystem modelling." + ) + + def __init__( + self, + width=20, + height=20, + initial_sheep=100, + initial_wolves=50, + sheep_reproduce=0.04, + wolf_reproduce=0.05, + wolf_gain_from_food=20, + grass=False, + grass_regrowth_time=30, + sheep_gain_from_food=4, + seed=None, + ): + """ + Create a new Wolf-Sheep model with the given parameters. + + Args: + initial_sheep: Number of sheep to start with + initial_wolves: Number of wolves to start with + sheep_reproduce: Probability of each sheep reproducing each step + wolf_reproduce: Probability of each wolf reproducing each step + wolf_gain_from_food: Energy a wolf gains from eating a sheep + grass: Whether to have the sheep eat grass for energy + grass_regrowth_time: How long it takes for a grass patch to regrow + once it is eaten + sheep_gain_from_food: Energy sheep gain from grass, if enabled. + """ + super().__init__(seed=seed) + # Set parameters + self.width = width + self.height = height + self.initial_sheep = initial_sheep + self.initial_wolves = initial_wolves + self.grass = grass + self.grass_regrowth_time = grass_regrowth_time + + self.grid = OrthogonalMooreGrid((self.width, self.height), torus=True) + + 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, + } + + self.datacollector = mesa.DataCollector(collectors) + + # Create sheep: + for i in range(self.initial_sheep): + x = self.random.randrange(self.width) + y = self.random.randrange(self.height) + energy = self.random.randrange(2 * self.sheep_gain_from_food) + Sheep( + self, energy, sheep_reproduce, sheep_gain_from_food, self.grid[(x, y)] + ) + + # Create wolves + for _ in range(self.initial_wolves): + x = self.random.randrange(self.width) + y = self.random.randrange(self.height) + energy = self.random.randrange(2 * self.wolf_gain_from_food) + Wolf(self, energy, wolf_reproduce, wolf_gain_from_food, self.grid[(x, y)]) + + # Create grass patches + if self.grass: + for cell in self.grid.all_cells: + fully_grown = self.random.choice([True, False]) + + if fully_grown: + countdown = self.grass_regrowth_time + else: + countdown = self.random.randrange(self.grass_regrowth_time) + + patch = GrassPatch(self, fully_grown, countdown) + patch.cell = cell + + self.running = True + self.datacollector.collect(self) + + def step(self): + # This replicated the behavior of the old RandomActivationByType scheduler + # when using step(shuffle_types=True, shuffle_agents=True). + # Conceptually, it can be argued that this should be modelled differently. + self.random.shuffle(self.agent_types) + for agent_type in self.agent_types: + self.agents_by_type[agent_type].shuffle_do("step") + + # collect data + self.datacollector.collect(self) + + def run_model(self, step_count=200): + for i in range(step_count): + self.step() 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) From 8ad2d7e27dbef2724fbdd7a51b3aaec0ad400207 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 24 Oct 2024 13:49:28 +0000 Subject: [PATCH 3/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/examples/advanced/sugarscape_g1mt/agents.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mesa/examples/advanced/sugarscape_g1mt/agents.py b/mesa/examples/advanced/sugarscape_g1mt/agents.py index f4e6c95f349..74b33e91c85 100644 --- a/mesa/examples/advanced/sugarscape_g1mt/agents.py +++ b/mesa/examples/advanced/sugarscape_g1mt/agents.py @@ -1,4 +1,3 @@ - import math from mesa.experimental.cell_space import CellAgent From eff76bbbdc2228075d56acb2d06eba472cce754d Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Thu, 24 Oct 2024 15:57:37 +0200 Subject: [PATCH 4/6] remove path trick --- mesa/examples/advanced/wolf_sheep/app.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/mesa/examples/advanced/wolf_sheep/app.py b/mesa/examples/advanced/wolf_sheep/app.py index ccd49a2a216..426610d6899 100644 --- a/mesa/examples/advanced/wolf_sheep/app.py +++ b/mesa/examples/advanced/wolf_sheep/app.py @@ -1,9 +1,3 @@ -import sys -import os.path -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../'))) - - - from mesa.examples.advanced.wolf_sheep.agents import Wolf, Sheep, GrassPatch from mesa.examples.advanced.wolf_sheep.model import WolfSheep from mesa.visualization import ( From df43226337de01b5ce7610217c16bbaaefc38b1c Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Thu, 24 Oct 2024 17:38:51 +0200 Subject: [PATCH 5/6] remove old folder --- .../wolf_sheep/wolf_sheep/__init__.py | 0 .../advanced/wolf_sheep/wolf_sheep/agents.py | 102 ------------- .../advanced/wolf_sheep/wolf_sheep/model.py | 136 ------------------ .../wolf_sheep/wolf_sheep/resources/sheep.png | Bin 1322 -> 0 bytes .../wolf_sheep/wolf_sheep/resources/wolf.png | Bin 1473 -> 0 bytes .../advanced/wolf_sheep/wolf_sheep/server.py | 78 ---------- 6 files changed, 316 deletions(-) delete mode 100644 mesa/examples/advanced/wolf_sheep/wolf_sheep/__init__.py delete mode 100644 mesa/examples/advanced/wolf_sheep/wolf_sheep/agents.py delete mode 100644 mesa/examples/advanced/wolf_sheep/wolf_sheep/model.py delete mode 100644 mesa/examples/advanced/wolf_sheep/wolf_sheep/resources/sheep.png delete mode 100644 mesa/examples/advanced/wolf_sheep/wolf_sheep/resources/wolf.png delete mode 100644 mesa/examples/advanced/wolf_sheep/wolf_sheep/server.py 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/agents.py b/mesa/examples/advanced/wolf_sheep/wolf_sheep/agents.py deleted file mode 100644 index 8e71988bc9a..00000000000 --- a/mesa/examples/advanced/wolf_sheep/wolf_sheep/agents.py +++ /dev/null @@ -1,102 +0,0 @@ -from mesa.experimental.cell_space import CellAgent, FixedAgent - - -class Animal(CellAgent): - """The base animal class.""" - - def __init__(self, model, energy, p_reproduce, energy_from_food, cell): - """Initializes an animal. - - Args: - model: a model instance - energy: starting amount of energy - p_reproduce: probability of sexless reproduction - energy_from_food: energy obtained from 1 unit of food - cell: the cell in which the animal starts - """ - super().__init__(model) - self.energy = energy - self.p_reproduce = p_reproduce - self.energy_from_food = energy_from_food - self.cell = cell - - def spawn_offspring(self): - """Create offspring.""" - self.energy /= 2 - self.__class__( - self.model, - self.energy, - self.p_reproduce, - self.energy_from_food, - self.cell, - ) - - def feed(self): ... - - def step(self): - """One step of the agent.""" - self.cell = self.cell.neighborhood.select_random_cell() - self.energy -= 1 - - self.feed() - - if self.energy < 0: - self.remove() - elif self.random.random() < self.p_reproduce: - self.spawn_offspring() - - -class Sheep(Animal): - """A sheep that walks around, reproduces (asexually) and gets eaten.""" - - def feed(self): - """If possible eat the food in the current location.""" - # If there is grass available, eat it - if self.model.grass: - grass_patch = next( - obj for obj in self.cell.agents if isinstance(obj, GrassPatch) - ) - if grass_patch.fully_grown: - self.energy += self.energy_from_food - grass_patch.fully_grown = False - - -class Wolf(Animal): - """A wolf that walks around, reproduces (asexually) and eats sheep.""" - - def feed(self): - """If possible eat the food in the current location.""" - sheep = [obj for obj in self.cell.agents if isinstance(obj, Sheep)] - if len(sheep) > 0: - sheep_to_eat = self.random.choice(sheep) - self.energy += self.energy_from_food - - # Kill the sheep - sheep_to_eat.remove() - - -class GrassPatch(FixedAgent): - """ - A patch of grass that grows at a fixed rate and it is eaten by sheep - """ - - def __init__(self, model, fully_grown, countdown): - """ - Creates a new patch of grass - - Args: - grown: (boolean) Whether the patch of grass is fully grown or not - countdown: Time for the patch of grass to be fully grown again - """ - super().__init__(model) - self.fully_grown = fully_grown - self.countdown = countdown - - def step(self): - if not self.fully_grown: - if self.countdown <= 0: - # Set as fully grown - self.fully_grown = True - self.countdown = self.model.grass_regrowth_time - else: - self.countdown -= 1 diff --git a/mesa/examples/advanced/wolf_sheep/wolf_sheep/model.py b/mesa/examples/advanced/wolf_sheep/wolf_sheep/model.py deleted file mode 100644 index 5b43b7912e1..00000000000 --- a/mesa/examples/advanced/wolf_sheep/wolf_sheep/model.py +++ /dev/null @@ -1,136 +0,0 @@ -""" -Wolf-Sheep Predation Model -================================ - -Replication of the model found in NetLogo: - Wilensky, U. (1997). NetLogo Wolf Sheep Predation model. - http://ccl.northwestern.edu/netlogo/models/WolfSheepPredation. - Center for Connected Learning and Computer-Based Modeling, - Northwestern University, Evanston, IL. -""" - -import mesa -from mesa.experimental.cell_space import OrthogonalMooreGrid - -from .agents import GrassPatch, Sheep, Wolf - - -class WolfSheep(mesa.Model): - """ - Wolf-Sheep Predation Model - """ - - height = 20 - width = 20 - - initial_sheep = 100 - initial_wolves = 50 - - sheep_reproduce = 0.04 - wolf_reproduce = 0.05 - - wolf_gain_from_food = 20 - - grass = False - grass_regrowth_time = 30 - sheep_gain_from_food = 4 - - description = ( - "A model for simulating wolf and sheep (predator-prey) ecosystem modelling." - ) - - def __init__( - self, - width=20, - height=20, - initial_sheep=100, - initial_wolves=50, - sheep_reproduce=0.04, - wolf_reproduce=0.05, - wolf_gain_from_food=20, - grass=False, - grass_regrowth_time=30, - sheep_gain_from_food=4, - seed=None, - ): - """ - Create a new Wolf-Sheep model with the given parameters. - - Args: - initial_sheep: Number of sheep to start with - initial_wolves: Number of wolves to start with - sheep_reproduce: Probability of each sheep reproducing each step - wolf_reproduce: Probability of each wolf reproducing each step - wolf_gain_from_food: Energy a wolf gains from eating a sheep - grass: Whether to have the sheep eat grass for energy - grass_regrowth_time: How long it takes for a grass patch to regrow - once it is eaten - sheep_gain_from_food: Energy sheep gain from grass, if enabled. - """ - super().__init__(seed=None) - # Set parameters - self.width = width - self.height = height - self.initial_sheep = initial_sheep - self.initial_wolves = initial_wolves - self.grass = grass - self.grass_regrowth_time = grass_regrowth_time - - self.grid = OrthogonalMooreGrid((self.width, self.height), torus=True) - - collectors = { - "Wolves": lambda m: len(m.agents_by_type[Wolf]), - "Sheep": lambda m: len(m.agents_by_type[Sheep]), - } - - if grass: - collectors["Grass"] = lambda m: len(m.agents_by_type[GrassPatch]) - - self.datacollector = mesa.DataCollector(collectors) - - # Create sheep: - for i in range(self.initial_sheep): - x = self.random.randrange(self.width) - y = self.random.randrange(self.height) - energy = self.random.randrange(2 * self.sheep_gain_from_food) - Sheep( - self, energy, sheep_reproduce, sheep_gain_from_food, self.grid[(x, y)] - ) - - # Create wolves - for _ in range(self.initial_wolves): - x = self.random.randrange(self.width) - y = self.random.randrange(self.height) - energy = self.random.randrange(2 * self.wolf_gain_from_food) - Wolf(self, energy, wolf_reproduce, wolf_gain_from_food, self.grid[(x, y)]) - - # Create grass patches - if self.grass: - for cell in self.grid.all_cells: - fully_grown = self.random.choice([True, False]) - - if fully_grown: - countdown = self.grass_regrowth_time - else: - countdown = self.random.randrange(self.grass_regrowth_time) - - patch = GrassPatch(self, fully_grown, countdown) - patch.cell = cell - - self.running = True - self.datacollector.collect(self) - - def step(self): - # This replicated the behavior of the old RandomActivationByType scheduler - # when using step(shuffle_types=True, shuffle_agents=True). - # Conceptually, it can be argued that this should be modelled differently. - self.random.shuffle(self.agent_types) - for agent_type in self.agent_types: - self.agents_by_type[agent_type].shuffle_do("step") - - # collect data - self.datacollector.collect(self) - - def run_model(self, step_count=200): - for i in range(step_count): - self.step() 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 dfb81b0e5d73bb9f41e4b788934b212b1d544818..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1322 zcmV+_1=aeAP)Nkl`0Z;)@0i*waLm3IY{CDgY_~DgY_~eJ8)u#Pa%QZ#O|v zEJc^Qwa4@3=gru=rz`@0O1dlQholdZ-lqSV@<`G{Nw?Gd z-)YXt(jMK#BcCKaC@6%ouO!`1KuS7)mh@N>a~BzNfxMA~H-1Wbk*vI*{^$FRq}?A$ z$CAFL?|6>yJjZ*0S!DD8AuMjC@Ij$(lD zuTu_`HX+W*kJ4~XkT<1)S6)iOv&=ELv$^3m1q8w502XC@OqL@jh<(O0N$+Qx5jn^& z4Zx9OL-{iW#1#P{4TfV7dWR$Or-1xQ?moys%yhtH z_~oGo7=YbLV924aF$LtWB;vJ1uU&$7m=wvNJA6w(NK{w}_jX%k2gOTEA(3|@#xxL= zhedK0S~Ii&z`oQYlM2Rzy2%}r0r|=xA0eG069NQRl6VNEz=+;{;)liholDE;q zog>x;HHUQUk&_6-yuM+9wY4tDC&5Yh?*1wjR2*;^fml#4Yb=d5o>4F=ml&-Bz6+ziS(l;k@Gj;#~V2z|T=sykLZozBanCwz#8NU zbO;TD9;@^es-Deja~xvq2a{rb{(LjTTApEWJhIU#Kaa$gA>VAt^9c!xfl9T?O)NP= zu5^vs3DE*T@A^`!y1uZ81*@b)JhB`@ET#n9B?PW zC%=3BkT*)9Weet1Y#=X$hJpJI6cOw){`k9=6aHp*v~ie@tEW!AE^AbW-|gIG`Y+2 zKt5NrH5P3a#oB8HD_hzqDn_V`0P%b)N8aF}*6DDTM_3e<%@f+Jgc5$$Q1gi2aB=d) zBfe;09|PIxuDRwBZ|;qv>mwNuhAodc!6R@KXI=c9eQ90;;$tKQ&F?KdKQb59yamLR zjnY(pDA{sX2NDw=04QJ}RB|V%6{&?*l;yi#=Et{ItA&gS^fkh`i^ahmbPn`}9b&iC@#=Ei!4*k_RMFTF0xMI;734eT27zp^XG?bg zSJY04k5Yij1FPBBv=%l3LhpL}c28v=?0qr{K6!l`y#J-rfUWqmscGCG?eF*6AJ&~f z`$iZbWWK z1U^5Sn%VQ0v1h?#L%s-*X)~%dLCeM49^f$s6<}QR!?izKHiSTV{xjq9aU+j3P89(< z)yF|1;3?F6EIt0lnEA@^ttIeO1ekW?fD)(|XVm>ZI9EZYjceHbMF0an&l5?+REIoZ z5%}gr)_#@Nd_<`+y`-5od#yHpQ;6Y2x+W1IYy0G^wL+^i?^I8j*;L;19|2n~lRLqx z2{=HHPMG-921_UvXu2uHtDpy?67*SR7|G81ReB2X*Z(cliwz}E zDPX#PDElGUmtwzwc`OXJ1fEE$WTMk6fqX$yA0S|qi*$@~*aXy-#=`^QA)eq@ah>#< zLNBN6${{e(xn(m8CZ^4FaM6}|JD&@?x4X?^&Tla=E1y0aS-ybZ0tg*$JeB~zT+_ql zKXkBQa-RxmBVHCHJuiMS)(?1Q-PhH=M+zMfBqZ} z-GGyLz#^b&E9?Y7ZV#-E8Kt{6RSF0dI6z^*dTd3>xivD%_P=ffZhd!HA7Y(}a!lH; z16EgU>KH=ES)LHa Date: Thu, 24 Oct 2024 17:41:09 +0200 Subject: [PATCH 6/6] Update mesa/examples/advanced/sugarscape_g1mt/Readme.md Co-authored-by: Ewout ter Hoeven --- mesa/examples/advanced/sugarscape_g1mt/Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesa/examples/advanced/sugarscape_g1mt/Readme.md b/mesa/examples/advanced/sugarscape_g1mt/Readme.md index c5ad388b571..2759450b959 100644 --- a/mesa/examples/advanced/sugarscape_g1mt/Readme.md +++ b/mesa/examples/advanced/sugarscape_g1mt/Readme.md @@ -53,7 +53,7 @@ Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and p ## Files * `model.py`: The Sugarscape Constant Growback with Traders model. -* `agent.py`: Defines the Trader agent class and the Resource agent class which contains an amount of sugar and spice. +* `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.