From 9d4781eba65ee0693d5f69572232778dee6c04c9 Mon Sep 17 00:00:00 2001 From: jackiekazil Date: Wed, 16 Nov 2022 12:36:29 -0500 Subject: [PATCH 001/116] Move examples from mesa repo to mesa-examples repo. --- .../Epstein Civil Violence.ipynb | 119 +++++++++++ .../advanced/epstein_civil_violence/Readme.md | 33 ++++ .../epstein_civil_violence/agent.py | 184 ++++++++++++++++++ .../epstein_civil_violence/model.py | 141 ++++++++++++++ .../epstein_civil_violence/portrayal.py | 33 ++++ .../epstein_civil_violence/server.py | 54 +++++ .../epstein_civil_violence/requirements.txt | 3 + .../advanced/epstein_civil_violence/run.py | 3 + examples/advanced/wolf_sheep/Readme.md | 57 ++++++ examples/advanced/wolf_sheep/requirements.txt | 1 + examples/advanced/wolf_sheep/run.py | 3 + .../wolf_sheep/wolf_sheep/__init__.py | 0 .../advanced/wolf_sheep/wolf_sheep/agents.py | 120 ++++++++++++ .../advanced/wolf_sheep/wolf_sheep/model.py | 166 ++++++++++++++++ .../wolf_sheep/wolf_sheep/random_walk.py | 41 ++++ .../wolf_sheep/wolf_sheep/resources/sheep.png | Bin 0 -> 1322 bytes .../wolf_sheep/wolf_sheep/resources/wolf.png | Bin 0 -> 1473 bytes .../wolf_sheep/wolf_sheep/scheduler.py | 28 +++ .../advanced/wolf_sheep/wolf_sheep/server.py | 79 ++++++++ .../wolf_sheep/wolf_sheep/test_random_walk.py | 82 ++++++++ 20 files changed, 1147 insertions(+) create mode 100644 examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb create mode 100644 examples/advanced/epstein_civil_violence/Readme.md create mode 100644 examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py create mode 100644 examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py create mode 100644 examples/advanced/epstein_civil_violence/epstein_civil_violence/portrayal.py create mode 100644 examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py create mode 100644 examples/advanced/epstein_civil_violence/requirements.txt create mode 100644 examples/advanced/epstein_civil_violence/run.py create mode 100644 examples/advanced/wolf_sheep/Readme.md create mode 100644 examples/advanced/wolf_sheep/requirements.txt create mode 100644 examples/advanced/wolf_sheep/run.py create mode 100644 examples/advanced/wolf_sheep/wolf_sheep/__init__.py create mode 100644 examples/advanced/wolf_sheep/wolf_sheep/agents.py create mode 100644 examples/advanced/wolf_sheep/wolf_sheep/model.py create mode 100644 examples/advanced/wolf_sheep/wolf_sheep/random_walk.py create mode 100644 examples/advanced/wolf_sheep/wolf_sheep/resources/sheep.png create mode 100644 examples/advanced/wolf_sheep/wolf_sheep/resources/wolf.png create mode 100644 examples/advanced/wolf_sheep/wolf_sheep/scheduler.py create mode 100644 examples/advanced/wolf_sheep/wolf_sheep/server.py create mode 100644 examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py diff --git a/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb b/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb new file mode 100644 index 00000000000..2fe5ed25879 --- /dev/null +++ b/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb @@ -0,0 +1,119 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This example implements the first model from \"Modeling civil violence: An agent-based computational approach,\" by Joshua Epstein. The paper (pdf) can be found [here](http://www.uvm.edu/~pdodds/files/papers/others/2002/epstein2002a.pdf).\n", + "\n", + "The model consists of two types of agents: \"Citizens\" (called \"Agents\" in the paper) and \"Cops.\" Agents decide whether or not to rebel by weighing their unhappiness ('grievance') against the risk of rebelling, which they estimate by comparing the local ratio of rebels to cops. \n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "%matplotlib inline\n", + "\n", + "from epstein_civil_violence.agent import Citizen, Cop\n", + "from epstein_civil_violence.model import EpsteinCivilViolence" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "model = EpsteinCivilViolence(\n", + " height=40,\n", + " width=40,\n", + " citizen_density=0.7,\n", + " cop_density=0.074,\n", + " citizen_vision=7,\n", + " cop_vision=7,\n", + " legitimacy=0.8,\n", + " max_jail_term=1000,\n", + " max_iters=1000,\n", + ") # cap the number of steps the model takes\n", + "model.run_model()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The model's data collector counts the number of citizens who are Active (in rebellion), Jailed, or Quiescent after each step." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "model_out = model.datacollector.get_model_vars_dataframe()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "ax = model_out.plot()\n", + "ax.set_title(\"Citizen Condition Over Time\")\n", + "ax.set_xlabel(\"Step\")\n", + "ax.set_ylabel(\"Number of Citizens\")\n", + "_ = ax.legend(bbox_to_anchor=(1.35, 1.025))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/examples/advanced/epstein_civil_violence/Readme.md b/examples/advanced/epstein_civil_violence/Readme.md new file mode 100644 index 00000000000..2e715b33b99 --- /dev/null +++ b/examples/advanced/epstein_civil_violence/Readme.md @@ -0,0 +1,33 @@ +# Epstein Civil Violence Model + +## Summary + +This model is based on Joshua Epstein's simulation of how civil unrest grows and is suppressed. Citizen agents wander the grid randomly, and are endowed with individual risk aversion and hardship levels; there is also a universal regime legitimacy value. There are also Cop agents, who work on behalf of the regime. Cops arrest Citizens who are actively rebelling; Citizens decide whether to rebel based on their hardship and the regime legitimacy, and their perceived probability of arrest. + +The model generates mass uprising as self-reinforcing processes: if enough agents are rebelling, the probability of any individual agent being arrested is reduced, making more agents more likely to join the uprising. However, the more rebelling Citizens the Cops arrest, the less likely additional agents become to join. + +## How to Run + +To run the model interactively, run ``EpsteinCivilViolenceServer.py`` in this directory. e.g. + +``` + $ python EpsteinCivilViolenceServer.py +``` + +Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run. + +## Files + +* ``EpsteinCivilViolence.py``: Core model and agent code. +* ``EpsteinCivilViolenceServer.py``: Sets up the interactive visualization. +* ``Epstein Civil Violence.ipynb``: Jupyter notebook conducting some preliminary analysis of the model. + +## Further Reading + +This model is based adapted from: + +[Epstein, J. “Modeling civil violence: An agent-based computational approach”, Proceedings of the National Academy of Sciences, Vol. 99, Suppl. 3, May 14, 2002](http://www.pnas.org/content/99/suppl.3/7243.short) + +A similar model is also included with NetLogo: + +Wilensky, U. (2004). NetLogo Rebellion model. http://ccl.northwestern.edu/netlogo/models/Rebellion. Center for Connected Learning and Computer-Based Modeling, Northwestern University, Evanston, IL. diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py new file mode 100644 index 00000000000..358b4484d44 --- /dev/null +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py @@ -0,0 +1,184 @@ +import math + +import mesa + + +class Citizen(mesa.Agent): + """ + A member of the general population, may or may not be in active rebellion. + Summary of rule: If grievance - risk > threshold, rebel. + + Attributes: + unique_id: unique int + x, y: Grid coordinates + hardship: Agent's 'perceived hardship (i.e., physical or economic + privation).' Exogenous, drawn from U(0,1). + regime_legitimacy: Agent's perception of regime legitimacy, equal + across agents. Exogenous. + risk_aversion: Exogenous, drawn from U(0,1). + threshold: if (grievance - (risk_aversion * arrest_probability)) > + threshold, go/remain Active + vision: number of cells in each direction (N, S, E and W) that agent + can inspect + condition: Can be "Quiescent" or "Active;" deterministic function of + greivance, perceived risk, and + grievance: deterministic function of hardship and regime_legitimacy; + how aggrieved is agent at the regime? + arrest_probability: agent's assessment of arrest probability, given + rebellion + """ + + def __init__( + self, + unique_id, + model, + pos, + hardship, + regime_legitimacy, + risk_aversion, + threshold, + vision, + ): + """ + Create a new Citizen. + Args: + unique_id: unique int + x, y: Grid coordinates + hardship: Agent's 'perceived hardship (i.e., physical or economic + privation).' Exogenous, drawn from U(0,1). + regime_legitimacy: Agent's perception of regime legitimacy, equal + across agents. Exogenous. + risk_aversion: Exogenous, drawn from U(0,1). + threshold: if (grievance - (risk_aversion * arrest_probability)) > + threshold, go/remain Active + vision: number of cells in each direction (N, S, E and W) that + agent can inspect. Exogenous. + model: model instance + """ + super().__init__(unique_id, model) + self.breed = "citizen" + self.pos = pos + self.hardship = hardship + self.regime_legitimacy = regime_legitimacy + self.risk_aversion = risk_aversion + self.threshold = threshold + self.condition = "Quiescent" + self.vision = vision + self.jail_sentence = 0 + self.grievance = self.hardship * (1 - self.regime_legitimacy) + self.arrest_probability = None + + def step(self): + """ + Decide whether to activate, then move if applicable. + """ + if self.jail_sentence: + self.jail_sentence -= 1 + return # no other changes or movements if agent is in jail. + self.update_neighbors() + self.update_estimated_arrest_probability() + net_risk = self.risk_aversion * self.arrest_probability + if ( + self.condition == "Quiescent" + and (self.grievance - net_risk) > self.threshold + ): + self.condition = "Active" + elif ( + self.condition == "Active" and (self.grievance - net_risk) <= self.threshold + ): + self.condition = "Quiescent" + if self.model.movement and self.empty_neighbors: + new_pos = self.random.choice(self.empty_neighbors) + self.model.grid.move_agent(self, new_pos) + + def update_neighbors(self): + """ + Look around and see who my neighbors are + """ + self.neighborhood = self.model.grid.get_neighborhood( + self.pos, moore=False, radius=1 + ) + self.neighbors = self.model.grid.get_cell_list_contents(self.neighborhood) + self.empty_neighbors = [ + c for c in self.neighborhood if self.model.grid.is_cell_empty(c) + ] + + def update_estimated_arrest_probability(self): + """ + Based on the ratio of cops to actives in my neighborhood, estimate the + p(Arrest | I go active). + """ + cops_in_vision = len([c for c in self.neighbors if c.breed == "cop"]) + actives_in_vision = 1.0 # citizen counts herself + for c in self.neighbors: + if ( + c.breed == "citizen" + and c.condition == "Active" + and c.jail_sentence == 0 + ): + actives_in_vision += 1 + self.arrest_probability = 1 - math.exp( + -1 * self.model.arrest_prob_constant * (cops_in_vision / actives_in_vision) + ) + + +class Cop(mesa.Agent): + """ + A cop for life. No defection. + Summary of rule: Inspect local vision and arrest a random active agent. + + Attributes: + unique_id: unique int + x, y: Grid coordinates + vision: number of cells in each direction (N, S, E and W) that cop is + able to inspect + """ + + def __init__(self, unique_id, model, pos, vision): + """ + Create a new Cop. + Args: + unique_id: unique int + x, y: Grid coordinates + vision: number of cells in each direction (N, S, E and W) that + agent can inspect. Exogenous. + model: model instance + """ + super().__init__(unique_id, model) + self.breed = "cop" + self.pos = pos + self.vision = vision + + def step(self): + """ + Inspect local vision and arrest a random active agent. Move if + applicable. + """ + self.update_neighbors() + active_neighbors = [] + for agent in self.neighbors: + if ( + agent.breed == "citizen" + and agent.condition == "Active" + and agent.jail_sentence == 0 + ): + active_neighbors.append(agent) + if active_neighbors: + arrestee = self.random.choice(active_neighbors) + sentence = self.random.randint(0, self.model.max_jail_term) + arrestee.jail_sentence = sentence + if self.model.movement and self.empty_neighbors: + new_pos = self.random.choice(self.empty_neighbors) + self.model.grid.move_agent(self, new_pos) + + def update_neighbors(self): + """ + Look around and see who my neighbors are. + """ + self.neighborhood = self.model.grid.get_neighborhood( + self.pos, moore=False, radius=1 + ) + self.neighbors = self.model.grid.get_cell_list_contents(self.neighborhood) + self.empty_neighbors = [ + c for c in self.neighborhood if self.model.grid.is_cell_empty(c) + ] diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py new file mode 100644 index 00000000000..760767c26d9 --- /dev/null +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py @@ -0,0 +1,141 @@ +import mesa + +from .agent import Cop, Citizen + + +class EpsteinCivilViolence(mesa.Model): + """ + Model 1 from "Modeling civil violence: An agent-based computational + approach," by Joshua Epstein. + http://www.pnas.org/content/99/suppl_3/7243.full + Attributes: + height: grid height + width: grid width + citizen_density: approximate % of cells occupied by citizens. + cop_density: approximate % of cells occupied by cops. + citizen_vision: number of cells in each direction (N, S, E and W) that + citizen can inspect + cop_vision: number of cells in each direction (N, S, E and W) that cop + can inspect + legitimacy: (L) citizens' perception of regime legitimacy, equal + across all citizens + max_jail_term: (J_max) + active_threshold: if (grievance - (risk_aversion * arrest_probability)) + > threshold, citizen rebels + arrest_prob_constant: set to ensure agents make plausible arrest + probability estimates + movement: binary, whether agents try to move at step end + max_iters: model may not have a natural stopping point, so we set a + max. + """ + + def __init__( + self, + width=40, + height=40, + citizen_density=0.7, + cop_density=0.074, + citizen_vision=7, + cop_vision=7, + legitimacy=0.8, + max_jail_term=1000, + active_threshold=0.1, + arrest_prob_constant=2.3, + movement=True, + max_iters=1000, + ): + super().__init__() + self.width = width + self.height = height + self.citizen_density = citizen_density + self.cop_density = cop_density + self.citizen_vision = citizen_vision + self.cop_vision = cop_vision + self.legitimacy = legitimacy + self.max_jail_term = max_jail_term + self.active_threshold = active_threshold + self.arrest_prob_constant = arrest_prob_constant + self.movement = movement + self.max_iters = max_iters + self.iteration = 0 + self.schedule = mesa.time.RandomActivation(self) + self.grid = mesa.space.Grid(width, height, torus=True) + model_reporters = { + "Quiescent": lambda m: self.count_type_citizens(m, "Quiescent"), + "Active": lambda m: self.count_type_citizens(m, "Active"), + "Jailed": self.count_jailed, + } + agent_reporters = { + "x": lambda a: a.pos[0], + "y": lambda a: a.pos[1], + "breed": lambda a: a.breed, + "jail_sentence": lambda a: getattr(a, "jail_sentence", None), + "condition": lambda a: getattr(a, "condition", None), + "arrest_probability": lambda a: getattr(a, "arrest_probability", None), + } + self.datacollector = mesa.DataCollector( + model_reporters=model_reporters, agent_reporters=agent_reporters + ) + unique_id = 0 + if self.cop_density + self.citizen_density > 1: + raise ValueError("Cop density + citizen density must be less than 1") + for (contents, x, y) in self.grid.coord_iter(): + if self.random.random() < self.cop_density: + cop = Cop(unique_id, self, (x, y), vision=self.cop_vision) + unique_id += 1 + self.grid[x][y] = cop + self.schedule.add(cop) + elif self.random.random() < (self.cop_density + self.citizen_density): + citizen = Citizen( + unique_id, + self, + (x, y), + hardship=self.random.random(), + regime_legitimacy=self.legitimacy, + risk_aversion=self.random.random(), + threshold=self.active_threshold, + vision=self.citizen_vision, + ) + unique_id += 1 + self.grid[x][y] = citizen + self.schedule.add(citizen) + + self.running = True + self.datacollector.collect(self) + + def step(self): + """ + Advance the model by one step and collect data. + """ + self.schedule.step() + # collect data + self.datacollector.collect(self) + self.iteration += 1 + if self.iteration > self.max_iters: + self.running = False + + @staticmethod + def count_type_citizens(model, condition, exclude_jailed=True): + """ + Helper method to count agents by Quiescent/Active. + """ + count = 0 + for agent in model.schedule.agents: + if agent.breed == "cop": + continue + if exclude_jailed and agent.jail_sentence: + continue + if agent.condition == condition: + count += 1 + return count + + @staticmethod + def count_jailed(model): + """ + Helper method to count jailed agents. + """ + count = 0 + for agent in model.schedule.agents: + if agent.breed == "citizen" and agent.jail_sentence: + count += 1 + return count diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/portrayal.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/portrayal.py new file mode 100644 index 00000000000..80134adcc79 --- /dev/null +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/portrayal.py @@ -0,0 +1,33 @@ +from .agent import Citizen, Cop + +COP_COLOR = "#000000" +AGENT_QUIET_COLOR = "#0066CC" +AGENT_REBEL_COLOR = "#CC0000" +JAIL_COLOR = "#757575" + + +def citizen_cop_portrayal(agent): + if agent is None: + return + + portrayal = { + "Shape": "circle", + "x": agent.pos[0], + "y": agent.pos[1], + "Filled": "true", + } + + if isinstance(agent, Citizen): + color = ( + AGENT_QUIET_COLOR if agent.condition == "Quiescent" else AGENT_REBEL_COLOR + ) + color = JAIL_COLOR if agent.jail_sentence else color + portrayal["Color"] = color + portrayal["r"] = 0.8 + portrayal["Layer"] = 0 + + elif isinstance(agent, Cop): + portrayal["Color"] = COP_COLOR + portrayal["r"] = 0.5 + portrayal["Layer"] = 1 + return portrayal diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py new file mode 100644 index 00000000000..6b835bd2b14 --- /dev/null +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py @@ -0,0 +1,54 @@ +import mesa + +from .model import EpsteinCivilViolence +from .agent import Citizen, Cop + + +COP_COLOR = "#000000" +AGENT_QUIET_COLOR = "#0066CC" +AGENT_REBEL_COLOR = "#CC0000" +JAIL_COLOR = "#757575" + + +def citizen_cop_portrayal(agent): + if agent is None: + return + + portrayal = { + "Shape": "circle", + "x": agent.pos[0], + "y": agent.pos[1], + "Filled": "true", + } + + if type(agent) is Citizen: + color = ( + AGENT_QUIET_COLOR if agent.condition == "Quiescent" else AGENT_REBEL_COLOR + ) + color = JAIL_COLOR if agent.jail_sentence else color + portrayal["Color"] = color + portrayal["r"] = 0.8 + portrayal["Layer"] = 0 + + elif type(agent) is Cop: + portrayal["Color"] = COP_COLOR + portrayal["r"] = 0.5 + portrayal["Layer"] = 1 + return portrayal + + +model_params = dict( + height=40, + width=40, + citizen_density=0.7, + cop_density=0.074, + citizen_vision=7, + cop_vision=7, + legitimacy=0.8, + max_jail_term=1000, +) + +canvas_element = mesa.visualization.CanvasGrid(citizen_cop_portrayal, 40, 40, 480, 480) +server = mesa.visualization.ModularServer( + EpsteinCivilViolence, [canvas_element], "Epstein Civil Violence", model_params +) diff --git a/examples/advanced/epstein_civil_violence/requirements.txt b/examples/advanced/epstein_civil_violence/requirements.txt new file mode 100644 index 00000000000..bcbfbbe220b --- /dev/null +++ b/examples/advanced/epstein_civil_violence/requirements.txt @@ -0,0 +1,3 @@ +jupyter +matplotlib +mesa diff --git a/examples/advanced/epstein_civil_violence/run.py b/examples/advanced/epstein_civil_violence/run.py new file mode 100644 index 00000000000..5aa2644ac3d --- /dev/null +++ b/examples/advanced/epstein_civil_violence/run.py @@ -0,0 +1,3 @@ +from epstein_civil_violence.server import server + +server.launch() diff --git a/examples/advanced/wolf_sheep/Readme.md b/examples/advanced/wolf_sheep/Readme.md new file mode 100644 index 00000000000..30794a6ee67 --- /dev/null +++ b/examples/advanced/wolf_sheep/Readme.md @@ -0,0 +1,57 @@ +# Wolf-Sheep Predation Model + +## Summary + +A simple ecological model, consisting of three agent types: wolves, sheep, and grass. The wolves and the sheep wander around the grid at random. Wolves and sheep both expend energy moving around, and replenish it by eating. Sheep eat grass, and wolves eat sheep if they end up on the same grid cell. + +If wolves and sheep have enough energy, they reproduce, creating a new wolf or sheep (in this simplified model, only one parent is needed for reproduction). The grass on each cell regrows at a constant rate. If any wolves and sheep run out of energy, they die. + +The model is tests and demonstrates several Mesa concepts and features: + - MultiGrid + - Multiple agent types (wolves, sheep, grass) + - Overlay arbitrary text (wolf's energy) on agent's shapes while drawing on CanvasGrid + - Agents inheriting a behavior (random movement) from an abstract parent + - Writing a model composed of multiple files. + - Dynamically adding and removing agents from the schedule + +## Installation + +To install the dependencies use pip and the requirements.txt in this directory. e.g. + +``` + # First, we clone the Mesa repo + $ git clone https://github.com/projectmesa/mesa.git + $ cd mesa + # Then we cd to the example directory + $ cd examples/wolf_sheep + $ pip install -r requirements.txt +``` + +## How to Run + +To run the model interactively, run ``mesa runserver`` in this directory. e.g. + +``` + $ mesa runserver +``` + +Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run. + +## Files + +* ``wolf_sheep/random_walk.py``: This defines the ``RandomWalker`` agent, which implements the behavior of moving randomly across a grid, one cell at a time. Both the Wolf and Sheep agents will inherit from it. +* ``wolf_sheep/test_random_walk.py``: Defines a simple model and a text-only visualization intended to make sure the RandomWalk class was working as expected. This doesn't actually model anything, but serves as an ad-hoc unit test. To run it, ``cd`` into the ``wolf_sheep`` directory and run ``python test_random_walk.py``. You'll see a series of ASCII grids, one per model step, with each cell showing a count of the number of agents in it. +* ``wolf_sheep/agents.py``: Defines the Wolf, Sheep, and GrassPatch agent classes. +* ``wolf_sheep/scheduler.py``: Defines a custom variant on the RandomActivationByType scheduler, where we can define filters for the `get_type_count` function. +* ``wolf_sheep/model.py``: Defines the Wolf-Sheep Predation model itself +* ``wolf_sheep/server.py``: Sets up the interactive visualization server +* ``run.py``: Launches a model visualization server. + +## Further Reading + +This model is closely based on the NetLogo Wolf-Sheep Predation Model: + +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. + +See also the [Lotka–Volterra equations +](https://en.wikipedia.org/wiki/Lotka%E2%80%93Volterra_equations) for an example of a classic differential-equation model with similar dynamics. diff --git a/examples/advanced/wolf_sheep/requirements.txt b/examples/advanced/wolf_sheep/requirements.txt new file mode 100644 index 00000000000..da0b5b956fd --- /dev/null +++ b/examples/advanced/wolf_sheep/requirements.txt @@ -0,0 +1 @@ +mesa diff --git a/examples/advanced/wolf_sheep/run.py b/examples/advanced/wolf_sheep/run.py new file mode 100644 index 00000000000..dc5d367e89d --- /dev/null +++ b/examples/advanced/wolf_sheep/run.py @@ -0,0 +1,3 @@ +from wolf_sheep.server import server + +server.launch() diff --git a/examples/advanced/wolf_sheep/wolf_sheep/__init__.py b/examples/advanced/wolf_sheep/wolf_sheep/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/advanced/wolf_sheep/wolf_sheep/agents.py b/examples/advanced/wolf_sheep/wolf_sheep/agents.py new file mode 100644 index 00000000000..fe62192bf60 --- /dev/null +++ b/examples/advanced/wolf_sheep/wolf_sheep/agents.py @@ -0,0 +1,120 @@ +import mesa +from wolf_sheep.random_walk import RandomWalker + + +class Sheep(RandomWalker): + """ + A sheep that walks around, reproduces (asexually) and gets eaten. + + The init is the same as the RandomWalker. + """ + + energy = None + + def __init__(self, unique_id, pos, model, moore, energy=None): + super().__init__(unique_id, pos, model, moore=moore) + self.energy = energy + + def step(self): + """ + A model step. Move, then eat grass and reproduce. + """ + self.random_move() + living = True + + if self.model.grass: + # Reduce energy + self.energy -= 1 + + # If there is grass available, eat it + this_cell = self.model.grid.get_cell_list_contents([self.pos]) + grass_patch = [obj for obj in this_cell if isinstance(obj, GrassPatch)][0] + if grass_patch.fully_grown: + self.energy += self.model.sheep_gain_from_food + grass_patch.fully_grown = False + + # Death + if self.energy < 0: + self.model.grid.remove_agent(self) + self.model.schedule.remove(self) + living = False + + if living and self.random.random() < self.model.sheep_reproduce: + # Create a new sheep: + if self.model.grass: + self.energy /= 2 + lamb = Sheep( + self.model.next_id(), self.pos, self.model, self.moore, self.energy + ) + self.model.grid.place_agent(lamb, self.pos) + self.model.schedule.add(lamb) + + +class Wolf(RandomWalker): + """ + A wolf that walks around, reproduces (asexually) and eats sheep. + """ + + energy = None + + def __init__(self, unique_id, pos, model, moore, energy=None): + super().__init__(unique_id, pos, model, moore=moore) + self.energy = energy + + def step(self): + self.random_move() + self.energy -= 1 + + # If there are sheep present, eat one + x, y = self.pos + this_cell = self.model.grid.get_cell_list_contents([self.pos]) + sheep = [obj for obj in this_cell if isinstance(obj, Sheep)] + if len(sheep) > 0: + sheep_to_eat = self.random.choice(sheep) + self.energy += self.model.wolf_gain_from_food + + # Kill the sheep + self.model.grid.remove_agent(sheep_to_eat) + self.model.schedule.remove(sheep_to_eat) + + # Death or reproduction + if self.energy < 0: + self.model.grid.remove_agent(self) + self.model.schedule.remove(self) + else: + if self.random.random() < self.model.wolf_reproduce: + # Create a new wolf cub + self.energy /= 2 + cub = Wolf( + self.model.next_id(), self.pos, self.model, self.moore, self.energy + ) + self.model.grid.place_agent(cub, cub.pos) + self.model.schedule.add(cub) + + +class GrassPatch(mesa.Agent): + """ + A patch of grass that grows at a fixed rate and it is eaten by sheep + """ + + def __init__(self, unique_id, pos, 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__(unique_id, model) + self.fully_grown = fully_grown + self.countdown = countdown + self.pos = pos + + 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/examples/advanced/wolf_sheep/wolf_sheep/model.py b/examples/advanced/wolf_sheep/wolf_sheep/model.py new file mode 100644 index 00000000000..2b8fdbdeed1 --- /dev/null +++ b/examples/advanced/wolf_sheep/wolf_sheep/model.py @@ -0,0 +1,166 @@ +""" +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 wolf_sheep.scheduler import RandomActivationByTypeFiltered +from wolf_sheep.agents import Sheep, Wolf, GrassPatch + + +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 + + verbose = False # Print-monitoring + + 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, + ): + """ + 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__() + # Set parameters + self.width = width + self.height = height + self.initial_sheep = initial_sheep + self.initial_wolves = initial_wolves + self.sheep_reproduce = sheep_reproduce + self.wolf_reproduce = wolf_reproduce + self.wolf_gain_from_food = wolf_gain_from_food + self.grass = grass + self.grass_regrowth_time = grass_regrowth_time + self.sheep_gain_from_food = sheep_gain_from_food + + self.schedule = RandomActivationByTypeFiltered(self) + self.grid = mesa.space.MultiGrid(self.width, self.height, torus=True) + self.datacollector = mesa.DataCollector( + { + "Wolves": lambda m: m.schedule.get_type_count(Wolf), + "Sheep": lambda m: m.schedule.get_type_count(Sheep), + "Grass": lambda m: m.schedule.get_type_count( + GrassPatch, lambda x: x.fully_grown + ), + } + ) + + # 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 = Sheep(self.next_id(), (x, y), self, True, energy) + self.grid.place_agent(sheep, (x, y)) + self.schedule.add(sheep) + + # Create wolves + for i 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 = Wolf(self.next_id(), (x, y), self, True, energy) + self.grid.place_agent(wolf, (x, y)) + self.schedule.add(wolf) + + # Create grass patches + if self.grass: + for agent, x, y in self.grid.coord_iter(): + + 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.next_id(), (x, y), self, fully_grown, countdown) + self.grid.place_agent(patch, (x, y)) + self.schedule.add(patch) + + self.running = True + self.datacollector.collect(self) + + def step(self): + self.schedule.step() + # collect data + self.datacollector.collect(self) + if self.verbose: + print( + [ + self.schedule.time, + self.schedule.get_type_count(Wolf), + self.schedule.get_type_count(Sheep), + self.schedule.get_type_count(GrassPatch, lambda x: x.fully_grown), + ] + ) + + def run_model(self, step_count=200): + + if self.verbose: + print("Initial number wolves: ", self.schedule.get_type_count(Wolf)) + print("Initial number sheep: ", self.schedule.get_type_count(Sheep)) + print( + "Initial number grass: ", + self.schedule.get_type_count(GrassPatch, lambda x: x.fully_grown), + ) + + for i in range(step_count): + self.step() + + if self.verbose: + print("") + print("Final number wolves: ", self.schedule.get_type_count(Wolf)) + print("Final number sheep: ", self.schedule.get_type_count(Sheep)) + print( + "Final number grass: ", + self.schedule.get_type_count(GrassPatch, lambda x: x.fully_grown), + ) diff --git a/examples/advanced/wolf_sheep/wolf_sheep/random_walk.py b/examples/advanced/wolf_sheep/wolf_sheep/random_walk.py new file mode 100644 index 00000000000..49219fa7fff --- /dev/null +++ b/examples/advanced/wolf_sheep/wolf_sheep/random_walk.py @@ -0,0 +1,41 @@ +""" +Generalized behavior for random walking, one grid cell at a time. +""" + +import mesa + + +class RandomWalker(mesa.Agent): + """ + Class implementing random walker methods in a generalized manner. + + Not intended to be used on its own, but to inherit its methods to multiple + other agents. + """ + + grid = None + x = None + y = None + moore = True + + def __init__(self, unique_id, pos, model, moore=True): + """ + grid: The MultiGrid object in which the agent lives. + x: The agent's current x coordinate + y: The agent's current y coordinate + moore: If True, may move in all 8 directions. + Otherwise, only up, down, left, right. + """ + super().__init__(unique_id, model) + self.pos = pos + self.moore = moore + + def random_move(self): + """ + Step one cell in any allowable direction. + """ + # Pick the next cell from the adjacent cells. + next_moves = self.model.grid.get_neighborhood(self.pos, self.moore, True) + next_move = self.random.choice(next_moves) + # Now move: + self.model.grid.move_agent(self, next_move) diff --git a/examples/advanced/wolf_sheep/wolf_sheep/resources/sheep.png b/examples/advanced/wolf_sheep/wolf_sheep/resources/sheep.png new file mode 100644 index 0000000000000000000000000000000000000000..dfb81b0e5d73bb9f41e4b788934b212b1d544818 GIT binary patch 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>> scheduler = RandomActivationByTypeFiltered(model) + >>> scheduler.get_type_count(AgentA, lambda agent: agent.some_attribute > 10) + """ + + def get_type_count( + self, + type_class: Type[mesa.Agent], + filter_func: Callable[[mesa.Agent], bool] = None, + ) -> int: + """ + Returns the current number of agents of certain type in the queue that satisfy the filter function. + """ + count = 0 + for agent in self.agents_by_type[type_class].values(): + if filter_func is None or filter_func(agent): + count += 1 + return count diff --git a/examples/advanced/wolf_sheep/wolf_sheep/server.py b/examples/advanced/wolf_sheep/wolf_sheep/server.py new file mode 100644 index 00000000000..bccf4ec849d --- /dev/null +++ b/examples/advanced/wolf_sheep/wolf_sheep/server.py @@ -0,0 +1,79 @@ +import mesa + +from wolf_sheep.agents import Wolf, Sheep, GrassPatch +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 diff --git a/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py b/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py new file mode 100644 index 00000000000..ab3b044ab1e --- /dev/null +++ b/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py @@ -0,0 +1,82 @@ +""" +Testing the RandomWalker by having an ABM composed only of random walker +agents. +""" + +from mesa import Model +from mesa.space import MultiGrid +from mesa.time import RandomActivation +from mesa.visualization.TextVisualization import TextVisualization, TextGrid + +from wolf_sheep.random_walk import RandomWalker + + +class WalkerAgent(RandomWalker): + """ + Agent which only walks around. + """ + + def step(self): + self.random_move() + + +class WalkerWorld(Model): + """ + Random walker world. + """ + + height = 10 + width = 10 + + def __init__(self, width, height, agent_count): + """ + Create a new WalkerWorld. + + Args: + width, height: World size. + agent_count: How many agents to create. + """ + self.height = height + self.width = width + self.grid = MultiGrid(self.width, self.height, torus=True) + self.agent_count = agent_count + + self.schedule = RandomActivation(self) + # Create agents + for i in range(self.agent_count): + x = self.random.randrange(self.width) + y = self.random.randrange(self.height) + a = WalkerAgent(i, (x, y), self, True) + self.schedule.add(a) + self.grid.place_agent(a, (x, y)) + + def step(self): + self.schedule.step() + + +class WalkerWorldViz(TextVisualization): + """ + ASCII Visualization for a WalkerWorld agent. + Each cell is displayed as the number of agents currently in that cell. + """ + + def __init__(self, model): + """ + Create a new visualization for a WalkerWorld instance. + + args: + model: An instance of a WalkerWorld model. + """ + self.model = model + grid_viz = TextGrid(self.model.grid, None) + grid_viz.converter = lambda x: str(len(x)) + self.elements = [grid_viz] + + +if __name__ == "__main__": + print("Testing 10x10 world, with 50 random walkers, for 10 steps.") + model = WalkerWorld(10, 10, 50) + viz = WalkerWorldViz(model) + for i in range(10): + print("Step:", str(i)) + viz.step() From d3aee9226b6ae61239c1623fcdf6f5a10b90aaae Mon Sep 17 00:00:00 2001 From: jackiekazil Date: Wed, 16 Nov 2022 12:36:29 -0500 Subject: [PATCH 002/116] Move examples from mesa repo to mesa-examples repo. --- .../basic/boid_flockers/Flocker Test.ipynb | 114 ++++++++++++++++++ examples/basic/boid_flockers/Readme.md | 34 ++++++ .../boid_flockers/SimpleContinuousModule.py | 32 +++++ .../basic/boid_flockers/boid_flockers/boid.py | 104 ++++++++++++++++ .../boid_flockers/boid_flockers/model.py | 76 ++++++++++++ .../boid_flockers/boid_flockers/server.py | 23 ++++ .../boid_flockers/simple_continuous_canvas.js | 79 ++++++++++++ examples/basic/boid_flockers/requirements.txt | 3 + examples/basic/boid_flockers/run.py | 3 + examples/basic/conways_game_of_life/Readme.md | 30 +++++ .../conways_game_of_life/cell.py | 53 ++++++++ .../conways_game_of_life/model.py | 43 +++++++ .../conways_game_of_life/portrayal.py | 19 +++ .../conways_game_of_life/server.py | 12 ++ .../conways_game_of_life/requirements.txt | 1 + examples/basic/conways_game_of_life/run.py | 3 + 16 files changed, 629 insertions(+) create mode 100644 examples/basic/boid_flockers/Flocker Test.ipynb create mode 100644 examples/basic/boid_flockers/Readme.md create mode 100644 examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py create mode 100644 examples/basic/boid_flockers/boid_flockers/boid.py create mode 100644 examples/basic/boid_flockers/boid_flockers/model.py create mode 100644 examples/basic/boid_flockers/boid_flockers/server.py create mode 100644 examples/basic/boid_flockers/boid_flockers/simple_continuous_canvas.js create mode 100644 examples/basic/boid_flockers/requirements.txt create mode 100644 examples/basic/boid_flockers/run.py create mode 100644 examples/basic/conways_game_of_life/Readme.md create mode 100644 examples/basic/conways_game_of_life/conways_game_of_life/cell.py create mode 100644 examples/basic/conways_game_of_life/conways_game_of_life/model.py create mode 100644 examples/basic/conways_game_of_life/conways_game_of_life/portrayal.py create mode 100644 examples/basic/conways_game_of_life/conways_game_of_life/server.py create mode 100644 examples/basic/conways_game_of_life/requirements.txt create mode 100644 examples/basic/conways_game_of_life/run.py diff --git a/examples/basic/boid_flockers/Flocker Test.ipynb b/examples/basic/boid_flockers/Flocker Test.ipynb new file mode 100644 index 00000000000..664019e51fc --- /dev/null +++ b/examples/basic/boid_flockers/Flocker Test.ipynb @@ -0,0 +1,114 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from boid_flockers.model import BoidFlockers\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def draw_boids(model):\n", + " x_vals = []\n", + " y_vals = []\n", + " for boid in model.schedule.agents:\n", + " x, y = boid.pos\n", + " x_vals.append(x)\n", + " y_vals.append(y)\n", + " fig = plt.figure(figsize=(10, 10))\n", + " ax = fig.add_subplot(111)\n", + " ax.scatter(x_vals, y_vals)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "model = BoidFlockers(100, 100, 100, speed=5, vision=5, separation=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "for i in range(50):\n", + " model.step()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlwAAAJPCAYAAACpXgqFAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3W+snNd9H/jvT1LMUnEVmQwg+Y9iB22MxEbqVt1N04Kt\nuGtLVI3WirCA0wAu1LTJInAXN1rSrSUnqPUi68ZuyPVqF4bRJnaJoPZWTaPYKdwV2TRMs9ggzsZx\n7Ur22img1rIhuiHtMHEU1TbPvpih7tXVveS9d+bcZ56ZzwcYaJ5n5rlz9PDOne+c8zvnqdZaAADo\n57qhGwAAsOwELgCAzgQuAIDOBC4AgM4ELgCAzgQuAIDOdhS4quoDVXW+qj69Yd8/qqrPVNW/r6pf\nrKpv2/DYg1X1+ar6bFXd1aPhAABjsdMerg8muXvTvjNJXttae12SzyV5MEmq6jVJfjDJa6bHvK+q\n9KQBACtrR0GotfbrSb6yad/Z1trl6eZvJnnF9P49ST7cWvt6a+3JJL+b5Pvm01wAgPGZV8/T307y\nsen9lyV5asNjTyV5+ZxeBwBgdGYOXFX1E0n+a2vtQ1d5musHAQAr64ZZDq6qv5XkjUlev2H3F5Pc\ntmH7FdN9m48VwgCA0Wit1V6P3XPgqqq7k/y9JHe01v54w0MfTfKhqjqVyVDidyX5+FY/Y5aGr7qq\neqi19tDQ7Rgr5282zt/eOXezcf5m4/zt3awdRTsKXFX14SR3JPn2qvpCkndmMivxRUnOVlWS/EZr\n7a2ttSeq6pEkTyT5RpK3ttb0ZgEAK2tHgau19kNb7P7AVZ7/riTv2mujAACWifWxxuvc0A0YuXND\nN2Dkzg3dgBE7N3QDRu7c0A0YuXNDN2BV1VCjfVXV1HABAGMwa27RwwUA0JnABQDQmcAFANCZwAUA\n0JnABQDQmcAFANCZwAUA0JnABQDQmcAFANCZwAUA0JnABQDQmcAFANCZwAUA0JnABQDQmcAFANCZ\nwAUA0JnABQDQmcAFANCZwAUA0JnABQDQmcAFANCZwAUA0JnABQDQmcAFANCZwAUA0JnABQDQmcAF\nANCZwAUA0JnABQDQmcAFANCZwAUA0JnABQDQmcAFANCZwAUA0JnABQDQmcAFANCZwAUA0JnABQDQ\nmcAFANCZwAUA0JnABQDQmcAFANCZwAUA0JnABQDQmcAFANCZwAUA0JnABQDQmcAFANCZwAUA0JnA\nBQDQmcAFANCZwAUA0JnABQDQmcAFANCZwAUA0JnABQDQmcAFANCZwAUA0JnABQDQmcAFANCZwAUA\n0JnABQDQmcAFANCZwAUA0JnABQDQmcAFANCZwAUA0JnABQDQmcAFANDZjgJXVX2gqs5X1ac37DtU\nVWer6nNVdaaqbt7w2INV9fmq+mxV3dWj4QAAY7HTHq4PJrl7074Hkpxtrb06ya9Mt1NVr0nyg0le\nMz3mfVWlJw0AWFk7CkKttV9P8pVNu9+U5PT0/ukkPzC9f0+SD7fWvt5aezLJ7yb5vtmbCgAwTrP0\nPN3SWjs/vX8+yS3T+y9L8tSG5z2V5OUzvA4AwKjNZaivtdaStKs9ZR6vAwAwRjfMcOz5qrq1tfZ0\nVb00yZen+7+Y5LYNz3vFdN8LVNVDGzbPtdbOzdAedqiqjiWHTky2Lp5srT02bIsAYLFU1dEkR+f2\n8yadUzt64Vcl+eXW2vdOt9+T5EJr7d1V9UCSm1trD0yL5j+USd3Wy5P8myR/um16oapqrbWa1/8I\nOzMJWzc9mjx8cLJn7XLyzU8mX3uH4AUAW5s1t+yoh6uqPpzkjiTfXlVfSPIPkvx0kkeq6u8keTLJ\nm5OktfZEVT2S5Ikk30jy1s1hiyEdOpGcOpjcd2XHdcn7b08+9ZGqlzyeXHdBrxcAzNeOAldr7Ye2\neegN2zz/XUnetddGsd+uT3LjgeRnbp9srx2pqnuFLgCYj1lquBiliyeTtSNJpkOKb0/y3Ul+Jht6\nvQ4mx08kEbgAYA4sSLpiJr1Wl+5N7v9Ecv/l5C1Jnh26WQCw1HZcND/3F1Y0P7j12YrPHk6uf23y\n8IHJI2vPJJcMKQLA1Ky5ReAiiaUiAObJ39TlI3CxLW94gP23xfI7Rg2WwL4sC8H4rL/hT115w5t5\nCLAvXrD8jolICFzLyxseABaFwAUAc7V5+Z21Z5JLJwdtEoNTw7WkJkOKN34k+TPTmYefejb5o3sM\nKQL0p4Z2+ajh4ipuSPJj0/trQzYEYKVMA5aQxXMEriXy/G9UNx9O3ntgQw3XATVcADAMgWtJvHBW\n4v2Xh20RAHCFwLU0Ns9K/PR1ydrlPHf5JkWbADAUgWtpfW+Sb34yOX5hsn1J0SYADMQsxSWx15WN\nJ8fd/K7kulcmz/6n5GvvmDxidg0AXOHSPjxnt9OQpyHtI+sXrX5bkj/4enLgsgtZA8A6y0LwnN1P\nQz50Ijm1cSZjkp/8luSnYoV6AJif64ZuAADAstPDtdIunkzW/kqSTUOKa5fX95ndCACzUsO1gjbV\nep1Lbv4fFM0DwPYUzbMre53NCACrbNbcooZrhKrqWNXhM5NbHdvd0YdOTMLWfZncHj643psFAPSg\nhmtkXngJn7UjVbWLHqrLh/u1DgDYisA1Opsv4bPzZRsmYe3G106K469Ye1ZRPAD0JXCtlCvrbt2a\n5B8n+VKSbz6ufgsA+hK4RufiyWTtSJKNRe+77KE6Nr2dzvq1FgGAXsxSHKHdXsLn+ceZoQgAu2VZ\nCHZlr2ENAFaZwAUA0Jl1uAAAFpzAtQJmWygVAJiVIcUlp1AeAGY3a26xLMTS2/tCqQDAfBhSBADo\nTA/X0pvHQqkAwCzUcK0Aa28BwGyswwUA0Jl1uAAAFpzABQDQmcAFANCZwAUA0JnABQDQmcAFANCZ\nwAUA0JnABQDQmcAFANCZwAUA0JnABQDQmcAFANCZwAUA0JnABQDQmcAFANCZwAUA0JnABQDQmcAF\nANCZwAUA0JnABQDQmcAFANCZwAUA0JnABQDQmcAFANCZwAUA0JnABQDQmcAFANCZwAUA0JnABQDQ\nmcAFANCZwAUA0JnABQDQmcAFANCZwAUA0NnMgauqHqyqx6vq01X1oao6UFWHqupsVX2uqs5U1c3z\naCwAzENVHas6fGZyq2NDt4flV621vR9c9aok/zbJ97TWnq2qf57kY0lem+T3Wmvvqaq3J3lJa+2B\nTce21lrt+cUBWGmToHToxGTr4snW2mM7P+6mR5OHD072rD2TXLp3p8ezmmbNLTfM+PqXknw9yY1V\n9c0kNyb5UpIHk9wxfc7pJOeSPLDVDwCA3VoPTaeuhKYjVbXD0HToxOS4+67sOJgcP5FE4KKbmYYU\nW2sXk5xM8p8zCVpfba2dTXJLa+389Gnnk9wyUysB4HkOnZj0UN2Xye3hg+u9XbB4Zurhqqo/leT+\nJK9K8vtJ/kVVvWXjc1prraq2HLesqoc2bJ5rrZ2bpT0AcG0XTyZrR5JsHFI8OWiTWDhVdTTJ0bn9\nvBlruH4wyZ2ttR+Zbv/NJN+f5L9P8t+11p6uqpcm+dXW2ndvOlYNFwB7Mmsd1l7rv1hds+aWWQPX\n65L8syT/bZI/TvJPk3w8ySuTXGitvbuqHkhys6J5AOZJaGI/DRq4pg34+5kMoF9O8okkP5LkTyZ5\n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\nMinr9tba9ya5PsnfiHN3NR/M5LNhoy3Pl8/cLW11/uaWWfb95FowdWbfl+R3W2tPtta+nuT/zOTc\nsYXW2tOttU9O7/9hks9kMpnjTUlOT592OskPDNPCxVdVr0jyxiQ/m+TKDB3nbwem34b/cmvtA0nS\nWvtGa+334/ztxKVMvjDdWFU3JLkxyZfi3G2rtfbrSb6yafd258tn7iZbnb95Zpah06wFU3fv5Um+\nsGHbedqh6TfmP5fJm+aW1tr56UPnk9wyULPG4H9N8veSXN6wz/nbme9M8l+q6oNV9Ymq+idV9a1x\n/q6ptXYxk5nw/zmToPXV1trZOHe7td358pm7ezNlli6Bazpe/Oktbn99w3MsmLo3zskeVNWLk/zL\nJD/eWvuDjY9NZ9E6r1uoqr+W5Muttd/Jeu/W8zh/V3VDktuTvK+1dnuSr2XTEJjzt7Wq+lNJ7k/y\nqkw+3F5cVW/Z+Bznbnd2cL6cy23MI7PMsizE9q/Y2p1Xe3y6YOobk7x+w+4vJrltw/Yrpvt4vs3n\n6bY8P2WzSVV9SyZh6+dba7803X2+qm5trT1dVS9N8uXhWrjQ/lKSN1XVG5P8iSQ3VdXPx/nbqaeS\nPNVa+63p9i9kUgPytPN3Tf9Nkv+ntXYhSarqF5P8xTh3u7Xde9Vn7g7NK7MMMUvx7kyGJ+65sjr9\n1EeT/I2qelFVfWeS70ry8f1u3wj8v0m+q6peVVUvyqRo76MDt2lhVVUl+bkkT7TW3rvhoY8muW96\n/74kv7T5WJLW2jtaa7e11r4zk4Llf9ta+5tx/naktfZ0ki9U1aunu96Q5PEkvxzn71o+m+T7q+rg\n9H38hkwmbjh3u7Pde9Vn7g7MM7Ps+zpcVfX5TBZMvTjd9RuttbdOH3tHJmOk38hk6OexfW3cSFTV\nX03y3kxm7fxca+0fDtykhVVVR5L8uySfynp374OZvDEeSfIdSZ5M8ubW2leHaONYVNUdSU601t5U\nVYfi/O1IVb0ukwkHL0ryH5P8cCbvXefvGqrq72cSEi4n+USSH0nyJ+PcbamqPpzkjiTfnkm91j9I\n8pFsc7585j7fFufvnZl8Xswls1j4FACgs6FnKQIALD2BCwCgM4ELAKAzgQsAoDOBCwCgM4ELAKAz\ngQsAoDOBCwCgs/8fICoqGcqtXKgAAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "draw_boids(model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.4.2" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/examples/basic/boid_flockers/Readme.md b/examples/basic/boid_flockers/Readme.md new file mode 100644 index 00000000000..cb3292b4f68 --- /dev/null +++ b/examples/basic/boid_flockers/Readme.md @@ -0,0 +1,34 @@ +# Flockers + +An implementation of Craig Reynolds's Boids flocker model. Agents (simulated birds) try to fly towards the average position of their neighbors and in the same direction as them, while maintaining a minimum distance. This produces flocking behavior. + +This model tests Mesa's continuous space feature, and uses numpy arrays to represent vectors. It also demonstrates how to create custom visualization components. + +## How to Run + +Launch the model: +``` + $ python Flocker_Server.py +``` + +Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run. + +## Files + +* [flockers/model.py](flockers/model.py): Core model file; contains the BoidModel class. +* [flockers/boid.py](flockers/boid.py): The Boid agent class. +* [flockers/SimpleContinuousModule.py](flockers/SimpleContinuousModule.py): Defines ``SimpleCanvas``, the Python side of a custom visualization module for drawing agents with continuous positions. +* [flockers/simple_continuous_canvas.js](flockers/simple_continuous_canvas.js): JavaScript side of the ``SimpleCanvas`` visualization module; takes the output generated by the Python ``SimpleCanvas`` element and draws it in the browser window via HTML5 canvas. +* [flockers/server.py](flockers/server.py): Sets up the visualization; uses the SimpleCanvas element defined above +* [run.py](run.py) Launches the visualization. +* [Flocker Test.ipynb](Flocker Test.ipynb): Tests the model in a Jupyter notebook. + +## Further Reading + +======= +* Launch the visualization +``` +$ mesa runserver +``` +* Visit your browser: http://127.0.0.1:8521/ +* In your browser hit *run* diff --git a/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py b/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py new file mode 100644 index 00000000000..3f3da5dd01e --- /dev/null +++ b/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py @@ -0,0 +1,32 @@ +import mesa + + +class SimpleCanvas(mesa.visualization.VisualizationElement): + local_includes = ["boid_flockers/simple_continuous_canvas.js"] + portrayal_method = None + canvas_height = 500 + canvas_width = 500 + + def __init__(self, portrayal_method, canvas_height=500, canvas_width=500): + """ + Instantiate a new SimpleCanvas + """ + self.portrayal_method = portrayal_method + self.canvas_height = canvas_height + self.canvas_width = canvas_width + new_element = "new Simple_Continuous_Module({}, {})".format( + self.canvas_width, self.canvas_height + ) + self.js_code = "elements.push(" + new_element + ");" + + def render(self, model): + space_state = [] + for obj in model.schedule.agents: + portrayal = self.portrayal_method(obj) + x, y = obj.pos + x = (x - model.space.x_min) / (model.space.x_max - model.space.x_min) + y = (y - model.space.y_min) / (model.space.y_max - model.space.y_min) + portrayal["x"] = x + portrayal["y"] = y + space_state.append(portrayal) + return space_state diff --git a/examples/basic/boid_flockers/boid_flockers/boid.py b/examples/basic/boid_flockers/boid_flockers/boid.py new file mode 100644 index 00000000000..f427f9ddbbc --- /dev/null +++ b/examples/basic/boid_flockers/boid_flockers/boid.py @@ -0,0 +1,104 @@ +import mesa +import numpy as np + + +class Boid(mesa.Agent): + """ + A Boid-style flocker agent. + + The agent follows three behaviors to flock: + - Cohesion: steering towards neighboring agents. + - Separation: avoiding getting too close to any other agent. + - Alignment: try to fly in the same direction as the neighbors. + + Boids have a vision that defines the radius in which they look for their + neighbors to flock with. Their speed (a scalar) and velocity (a vector) + define their movement. Separation is their desired minimum distance from + any other Boid. + """ + + def __init__( + self, + unique_id, + model, + pos, + speed, + velocity, + vision, + separation, + cohere=0.025, + separate=0.25, + match=0.04, + ): + """ + Create a new Boid flocker agent. + + Args: + unique_id: Unique agent identifyer. + pos: Starting position + speed: Distance to move per step. + heading: numpy vector for the Boid's direction of movement. + vision: Radius to look around for nearby Boids. + separation: Minimum distance to maintain from other Boids. + cohere: the relative importance of matching neighbors' positions + separate: the relative importance of avoiding close neighbors + match: the relative importance of matching neighbors' headings + """ + super().__init__(unique_id, model) + self.pos = np.array(pos) + self.speed = speed + self.velocity = velocity + self.vision = vision + self.separation = separation + self.cohere_factor = cohere + self.separate_factor = separate + self.match_factor = match + + def cohere(self, neighbors): + """ + Return the vector toward the center of mass of the local neighbors. + """ + cohere = np.zeros(2) + if neighbors: + for neighbor in neighbors: + cohere += self.model.space.get_heading(self.pos, neighbor.pos) + cohere /= len(neighbors) + return cohere + + def separate(self, neighbors): + """ + Return a vector away from any neighbors closer than separation dist. + """ + me = self.pos + them = (n.pos for n in neighbors) + separation_vector = np.zeros(2) + for other in them: + if self.model.space.get_distance(me, other) < self.separation: + separation_vector -= self.model.space.get_heading(me, other) + return separation_vector + + def match_heading(self, neighbors): + """ + Return a vector of the neighbors' average heading. + """ + match_vector = np.zeros(2) + if neighbors: + for neighbor in neighbors: + match_vector += neighbor.velocity + match_vector /= len(neighbors) + return match_vector + + def step(self): + """ + Get the Boid's neighbors, compute the new vector, and move accordingly. + """ + + neighbors = self.model.space.get_neighbors(self.pos, self.vision, False) + self.velocity += ( + self.cohere(neighbors) * self.cohere_factor + + self.separate(neighbors) * self.separate_factor + + self.match_heading(neighbors) * self.match_factor + ) / 2 + self.velocity /= np.linalg.norm(self.velocity) + new_pos = self.pos + self.velocity * self.speed + self.model.space.move_agent(self, new_pos) diff --git a/examples/basic/boid_flockers/boid_flockers/model.py b/examples/basic/boid_flockers/boid_flockers/model.py new file mode 100644 index 00000000000..00a08d765d5 --- /dev/null +++ b/examples/basic/boid_flockers/boid_flockers/model.py @@ -0,0 +1,76 @@ +""" +Flockers +============================================================= +A Mesa implementation of Craig Reynolds's Boids flocker model. +Uses numpy arrays to represent vectors. +""" + +import mesa +import numpy as np + +from .boid import Boid + + +class BoidFlockers(mesa.Model): + """ + Flocker model class. Handles agent creation, placement and scheduling. + """ + + def __init__( + self, + population=100, + width=100, + height=100, + speed=1, + vision=10, + separation=2, + cohere=0.025, + separate=0.25, + match=0.04, + ): + """ + Create a new Flockers model. + + Args: + population: Number of Boids + width, height: Size of the space. + speed: How fast should the Boids move. + vision: How far around should each Boid look for its neighbors + separation: What's the minimum distance each Boid will attempt to + keep from any other + cohere, separate, match: factors for the relative importance of + the three drives.""" + self.population = population + self.vision = vision + self.speed = speed + self.separation = separation + self.schedule = mesa.time.RandomActivation(self) + self.space = mesa.space.ContinuousSpace(width, height, True) + self.factors = dict(cohere=cohere, separate=separate, match=match) + self.make_agents() + self.running = True + + def make_agents(self): + """ + Create self.population agents, with random positions and starting headings. + """ + for i in range(self.population): + x = self.random.random() * self.space.x_max + y = self.random.random() * self.space.y_max + pos = np.array((x, y)) + velocity = np.random.random(2) * 2 - 1 + boid = Boid( + i, + self, + pos, + self.speed, + velocity, + self.vision, + self.separation, + **self.factors + ) + self.space.place_agent(boid, pos) + self.schedule.add(boid) + + def step(self): + self.schedule.step() diff --git a/examples/basic/boid_flockers/boid_flockers/server.py b/examples/basic/boid_flockers/boid_flockers/server.py new file mode 100644 index 00000000000..4906df699c7 --- /dev/null +++ b/examples/basic/boid_flockers/boid_flockers/server.py @@ -0,0 +1,23 @@ +import mesa + +from .model import BoidFlockers +from .SimpleContinuousModule import SimpleCanvas + + +def boid_draw(agent): + return {"Shape": "circle", "r": 2, "Filled": "true", "Color": "Red"} + + +boid_canvas = SimpleCanvas(boid_draw, 500, 500) +model_params = { + "population": 100, + "width": 100, + "height": 100, + "speed": 5, + "vision": 10, + "separation": 2, +} + +server = mesa.visualization.ModularServer( + BoidFlockers, [boid_canvas], "Boids", model_params +) diff --git a/examples/basic/boid_flockers/boid_flockers/simple_continuous_canvas.js b/examples/basic/boid_flockers/boid_flockers/simple_continuous_canvas.js new file mode 100644 index 00000000000..20c0ded8732 --- /dev/null +++ b/examples/basic/boid_flockers/boid_flockers/simple_continuous_canvas.js @@ -0,0 +1,79 @@ +const ContinuousVisualization = function(width, height, context) { + this.draw = function(objects) { + for (const p of objects) { + if (p.Shape == "rect") + this.drawRectange(p.x, p.y, p.w, p.h, p.Color, p.Filled); + if (p.Shape == "circle") + this.drawCircle(p.x, p.y, p.r, p.Color, p.Filled); + }; + + }; + + this.drawCircle = function(x, y, radius, color, fill) { + const cx = x * width; + const cy = y * height; + const r = radius; + + context.beginPath(); + context.arc(cx, cy, r, 0, Math.PI * 2, false); + context.closePath(); + + context.strokeStyle = color; + context.stroke(); + + if (fill) { + context.fillStyle = color; + context.fill(); + } + + }; + + this.drawRectange = function(x, y, w, h, color, fill) { + context.beginPath(); + const dx = w * width; + const dy = h * height; + + // Keep the drawing centered: + const x0 = (x*width) - 0.5*dx; + const y0 = (y*height) - 0.5*dy; + + context.strokeStyle = color; + context.fillStyle = color; + if (fill) + context.fillRect(x0, y0, dx, dy); + else + context.strokeRect(x0, y0, dx, dy); + }; + + this.resetCanvas = function() { + context.clearRect(0, 0, width, height); + context.beginPath(); + }; +}; + +const Simple_Continuous_Module = function(canvas_width, canvas_height) { + // Create the element + // ------------------ + + const canvas = document.createElement("canvas"); + Object.assign(canvas, { + width: canvas_width, + height: canvas_height, + style: 'border:1px dotted' + }); + // Append it to body: + document.getElementById("elements").appendChild(canvas); + + // Create the context and the drawing controller: + const context = canvas.getContext("2d"); + const canvasDraw = new ContinuousVisualization(canvas_width, canvas_height, context); + + this.render = function(data) { + canvasDraw.resetCanvas(); + canvasDraw.draw(data); + }; + + this.reset = function() { + canvasDraw.resetCanvas(); + }; +}; diff --git a/examples/basic/boid_flockers/requirements.txt b/examples/basic/boid_flockers/requirements.txt new file mode 100644 index 00000000000..bcbfbbe220b --- /dev/null +++ b/examples/basic/boid_flockers/requirements.txt @@ -0,0 +1,3 @@ +jupyter +matplotlib +mesa diff --git a/examples/basic/boid_flockers/run.py b/examples/basic/boid_flockers/run.py new file mode 100644 index 00000000000..be0c1c75c58 --- /dev/null +++ b/examples/basic/boid_flockers/run.py @@ -0,0 +1,3 @@ +from boid_flockers.server import server + +server.launch() diff --git a/examples/basic/conways_game_of_life/Readme.md b/examples/basic/conways_game_of_life/Readme.md new file mode 100644 index 00000000000..686afb4065a --- /dev/null +++ b/examples/basic/conways_game_of_life/Readme.md @@ -0,0 +1,30 @@ +# Conway's Game Of "Life" + +## Summary + +[The Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life), also known simply as "Life", is a cellular automaton devised by the British mathematician John Horton Conway in 1970. + +The "game" is a zero-player game, meaning that its evolution is determined by its initial state, requiring no further input by a human. One interacts with the Game of "Life" by creating an initial configuration and observing how it evolves, or, for advanced "players", by creating patterns with particular properties. + + +## How to Run + +To run the model interactively, run ``mesa runserver`` in this directory. e.g. + +``` + $ mesa runserver +``` + +Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press ``run``. + +## Files + +* ``game_of_life/cell.py``: Defines the behavior of an individual cell, which can be in two states: DEAD or ALIVE. +* ``game_of_life/model.py``: Defines the model itself, initialized with a random configuration of alive and dead cells. +* ``game_of_life/portrayal.py``: Describes for the front end how to render a cell. +* ``game_of_live/server.py``: Defines an interactive visualization. +* ``run.py``: Launches the visualization + +## Further Reading +[Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) + diff --git a/examples/basic/conways_game_of_life/conways_game_of_life/cell.py b/examples/basic/conways_game_of_life/conways_game_of_life/cell.py new file mode 100644 index 00000000000..8639288d4ca --- /dev/null +++ b/examples/basic/conways_game_of_life/conways_game_of_life/cell.py @@ -0,0 +1,53 @@ +import mesa + + +class Cell(mesa.Agent): + """Represents a single ALIVE or DEAD cell in the simulation.""" + + DEAD = 0 + ALIVE = 1 + + def __init__(self, pos, model, init_state=DEAD): + """ + Create a cell, in the given state, at the given x, y position. + """ + super().__init__(pos, model) + self.x, self.y = pos + self.state = init_state + self._nextState = None + + @property + def isAlive(self): + return self.state == self.ALIVE + + @property + def neighbors(self): + return self.model.grid.iter_neighbors((self.x, self.y), True) + + def step(self): + """ + Compute if the cell will be dead or alive at the next tick. This is + based on the number of alive or dead neighbors. The state is not + changed here, but is just computed and stored in self._nextState, + because our current state may still be necessary for our neighbors + to calculate their next state. + """ + + # Get the neighbors and apply the rules on whether to be alive or dead + # at the next tick. + live_neighbors = sum(neighbor.isAlive for neighbor in self.neighbors) + + # Assume nextState is unchanged, unless changed below. + self._nextState = self.state + if self.isAlive: + if live_neighbors < 2 or live_neighbors > 3: + self._nextState = self.DEAD + else: + if live_neighbors == 3: + self._nextState = self.ALIVE + + def advance(self): + """ + Set the state to the new computed state -- computed in step(). + """ + self.state = self._nextState diff --git a/examples/basic/conways_game_of_life/conways_game_of_life/model.py b/examples/basic/conways_game_of_life/conways_game_of_life/model.py new file mode 100644 index 00000000000..635ccaa959d --- /dev/null +++ b/examples/basic/conways_game_of_life/conways_game_of_life/model.py @@ -0,0 +1,43 @@ +import mesa + +from .cell import Cell + + +class ConwaysGameOfLife(mesa.Model): + """ + Represents the 2-dimensional array of cells in Conway's + Game of Life. + """ + + def __init__(self, width=50, height=50): + """ + Create a new playing area of (width, height) cells. + """ + + # Set up the grid and schedule. + + # Use SimultaneousActivation which simulates all the cells + # computing their next state simultaneously. This needs to + # be done because each cell's next state depends on the current + # state of all its neighbors -- before they've changed. + self.schedule = mesa.time.SimultaneousActivation(self) + + # Use a simple grid, where edges wrap around. + self.grid = mesa.space.Grid(width, height, torus=True) + + # Place a cell at each location, with some initialized to + # ALIVE and some to DEAD. + for (contents, x, y) in self.grid.coord_iter(): + cell = Cell((x, y), self) + if self.random.random() < 0.1: + cell.state = cell.ALIVE + self.grid.place_agent(cell, (x, y)) + self.schedule.add(cell) + + self.running = True + + def step(self): + """ + Have the scheduler advance each cell by one step + """ + self.schedule.step() diff --git a/examples/basic/conways_game_of_life/conways_game_of_life/portrayal.py b/examples/basic/conways_game_of_life/conways_game_of_life/portrayal.py new file mode 100644 index 00000000000..4f68468d857 --- /dev/null +++ b/examples/basic/conways_game_of_life/conways_game_of_life/portrayal.py @@ -0,0 +1,19 @@ +def portrayCell(cell): + """ + This function is registered with the visualization server to be called + each tick to indicate how to draw the cell in its current state. + :param cell: the cell in the simulation + :return: the portrayal dictionary. + """ + if cell is None: + raise AssertionError + return { + "Shape": "rect", + "w": 1, + "h": 1, + "Filled": "true", + "Layer": 0, + "x": cell.x, + "y": cell.y, + "Color": "black" if cell.isAlive else "white", + } diff --git a/examples/basic/conways_game_of_life/conways_game_of_life/server.py b/examples/basic/conways_game_of_life/conways_game_of_life/server.py new file mode 100644 index 00000000000..4167b3d01bd --- /dev/null +++ b/examples/basic/conways_game_of_life/conways_game_of_life/server.py @@ -0,0 +1,12 @@ +import mesa + +from .portrayal import portrayCell +from .model import ConwaysGameOfLife + + +# Make a world that is 50x50, on a 250x250 display. +canvas_element = mesa.visualization.CanvasGrid(portrayCell, 50, 50, 250, 250) + +server = mesa.visualization.ModularServer( + ConwaysGameOfLife, [canvas_element], "Game of Life", {"height": 50, "width": 50} +) diff --git a/examples/basic/conways_game_of_life/requirements.txt b/examples/basic/conways_game_of_life/requirements.txt new file mode 100644 index 00000000000..1ad1bbec7ab --- /dev/null +++ b/examples/basic/conways_game_of_life/requirements.txt @@ -0,0 +1 @@ +mesa \ No newline at end of file diff --git a/examples/basic/conways_game_of_life/run.py b/examples/basic/conways_game_of_life/run.py new file mode 100644 index 00000000000..2854fdee59d --- /dev/null +++ b/examples/basic/conways_game_of_life/run.py @@ -0,0 +1,3 @@ +from conways_game_of_life.server import server + +server.launch() From b88486d0b6519ac53cf3cb5c3d622edfe8de5d39 Mon Sep 17 00:00:00 2001 From: Tom Pike Date: Thu, 2 Feb 2023 20:20:08 -0500 Subject: [PATCH 003/116] Add sugarcape_g1mt that is consistent with complexity tutorial (#18) Additionally: - Include interactive version with single run, batch run and server options - Update .gitignore to ignore IDE environment files --- examples/advanced/sugarscape_g1mt/Readme.md | 89 +++++ .../advanced/sugarscape_g1mt/requirements.txt | 5 + examples/advanced/sugarscape_g1mt/run.py | 104 +++++ .../sugarscape_g1mt/__init__.py | 0 .../sugarscape_g1mt/sugarscape_g1mt/model.py | 195 +++++++++ .../sugarscape_g1mt/resource_agents.py | 43 ++ .../sugarscape_g1mt/sugarscape_g1mt/server.py | 77 ++++ .../sugarscape_g1mt/sugar-map.txt | 50 +++ .../sugarscape_g1mt/trader_agents.py | 377 ++++++++++++++++++ 9 files changed, 940 insertions(+) create mode 100644 examples/advanced/sugarscape_g1mt/Readme.md create mode 100644 examples/advanced/sugarscape_g1mt/requirements.txt create mode 100644 examples/advanced/sugarscape_g1mt/run.py create mode 100644 examples/advanced/sugarscape_g1mt/sugarscape_g1mt/__init__.py create mode 100644 examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py create mode 100644 examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py create mode 100644 examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py create mode 100644 examples/advanced/sugarscape_g1mt/sugarscape_g1mt/sugar-map.txt create mode 100644 examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py diff --git a/examples/advanced/sugarscape_g1mt/Readme.md b/examples/advanced/sugarscape_g1mt/Readme.md new file mode 100644 index 00000000000..2b69ed0f39a --- /dev/null +++ b/examples/advanced/sugarscape_g1mt/Readme.md @@ -0,0 +1,89 @@ +# Sugarscape Constant Growback Model with Traders + +## Summary + +This is Epstein & Axtell's Sugarscape model with Traders, a detailed description is in Chapter four of +*Growing Artificial Societies: Social Science from the Bottom Up.* (1996) + +This code generally matches the code in the Complexity Explorer Tutorial, but in `.py` instead of `.ipynb` format. + +### Agents: + +- **Sugar**: Sugar agents grow back at one unit per time step and can be harvested and traded by the trader agents. Sugar +is unequally distributed across the landscape with sugar hills in the upper left and lower right of the space. + (green if you do the interactive run) +- **Spice**: Spice agents grow back at one unit per time step and can be harvested and traded by the trader agents. Spice +is unequally distributed across the landscape with spice hills in the upper right and lower left of the space. +(yellow if you do the interactive run) +- **Traders**: Trader agents have the following attributes: (1) metabolism for sugar, (2) metabolism for spice, (3) vision, + (4) initial sugar endowment and (5) initial spice endowment. The traverse the landscape harvesting sugar and spice and +trading with other agents. If they run out of sugar or spice then they are removed from the model. + +The trader agents traverse the landscape according to rule **M**: +- Look out as far as vision permits in the four principal lattice directions and identify the unoccupied site(s). +- Considering only unoccupied sites find the nearest position that produces the most welfare using the Cobb-Douglas function. +- Move to the new position +- Collect all the resources (sugar and spice) at that location +(Epstein and Axtell, 1996, p. 99) + +The traders trade according to rule **T**: +- Agents and potential trade partner compute their marginal rates of substitution (MRS), if they are equal *end*. +- Exchange resources, with spice flowing from the agent with the higher MRS to the agent with the lower MRS and sugar +flowing the opposite direction. +- The price (p) is calculated by taking the geometric mean of the agents' MRS. +- If p > 1 then p units of spice are traded for 1 unit of sugar; if p < 1 then 1/p units of sugar for 1 unit of spice +- The trade occurs if it will (a) make both agent better off (increases MRS) and (b) does not cause the agents' MRS to +cross over one another otherwise *end*. +- This process then repeats until an *end* condition is met. +(Epstein and Axtell, 1996, p. 105) + +The model demonstrates several Mesa concepts and features: + - MultiGrid + - 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) + +## 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 +``` + +To run the model interactively: + +``` + $ mesa runserver +``` + +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 Sugar and Spice agent classes. +* ``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 and launches and interactive visualization server. +* ``run.py``: Runs Server, Single Run or Batch Run with data collection and basic analysis. + +## Additional Resources + +- [Growing Artificial Societies](https://mitpress.mit.edu/9780262550253/growing-artificial-societies/) +- [Complexity Explorer Sugarscape with Traders Tutorial](https://www.complexityexplorer.org/courses/172-agent-based-models-with-python-an-introduction-to-mesa) diff --git a/examples/advanced/sugarscape_g1mt/requirements.txt b/examples/advanced/sugarscape_g1mt/requirements.txt new file mode 100644 index 00000000000..cc578b40b7c --- /dev/null +++ b/examples/advanced/sugarscape_g1mt/requirements.txt @@ -0,0 +1,5 @@ +jupyter +mesa +numpy +matplotlib +networkx diff --git a/examples/advanced/sugarscape_g1mt/run.py b/examples/advanced/sugarscape_g1mt/run.py new file mode 100644 index 00000000000..bb375a97420 --- /dev/null +++ b/examples/advanced/sugarscape_g1mt/run.py @@ -0,0 +1,104 @@ +import sys +import pandas as pd +import matplotlib.pyplot as plt +import networkx as nx +import mesa +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("Node Connectivity {}".format(nx.node_connectivity(G))) + print("Average Clustering {}".format(nx.average_clustering(G))) + print("Global Efficiency {}".format(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 + +args = sys.argv[1:] + + +if args[0] == "runserver": + server.launch() + +elif "s" in args[0] or "Single" in args[0]: + print("Running Single Model") + # instantiate the model + model = SugarscapeG1mt() + # run the model + model.run_model() + # Get results + model_results = model.datacollector.get_model_vars_dataframe() + # Convert to make similar to batch_run_results + model_results["Step"] = model_results.index + agent_results = model.datacollector.get_agent_vars_dataframe() + agent_results = agent_results.reset_index() + # assess the results + assess_results(model_results, agent_results) + +else: + print("Conducting a Batch Run") + # Batch Run + params = { + "width": 50, + "height": 50, + "vision_min": range(1, 3), + "metabolism_max": [3, 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) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/__init__.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py new file mode 100644 index 00000000000..57f1599b338 --- /dev/null +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -0,0 +1,195 @@ +import numpy as np +import math + + +import mesa +from .trader_agents import Trader +from .resource_agents import Sugar, Spice + +# Helper Functions +def flatten(list_of_lists): + """ + helper function for model datacollector for trade price + collapses agent price list into one list + """ + return [item for sublist in list_of_lists for item in sublist] + + +def geometric_mean(list_of_prices): + """ + find the geometric mean of a list of prices + """ + return np.exp(np.log(list_of_prices).mean()) + + +def get_trade(agent): + """ + For agent reporters in data collector + + return list of trade partners and None for other agents + """ + if isinstance(agent, Trader): + return agent.trade_partners + else: + return None + + +class SugarscapeG1mt(mesa.Model): + """ + Manager class to run Sugarscape with Traders + """ + + def __init__( + self, + width=50, + height=50, + initial_population=200, + endowment_min=25, + endowment_max=50, + metabolism_min=1, + metabolism_max=5, + vision_min=1, + vision_max=5, + ): + + # Initiate width and heigh of sugarscape + self.width = width + self.height = height + # Initiate population attributes + self.initial_population = initial_population + self.endowment_min = endowment_min + self.endowment_max = endowment_max + self.metabolism_min = metabolism_min + self.metabolism_max = metabolism_max + self.vision_min = vision_min + self.vision_max = vision_max + self.running = True + + # initiate activation schedule + self.schedule = mesa.time.RandomActivationByType(self) + # initiate mesa grid class + self.grid = mesa.space.MultiGrid(self.width, self.height, torus=False) + # initiate datacollector + self.datacollector = mesa.DataCollector( + model_reporters={ + "Trader": lambda m: m.schedule.get_type_count(Trader), + "Trade Volume": lambda m: sum( + len(a.trade_partners) + for a in m.schedule.agents_by_type[Trader].values() + ), + "Price": lambda m: geometric_mean( + flatten( + [a.prices for a in m.schedule.agents_by_type[Trader].values()] + ) + ), + }, + agent_reporters={"Trade Network": lambda a: get_trade(a)}, + ) + + # read in landscape file from supplmentary material + sugar_distribution = np.genfromtxt("sugarscape_g1mt/sugar-map.txt") + spice_distribution = np.flip(sugar_distribution, 1) + + agent_id = 0 + for _, x, y in self.grid.coord_iter(): + max_sugar = sugar_distribution[x, y] + if max_sugar > 0: + sugar = Sugar(agent_id, self, (x, y), max_sugar) + self.schedule.add(sugar) + self.grid.place_agent(sugar, (x, y)) + agent_id += 1 + + max_spice = spice_distribution[x, y] + if max_spice > 0: + spice = Spice(agent_id, self, (x, y), max_spice) + self.schedule.add(spice) + self.grid.place_agent(spice, (x, y)) + agent_id += 1 + + for i in range(self.initial_population): + # get agent position + x = self.random.randrange(self.width) + y = self.random.randrange(self.height) + # see Growing Artificial Societies p. 108 for initialization + # give agents initial endowment + sugar = int(self.random.uniform(self.endowment_min, self.endowment_max + 1)) + spice = int(self.random.uniform(self.endowment_min, self.endowment_max + 1)) + # give agents initial metabolism + metabolism_sugar = int( + self.random.uniform(self.metabolism_min, self.metabolism_max + 1) + ) + metabolism_spice = int( + self.random.uniform(self.metabolism_min, self.metabolism_max + 1) + ) + # give agents vision + vision = int(self.random.uniform(self.vision_min, self.vision_max + 1)) + # create Trader object + trader = Trader( + agent_id, + self, + (x, y), + moore=False, + sugar=sugar, + spice=spice, + metabolism_sugar=metabolism_sugar, + metabolism_spice=metabolism_spice, + vision=vision, + ) + # place agent + self.grid.place_agent(trader, (x, y)) + self.schedule.add(trader) + agent_id += 1 + + def randomize_traders(self): + """ + helper function for self.step() + + puts traders in randomized list for step function + """ + + traders_shuffle = list(self.schedule.agents_by_type[Trader].values()) + self.random.shuffle(traders_shuffle) + + return traders_shuffle + + def step(self): + """ + Unique step function that does staged activation of sugar and spice + and then randomly activates traders + """ + # step Sugar agents + for sugar in self.schedule.agents_by_type[Sugar].values(): + sugar.step() + + # step Spice agents + for spice in self.schedule.agents_by_type[Spice].values(): + spice.step() + + # step trader agents + # to account for agent death and removal we need a seperate data strcuture to + # iterate + trader_shuffle = self.randomize_traders() + + for agent in trader_shuffle: + agent.prices = [] + agent.trade_partners = [] + agent.move() + agent.eat() + agent.maybe_die() + + trader_shuffle = self.randomize_traders() + + for agent in trader_shuffle: + agent.trade_with_neighbors() + + self.schedule.steps += ( + 1 # important for data collector to track number of steps + ) + + # collect model level data + self.datacollector.collect(self) + + def run_model(self, step_count=1000): + + for i in range(step_count): + self.step() diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py new file mode 100644 index 00000000000..6d78a93ec71 --- /dev/null +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py @@ -0,0 +1,43 @@ +import mesa + + +class Sugar(mesa.Agent): + """ + Sugar: + - contains an amount of sugar + - grows 1 amount of sugar at each turn + """ + + def __init__(self, unique_id, model, pos, max_sugar): + super().__init__(unique_id, model) + self.pos = pos + self.amount = max_sugar + self.max_sugar = max_sugar + + def step(self): + """ + Sugar growth function, adds one unit of sugar each step until + max amount + """ + self.amount = min([self.max_sugar, self.amount + 1]) + + +class Spice(mesa.Agent): + """ + Spice: + - contains an amount of spice + - grows 1 amount of spice at each turn + """ + + def __init__(self, unique_id, model, pos, max_spice): + super().__init__(unique_id, model) + self.pos = pos + self.amount = max_spice + self.max_spice = max_spice + + def step(self): + """ + Spice growth function, adds one unit of spice each step until + max amount + """ + self.amount = min([self.max_spice, self.amount + 1]) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py new file mode 100644 index 00000000000..a0e1a2f1a05 --- /dev/null +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py @@ -0,0 +1,77 @@ +import mesa + +from .resource_agents import Sugar, Spice +from .trader_agents import Trader +from .model import SugarscapeG1mt + + +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, Sugar): + if agent.amount != 0: + color = sugar_dic[agent.amount] + else: + color = "#D6F5D6" + if agent.amount > 2: + layer = 1 + else: + layer = 0 + return { + "Color": color, + "Shape": "rect", + "Filled": "true", + "Layer": layer, + "w": 1, + "h": 1, + } + + elif isinstance(agent, Spice): + if agent.amount != 0: + color = spice_dic[agent.amount] + else: + color = "#D6F5D6" + if agent.amount > 2: + layer = 1 + else: + layer = 0 + return { + "Color": color, + "Shape": "rect", + "Filled": "true", + "Layer": 0, + "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/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/sugar-map.txt b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/sugar-map.txt new file mode 100644 index 00000000000..1357a6676b4 --- /dev/null +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/sugar-map.txt @@ -0,0 +1,50 @@ +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 2 2 2 2 2 2 2 2 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 3 3 3 3 3 3 3 2 2 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 3 3 3 3 3 3 2 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 2 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 +0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 +0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 +0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 +0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 +0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 +0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 2 +0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 3 3 3 3 3 3 2 +1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 4 4 4 4 3 3 3 3 3 3 3 2 2 +1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 +1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 +1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 +1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 +1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 +1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 +1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 +1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 +1 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 +1 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 +2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 +2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 +2 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 0 0 0 +2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 +2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 +2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 +2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 +2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 +2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 2 2 2 2 2 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 +2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 2 2 2 2 2 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 +2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 +2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 3 3 3 3 3 3 2 2 2 2 2 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 +2 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 3 3 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 +2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 +2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 +1 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 +1 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 +1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 +1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py new file mode 100644 index 00000000000..14b9802988c --- /dev/null +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py @@ -0,0 +1,377 @@ +import math +import mesa +from .resource_agents import Sugar, Spice + + +# Helper function +def get_distance(pos_1, pos_2): + """ + Calculate the Euclidean distance between two positions + + used in trade.move() + """ + + x1, y1 = pos_1 + x2, y2 = pos_2 + dx = x1 - x2 + dy = y1 - y2 + return math.sqrt(dx**2 + dy**2) + + +class Trader(mesa.Agent): + """ + Trader: + - has a metabolism of sugar and spice + - harvest and trade sugar and spice to survive + """ + + def __init__( + self, + unique_id, + model, + pos, + moore=False, + sugar=0, + spice=0, + metabolism_sugar=0, + metabolism_spice=0, + vision=0, + ): + super().__init__(unique_id, model) + self.pos = pos + self.moore = moore + self.sugar = sugar + self.spice = spice + self.metabolism_sugar = metabolism_sugar + self.metabolism_spice = metabolism_spice + self.vision = vision + self.prices = [] + self.trade_partners = [] + + def get_sugar(self, pos): + """ + used in self.get_sugar_amount() + """ + + this_cell = self.model.grid.get_cell_list_contents(pos) + for agent in this_cell: + if type(agent) is Sugar: + return agent + return None + + def get_sugar_amount(self, pos): + """ + used in self.move() as part of self.calculate_welfare() + """ + + sugar_patch = self.get_sugar(pos) + if sugar_patch: + return sugar_patch.amount + return 0 + + def get_spice(self, pos): + """ + used in self.get_spice_amount() + """ + + this_cell = self.model.grid.get_cell_list_contents(pos) + for agent in this_cell: + if type(agent) is Spice: + return agent + return None + + def get_spice_amount(self, pos): + """ + used in self.move() as part of self.calculate_welfare() + """ + + spice_patch = self.get_spice(pos) + if spice_patch: + return spice_patch.amount + return 0 + + def get_trader(self, pos): + """ + helper function used in self.trade_with_neighbors() + """ + + this_cell = self.model.grid.get_cell_list_contents(pos) + + for agent in this_cell: + if isinstance(agent, Trader): + return agent + + def is_occupied_by_other(self, pos): + """ + helper function part 1 of self.move() + """ + + if pos == self.pos: + # agent's position is considered unoccupied as agent can stay there + return False + # get contents of each cell in neighborhood + this_cell = self.model.grid.get_cell_list_contents(pos) + for a in this_cell: + # see if occupied by another agent + if isinstance(a, Trader): + return True + return False + + def calculate_welfare(self, sugar, spice): + """ + helper function + + part 2 self.move() + self.trade() + """ + + # calculate total resources + m_total = self.metabolism_sugar + self.metabolism_spice + # Cobb-Douglas functional form; starting on p. 97 on Growing Artificial Societies + return sugar ** (self.metabolism_sugar / m_total) * spice ** ( + self.metabolism_spice / m_total + ) + + def is_starved(self): + """ + Helper function for self.maybe_die() + """ + + return (self.sugar <= 0) or (self.spice <= 0) + + def calculate_MRS(self): + """ + Helper function for self.trade() + + Determines what trader agent is needs and can give up + """ + + return (self.spice / self.metabolism_spice) / ( + self.sugar / self.metabolism_sugar + ) + + def calculate_sell_spice_amount(self, price): + """ + helper function for self.maybe_sell_spice() which is called from + self.trade() + """ + + if price >= 1: + sugar = 1 + spice = int(price) + else: + sugar = int(1 / price) + spice = 1 + return sugar, spice + + def sell_spice(self, other, sugar, spice): + """ + used in self.maybe_sell_spice() + + exchanges sugar and spice between traders + """ + + self.sugar += sugar + other.sugar -= sugar + self.spice -= spice + other.spice += spice + + def maybe_sell_spice(self, other, price, welfare_self, welfare_other): + """ + helper function for self.trade() + """ + + sugar_exchanged, spice_exchanged = self.calculate_sell_spice_amount(price) + + # Assess new sugar and spice amount - what if change did occur + self_sugar = self.sugar + sugar_exchanged + other_sugar = other.sugar - sugar_exchanged + self_spice = self.spice - spice_exchanged + other_spice = other.spice + spice_exchanged + + # double check to ensure agents have resources + + if ( + (self_sugar <= 0) + or (other_sugar <= 0) + or (self_spice <= 0) + or (other_spice <= 0) + ): + return False + + # trade criteria #1 - are both agents better off? + both_agents_better_off = ( + welfare_self < self.calculate_welfare(self_sugar, self_spice) + ) and (welfare_other < other.calculate_welfare(other_sugar, other_spice)) + + # trade criteria #2 is their mrs crossing + mrs_not_crossing = self.calculate_MRS() > other.calculate_MRS() + + if not (both_agents_better_off and mrs_not_crossing): + return False + + # criteria met, execute trade + self.sell_spice(other, sugar_exchanged, spice_exchanged) + + return True + + def trade(self, other): + """ + helper function used in trade_with_neighbors() + + other is a trader agent object + """ + + # sanity check to verify code is working as expected + assert self.sugar > 0 + assert self.spice > 0 + assert other.sugar > 0 + assert other.spice > 0 + + # calculate marginal rate of subsitution in Growing Artificial Socieites p. 101 + mrs_self = self.calculate_MRS() + mrs_other = other.calculate_MRS() + + # calculate each agents welfare + welfare_self = self.calculate_welfare(self.sugar, self.spice) + welfare_other = other.calculate_welfare(other.sugar, other.spice) + + if math.isclose(mrs_self, mrs_other): + return + + # calcualte price + price = math.sqrt(mrs_self * mrs_other) + + if mrs_self > mrs_other: + # self is a sugar buyer, spice seller + sold = self.maybe_sell_spice(other, price, welfare_self, welfare_other) + # no trade - criteria not met + if not sold: + return + else: + # self is a spice buyer, sugar seller + sold = other.maybe_sell_spice(self, price, welfare_other, welfare_self) + # no trade - criteria not met + if not sold: + return + + # Capture data + self.prices.append(price) + self.trade_partners.append(other.unique_id) + + # continue trading + self.trade(other) + + ###################################################################### + # # + # MAIN TRADE FUNCTIONS # + # # + ###################################################################### + + def move(self): + """ + Function for trader agent to identify optimal move for each step in 4 parts + 1 - identify all possible moves + 2 - determine which move maximizes welfare + 3 - find closest best option + 4 - move + """ + + # 1. identify all possible moves + + neighbors = [ + i + for i in self.model.grid.get_neighborhood( + self.pos, self.moore, True, self.vision + ) + if not self.is_occupied_by_other(i) + ] + + # 2. determine which move maximizes welfare + + welfares = [ + self.calculate_welfare( + self.sugar + self.get_sugar_amount(pos), + self.spice + self.get_spice_amount(pos), + ) + for pos in neighbors + ] + + # 3. Find closest best option + + # find the highest welfare in welfares + max_welfare = max(welfares) + # get the index of max welfare cells + candidate_indices = [ + i for i in range(len(welfares)) if math.isclose(welfares[i], max_welfare) + ] + + # convert index to positions of those cells + candidates = [neighbors[i] for i in candidate_indices] + + min_dist = min(get_distance(self.pos, pos) for pos in candidates) + + final_candidates = [ + pos + for pos in candidates + if math.isclose(get_distance(self.pos, pos), min_dist, rel_tol=1e-02) + ] + final_candidate = self.random.choice(final_candidates) + + # 4. Move Agent + self.model.grid.move_agent(self, final_candidate) + + def eat(self): + """ + Function for trader to consume sugar and spice in grid cell + """ + + # get sugar + sugar_patch = self.get_sugar(self.pos) + # eat sugar + if sugar_patch: + self.sugar = self.sugar - self.metabolism_sugar + sugar_patch.amount + sugar_patch.amount = 0 + + # get spice + spice_patch = self.get_spice(self.pos) + # eat spice + if spice_patch: + self.spice = self.spice - self.metabolism_spice + spice_patch.amount + spice_patch.amount = 0 + + def maybe_die(self): + """ + Function to remove Traders who have consumed all their sugar or spice + """ + + if self.is_starved(): + self.model.grid.remove_agent(self) + self.model.schedule.remove(self) + + def trade_with_neighbors(self): + """ + Function for trader agents to decide who to trade with in three parts + + 1- identify neighbors who can trade + 2- trade (2 sessions) + 3- collect data + """ + + neighbor_agents = [ + self.get_trader(pos) + for pos in self.model.grid.get_neighborhood( + self.pos, self.moore, False, self.vision + ) + if self.is_occupied_by_other(pos) + ] + + if len(neighbor_agents) == 0: + return + + # iterate through traders in neighboring cells and trade + for a in neighbor_agents: + if a: + self.trade(a) + + return From e43e1f729cb9f2b48927999e13af84473a9d51cc Mon Sep 17 00:00:00 2001 From: Tom Pike Date: Fri, 31 Mar 2023 17:54:44 -0400 Subject: [PATCH 004/116] Fix Eat Sugarscape_g1mt (#20) * - Add sugarcape_g1mt that is consistent with complexity tutorial - Include interactive version with single run, batch run and server options - Update .gitignore to ignore IDE environment files * - update eat function based on user identified bug in Complexity Explorer Tutorial - update Readme so batch run does not get an argument error - update run so data processing of batchrunner does not get a key error --- examples/advanced/sugarscape_g1mt/Readme.md | 2 +- examples/advanced/sugarscape_g1mt/run.py | 2 +- .../sugarscape_g1mt/trader_agents.py | 16 +++++++--------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/Readme.md b/examples/advanced/sugarscape_g1mt/Readme.md index 2b69ed0f39a..36f1f5332df 100644 --- a/examples/advanced/sugarscape_g1mt/Readme.md +++ b/examples/advanced/sugarscape_g1mt/Readme.md @@ -63,7 +63,7 @@ To run the model a single instance of the model: To run the model with BatchRunner: ``` - $ python run.py + $ python run.py -b ``` To run the model interactively: diff --git a/examples/advanced/sugarscape_g1mt/run.py b/examples/advanced/sugarscape_g1mt/run.py index bb375a97420..a5757d64a9b 100644 --- a/examples/advanced/sugarscape_g1mt/run.py +++ b/examples/advanced/sugarscape_g1mt/run.py @@ -19,7 +19,7 @@ def assess_results(results, single_agent): plt.plot(results_df["Step"], results_df["Trader"]) plt.show() else: - n = max(results_df["RunID"]) + n = max(results_df["RunId"]) # Plot number of Traders for i in range(n): results_explore = results_df[results_df["RunId"] == i] diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py index 14b9802988c..7287d484ac5 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py @@ -322,23 +322,21 @@ def move(self): self.model.grid.move_agent(self, final_candidate) def eat(self): - """ - Function for trader to consume sugar and spice in grid cell - """ - # get sugar sugar_patch = self.get_sugar(self.pos) - # eat sugar + if sugar_patch: - self.sugar = self.sugar - self.metabolism_sugar + sugar_patch.amount + self.sugar += sugar_patch.amount sugar_patch.amount = 0 + self.sugar -= self.metabolism_sugar - # get spice + # get_spice spice_patch = self.get_spice(self.pos) - # eat spice + if spice_patch: - self.spice = self.spice - self.metabolism_spice + spice_patch.amount + self.spice += spice_patch.amount spice_patch.amount = 0 + self.spice -= self.metabolism_spice def maybe_die(self): """ From 579521c140759e881d326cc25b30d5ee23473756 Mon Sep 17 00:00:00 2001 From: rht Date: Mon, 5 Dec 2022 06:23:19 -0500 Subject: [PATCH 005/116] requirements: Pin Mesa version to 1.x --- examples/basic/boid_flockers/requirements.txt | 2 +- examples/basic/conways_game_of_life/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/basic/boid_flockers/requirements.txt b/examples/basic/boid_flockers/requirements.txt index bcbfbbe220b..19b805acb1f 100644 --- a/examples/basic/boid_flockers/requirements.txt +++ b/examples/basic/boid_flockers/requirements.txt @@ -1,3 +1,3 @@ jupyter matplotlib -mesa +mesa~=1.1 diff --git a/examples/basic/conways_game_of_life/requirements.txt b/examples/basic/conways_game_of_life/requirements.txt index 1ad1bbec7ab..0d2d0bc66aa 100644 --- a/examples/basic/conways_game_of_life/requirements.txt +++ b/examples/basic/conways_game_of_life/requirements.txt @@ -1 +1 @@ -mesa \ No newline at end of file +mesa~=1.1 \ No newline at end of file From 15d279cf89476a6c05eea48c74c65ebca78483a3 Mon Sep 17 00:00:00 2001 From: rht Date: Mon, 5 Dec 2022 06:23:19 -0500 Subject: [PATCH 006/116] requirements: Pin Mesa version to 1.x --- examples/advanced/epstein_civil_violence/requirements.txt | 2 +- examples/advanced/wolf_sheep/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/advanced/epstein_civil_violence/requirements.txt b/examples/advanced/epstein_civil_violence/requirements.txt index bcbfbbe220b..19b805acb1f 100644 --- a/examples/advanced/epstein_civil_violence/requirements.txt +++ b/examples/advanced/epstein_civil_violence/requirements.txt @@ -1,3 +1,3 @@ jupyter matplotlib -mesa +mesa~=1.1 diff --git a/examples/advanced/wolf_sheep/requirements.txt b/examples/advanced/wolf_sheep/requirements.txt index da0b5b956fd..63b0d24e76d 100644 --- a/examples/advanced/wolf_sheep/requirements.txt +++ b/examples/advanced/wolf_sheep/requirements.txt @@ -1 +1 @@ -mesa +mesa~=1.1 From bfadd939e8014f854bd69148594b9d16f4f7c867 Mon Sep 17 00:00:00 2001 From: ItsQuinnMoore Date: Mon, 24 Apr 2023 11:13:30 -0600 Subject: [PATCH 007/116] Fixed consistent capitalization of files. --- .../basic/boltzmann_wealth_model/Readme.md | 39 ++ .../boltzmann_wealth_model/__init__.py | 0 .../boltzmann_wealth_model/model.py | 73 +++ .../boltzmann_wealth_model/server.py | 40 ++ .../boltzmann_wealth_model/requirements.txt | 4 + examples/basic/boltzmann_wealth_model/run.py | 3 + examples/basic/schelling/README.md | 49 ++ examples/basic/schelling/analysis.ipynb | 457 ++++++++++++++++++ examples/basic/schelling/model.py | 89 ++++ examples/basic/schelling/requirements.txt | 3 + examples/basic/schelling/run.py | 3 + examples/basic/schelling/run_ascii.py | 49 ++ examples/basic/schelling/server.py | 46 ++ examples/basic/virus_on_network/README.md | 46 ++ .../basic/virus_on_network/requirements.txt | 2 + examples/basic/virus_on_network/run.py | 3 + .../virus_on_network/__init__.py | 0 .../virus_on_network/model.py | 160 ++++++ .../virus_on_network/server.py | 133 +++++ 19 files changed, 1199 insertions(+) create mode 100644 examples/basic/boltzmann_wealth_model/Readme.md create mode 100644 examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py create mode 100644 examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py create mode 100644 examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/server.py create mode 100644 examples/basic/boltzmann_wealth_model/requirements.txt create mode 100644 examples/basic/boltzmann_wealth_model/run.py create mode 100644 examples/basic/schelling/README.md create mode 100644 examples/basic/schelling/analysis.ipynb create mode 100644 examples/basic/schelling/model.py create mode 100644 examples/basic/schelling/requirements.txt create mode 100644 examples/basic/schelling/run.py create mode 100644 examples/basic/schelling/run_ascii.py create mode 100644 examples/basic/schelling/server.py create mode 100644 examples/basic/virus_on_network/README.md create mode 100644 examples/basic/virus_on_network/requirements.txt create mode 100644 examples/basic/virus_on_network/run.py create mode 100644 examples/basic/virus_on_network/virus_on_network/__init__.py create mode 100644 examples/basic/virus_on_network/virus_on_network/model.py create mode 100644 examples/basic/virus_on_network/virus_on_network/server.py diff --git a/examples/basic/boltzmann_wealth_model/Readme.md b/examples/basic/boltzmann_wealth_model/Readme.md new file mode 100644 index 00000000000..785a0946a24 --- /dev/null +++ b/examples/basic/boltzmann_wealth_model/Readme.md @@ -0,0 +1,39 @@ +# Boltzmann Wealth Model (Tutorial) + +## Summary + +A simple model of agents exchanging wealth. All agents start with the same amount of money. Every step, each agent with one unit of money or more gives one unit of wealth to another random agent. This is the model described in the [Intro Tutorial](https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html). + +As the model runs, the distribution of wealth among agents goes from being perfectly uniform (all agents have the same starting wealth), to highly skewed -- a small number have high wealth, more have none at all. + +## How to Run + +To follow the tutorial examples, launch the Jupyter Notebook and run the code in ``Introduction to Mesa Tutorial Code.ipynb``. + +To launch the interactive server, as described in the [last section of the tutorial](https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html#adding-visualization), run: + +``` + $ python viz_money_model.py +``` + +If your browser doesn't open automatically, point it to [http://127.0.0.1:8521/](http://127.0.0.1:8521/). When the visualization loads, press Reset, then Run. + + +## Files + +* ``Introduction to Mesa Tutorial Code.ipynb``: Jupyter Notebook with all the steps as described in the tutorial. +* ``money_model.py``: Final version of the model. +* ``viz_money_model.py``: Creates and launches interactive visualization. + +## Further Reading + +The full tutorial describing how the model is built can be found at: +https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html + +This model is drawn from econophysics and presents a statistical mechanics approach to wealth distribution. Some examples of further reading on the topic can be found at: + +[Milakovic, M. A Statistical Equilibrium Model of Wealth Distribution. February, 2001.](https://editorialexpress.com/cgi-bin/conference/download.cgi?db_name=SCE2001&paper_id=214) + +[Dragulescu, A and Yakovenko, V. Statistical Mechanics of Money, Income, and Wealth: A Short Survey. November, 2002](http://arxiv.org/pdf/cond-mat/0211175v1.pdf) +____ +You will need to open the file as a Jupyter (aka iPython) notebook with an iPython 3 kernel. Required dependencies are listed in the provided `requirements.txt` file which can be installed by running `pip install -r requirements.txt` diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py new file mode 100644 index 00000000000..76ebc516b36 --- /dev/null +++ b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -0,0 +1,73 @@ +import mesa + + +def compute_gini(model): + agent_wealths = [agent.wealth for agent in model.schedule.agents] + x = sorted(agent_wealths) + N = model.num_agents + B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x)) + return 1 + (1 / N) - 2 * B + + +class BoltzmannWealthModel(mesa.Model): + """A simple model of an economy where agents exchange currency at random. + + All the agents begin with one unit of currency, and each time step can give + a unit of currency to another agent. Note how, over time, this produces a + highly skewed distribution of wealth. + """ + + def __init__(self, N=100, width=10, height=10): + self.num_agents = N + self.grid = mesa.space.MultiGrid(width, height, True) + self.schedule = mesa.time.RandomActivation(self) + self.datacollector = mesa.DataCollector( + model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} + ) + # Create agents + for i in range(self.num_agents): + a = MoneyAgent(i, self) + self.schedule.add(a) + # Add the agent to a random grid cell + x = self.random.randrange(self.grid.width) + y = self.random.randrange(self.grid.height) + self.grid.place_agent(a, (x, y)) + + self.running = True + self.datacollector.collect(self) + + def step(self): + self.schedule.step() + # collect data + self.datacollector.collect(self) + + def run_model(self, n): + for i in range(n): + self.step() + + +class MoneyAgent(mesa.Agent): + """An agent with fixed initial wealth.""" + + def __init__(self, unique_id, model): + super().__init__(unique_id, model) + self.wealth = 1 + + def move(self): + possible_steps = self.model.grid.get_neighborhood( + self.pos, moore=True, include_center=False + ) + new_position = self.random.choice(possible_steps) + self.model.grid.move_agent(self, new_position) + + def give_money(self): + cellmates = self.model.grid.get_cell_list_contents([self.pos]) + if len(cellmates) > 1: + other = self.random.choice(cellmates) + other.wealth += 1 + self.wealth -= 1 + + def step(self): + self.move() + if self.wealth > 0: + self.give_money() diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/server.py b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/server.py new file mode 100644 index 00000000000..a49546ce741 --- /dev/null +++ b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/server.py @@ -0,0 +1,40 @@ +import mesa + +from .model import BoltzmannWealthModel + + +def agent_portrayal(agent): + portrayal = {"Shape": "circle", "Filled": "true", "r": 0.5} + + if agent.wealth > 0: + portrayal["Color"] = "red" + portrayal["Layer"] = 0 + else: + portrayal["Color"] = "grey" + portrayal["Layer"] = 1 + portrayal["r"] = 0.2 + return portrayal + + +grid = mesa.visualization.CanvasGrid(agent_portrayal, 10, 10, 500, 500) +chart = mesa.visualization.ChartModule( + [{"Label": "Gini", "Color": "#0000FF"}], data_collector_name="datacollector" +) + +model_params = { + "N": mesa.visualization.Slider( + "Number of agents", + 100, + 2, + 200, + 1, + description="Choose how many agents to include in the model", + ), + "width": 10, + "height": 10, +} + +server = mesa.visualization.ModularServer( + BoltzmannWealthModel, [grid, chart], "Money Model", model_params +) +server.port = 8521 diff --git a/examples/basic/boltzmann_wealth_model/requirements.txt b/examples/basic/boltzmann_wealth_model/requirements.txt new file mode 100644 index 00000000000..b93c188674f --- /dev/null +++ b/examples/basic/boltzmann_wealth_model/requirements.txt @@ -0,0 +1,4 @@ +jupyter +matplotlib +mesa~=1.1 +numpy diff --git a/examples/basic/boltzmann_wealth_model/run.py b/examples/basic/boltzmann_wealth_model/run.py new file mode 100644 index 00000000000..ea57809eb0a --- /dev/null +++ b/examples/basic/boltzmann_wealth_model/run.py @@ -0,0 +1,3 @@ +from boltzmann_wealth_model.server import server + +server.launch() diff --git a/examples/basic/schelling/README.md b/examples/basic/schelling/README.md new file mode 100644 index 00000000000..64cc9c83295 --- /dev/null +++ b/examples/basic/schelling/README.md @@ -0,0 +1,49 @@ +# Schelling Segregation Model + +## Summary + +The Schelling segregation model is a classic agent-based model, demonstrating how even a mild preference for similar neighbors can lead to a much higher degree of segregation than we would intuitively expect. The model consists of agents on a square grid, where each grid cell can contain at most one agent. Agents come in two colors: red and blue. They are happy if a certain number of their eight possible neighbors are of the same color, and unhappy otherwise. Unhappy agents will pick a random empty cell to move to each step, until they are happy. The model keeps running until there are no unhappy agents. + +By default, the number of similar neighbors the agents need to be happy is set to 3. That means the agents would be perfectly happy with a majority of their neighbors being of a different color (e.g. a Blue agent would be happy with five Red neighbors and three Blue ones). Despite this, the model consistently leads to a high degree of segregation, with most agents ending up with no neighbors of a different color. + +## 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 interactively, run ``mesa runserver`` in this directory. e.g. + +``` + $ mesa runserver +``` + +Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run. + +To view and run some example model analyses, launch the IPython Notebook and open ``analysis.ipynb``. Visualizing the analysis also requires [matplotlib](http://matplotlib.org/). + +## How to Run without the GUI + +To run the model with the grid displayed as an ASCII text, run `python run_ascii.py` in this directory. + +## Files + +* ``run.py``: Launches a model visualization server. +* ``run_ascii.py``: Run the model in text mode. +* ``schelling.py``: Contains the agent class, and the overall model class. +* ``server.py``: Defines classes for visualizing the model in the browser via Mesa's modular server, and instantiates a visualization server. +* ``analysis.ipynb``: Notebook demonstrating how to run experiments and parameter sweeps on the model. + +## Further Reading + +Schelling's original paper describing the model: + +[Schelling, Thomas C. Dynamic Models of Segregation. Journal of Mathematical Sociology. 1971, Vol. 1, pp 143-186.](https://www.stat.berkeley.edu/~aldous/157/Papers/Schelling_Seg_Models.pdf) + +An interactive, browser-based explanation and implementation: + +[Parable of the Polygons](http://ncase.me/polygons/), by Vi Hart and Nicky Case. diff --git a/examples/basic/schelling/analysis.ipynb b/examples/basic/schelling/analysis.ipynb new file mode 100644 index 00000000000..50f382c66a0 --- /dev/null +++ b/examples/basic/schelling/analysis.ipynb @@ -0,0 +1,457 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Schelling Segregation Model\n", + "\n", + "## Background\n", + "\n", + "The Schelling (1971) segregation model is a classic of agent-based modeling, demonstrating how agents following simple rules lead to the emergence of qualitatively different macro-level outcomes. Agents are randomly placed on a grid. There are two types of agents, one constituting the majority and the other the minority. All agents want a certain number (generally, 3) of their 8 surrounding neighbors to be of the same type in order for them to be happy. Unhappy agents will move to a random available grid space. While individual agents do not have a preference for a segregated outcome (e.g. they would be happy with 3 similar neighbors and 5 different ones), the aggregate outcome is nevertheless heavily segregated.\n", + "\n", + "## Implementation\n", + "\n", + "This is a demonstration of running a Mesa model in an IPython Notebook. The actual model and agent code are implemented in Schelling.py, in the same directory as this notebook. Below, we will import the model class, instantiate it, run it, and plot the time series of the number of happy agents." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "%matplotlib inline\n", + "\n", + "from model import Schelling" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we instantiate a model instance: a 10x10 grid, with an 80% change of an agent being placed in each cell, approximately 20% of agents set as minorities, and agents wanting at least 3 similar neighbors." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "model = Schelling(10, 10, 0.8, 0.2, 3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We want to run the model until all the agents are happy with where they are. However, there's no guarantee that a given model instantiation will *ever* settle down. So let's run it for either 100 steps or until it stops on its own, whichever comes first:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100\n" + ] + } + ], + "source": [ + "while model.running and model.schedule.steps < 100:\n", + " model.step()\n", + "print(model.schedule.steps) # Show how many steps have actually run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The model has a DataCollector object, which checks and stores how many agents are happy at the end of each step. It can also generate a pandas DataFrame of the data it has collected:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "model_out = model.datacollector.get_model_vars_dataframe()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
happy
00
173
267
372
472
\n", + "
" + ], + "text/plain": [ + " happy\n", + "0 0\n", + "1 73\n", + "2 72\n", + "3 73\n", + "4 72" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model_out.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we can plot the 'happy' series:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "model_out.happy.plot()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For testing purposes, here is a table giving each agent's x and y values at each step." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "x_positions = model.datacollector.get_agent_vars_dataframe()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
xy
StepAgentID
0(0, 0)01
(0, 1)89
(0, 2)52
(0, 3)00
(0, 4)17
\n", + "
" + ], + "text/plain": [ + " x y\n", + "Step AgentID \n", + "0 (0, 0) 0 1\n", + " (0, 1) 8 9\n", + " (0, 2) 5 2\n", + " (0, 3) 0 0\n", + " (0, 4) 1 7" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x_positions.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Effect of Homophily on segregation\n", + "\n", + "Now, we can do a parameter sweep to see how segregation changes with homophily.\n", + "\n", + "First, we create a function which takes a model instance and returns what fraction of agents are segregated -- that is, have no neighbors of the opposite type." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "from mesa.batchrunner import BatchRunner" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "def get_segregation(model):\n", + " \"\"\"\n", + " Find the % of agents that only have neighbors of their same type.\n", + " \"\"\"\n", + " segregated_agents = 0\n", + " for agent in model.schedule.agents:\n", + " segregated = True\n", + " for neighbor in model.grid.iter_neighbors(agent.pos, True):\n", + " if neighbor.type != agent.type:\n", + " segregated = False\n", + " break\n", + " if segregated:\n", + " segregated_agents += 1\n", + " return segregated_agents / model.schedule.get_agent_count()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we set up the batch run, with a dictionary of fixed and changing parameters. Let's hold everything fixed except for Homophily." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "fixed_params = {\"height\": 10, \"width\": 10, \"density\": 0.8, \"minority_pc\": 0.2}\n", + "variable_parms = {\"homophily\": range(1, 9)}" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "model_reporters = {\"Segregated_Agents\": get_segregation}" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "param_sweep = BatchRunner(\n", + " Schelling,\n", + " variable_parameters=variable_parms,\n", + " fixed_parameters=fixed_params,\n", + " iterations=10,\n", + " max_steps=200,\n", + " model_reporters=model_reporters,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "80it [00:15, 3.13it/s]\n" + ] + } + ], + "source": [ + "param_sweep.run_all()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "df = param_sweep.get_model_vars_dataframe()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.scatter(df.homophily, df.Segregated_Agents)\n", + "plt.grid(True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.9" + }, + "widgets": { + "state": {}, + "version": "1.1.2" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/examples/basic/schelling/model.py b/examples/basic/schelling/model.py new file mode 100644 index 00000000000..821b68af951 --- /dev/null +++ b/examples/basic/schelling/model.py @@ -0,0 +1,89 @@ +import mesa + + +class SchellingAgent(mesa.Agent): + """ + Schelling segregation agent + """ + + def __init__(self, pos, model, agent_type): + """ + Create a new Schelling agent. + + Args: + unique_id: Unique identifier for the agent. + x, y: Agent initial location. + agent_type: Indicator for the agent's type (minority=1, majority=0) + """ + super().__init__(pos, model) + self.pos = pos + self.type = agent_type + + def step(self): + similar = 0 + for neighbor in self.model.grid.iter_neighbors(self.pos, True): + if neighbor.type == self.type: + similar += 1 + + # If unhappy, move: + if similar < self.model.homophily: + self.model.grid.move_to_empty(self) + else: + self.model.happy += 1 + + +class Schelling(mesa.Model): + """ + Model class for the Schelling segregation model. + """ + + def __init__(self, width=20, height=20, density=0.8, minority_pc=0.2, homophily=3): + """ """ + + self.width = width + self.height = height + self.density = density + self.minority_pc = minority_pc + self.homophily = homophily + + self.schedule = mesa.time.RandomActivation(self) + self.grid = mesa.space.SingleGrid(width, height, torus=True) + + self.happy = 0 + self.datacollector = mesa.DataCollector( + {"happy": "happy"}, # Model-level count of happy agents + # For testing purposes, agent's individual x and y + {"x": lambda a: a.pos[0], "y": lambda a: a.pos[1]}, + ) + + # Set up agents + # We use a grid iterator that returns + # the coordinates of a cell as well as + # its contents. (coord_iter) + for cell in self.grid.coord_iter(): + x = cell[1] + y = cell[2] + if self.random.random() < self.density: + if self.random.random() < self.minority_pc: + agent_type = 1 + else: + agent_type = 0 + + agent = SchellingAgent((x, y), self, agent_type) + self.grid.place_agent(agent, (x, y)) + self.schedule.add(agent) + + self.running = True + self.datacollector.collect(self) + + def step(self): + """ + Run one step of the model. If All agents are happy, halt the model. + """ + self.happy = 0 # Reset counter of happy agents + self.schedule.step() + # collect data + self.datacollector.collect(self) + + if self.happy == self.schedule.get_agent_count(): + self.running = False diff --git a/examples/basic/schelling/requirements.txt b/examples/basic/schelling/requirements.txt new file mode 100644 index 00000000000..19b805acb1f --- /dev/null +++ b/examples/basic/schelling/requirements.txt @@ -0,0 +1,3 @@ +jupyter +matplotlib +mesa~=1.1 diff --git a/examples/basic/schelling/run.py b/examples/basic/schelling/run.py new file mode 100644 index 00000000000..a25f3b1294e --- /dev/null +++ b/examples/basic/schelling/run.py @@ -0,0 +1,3 @@ +from server import server + +server.launch() diff --git a/examples/basic/schelling/run_ascii.py b/examples/basic/schelling/run_ascii.py new file mode 100644 index 00000000000..8d70c39756e --- /dev/null +++ b/examples/basic/schelling/run_ascii.py @@ -0,0 +1,49 @@ +import mesa + +from model import Schelling + + +class SchellingTextVisualization(mesa.visualization.TextVisualization): + """ + ASCII visualization for schelling model + """ + + def __init__(self, model): + """ + Create new Schelling ASCII visualization. + """ + self.model = model + + grid_viz = mesa.visualization.TextGrid(self.model.grid, self.print_ascii_agent) + happy_viz = mesa.visualization.TextData(self.model, "happy") + self.elements = [grid_viz, happy_viz] + + @staticmethod + def print_ascii_agent(a): + """ + Minority agents are X, Majority are O. + """ + if a.type == 0: + return "O" + if a.type == 1: + return "X" + + +if __name__ == "__main__": + model_params = { + "height": 20, + "width": 20, + # Agent density, from 0.8 to 1.0 + "density": 0.8, + # Fraction minority, from 0.2 to 1.0 + "minority_pc": 0.2, + # Homophily, from 3 to 8 + "homophily": 3, + } + + model = Schelling(**model_params) + viz = SchellingTextVisualization(model) + for i in range(10): + print("Step:", i) + viz.step() + print("---") diff --git a/examples/basic/schelling/server.py b/examples/basic/schelling/server.py new file mode 100644 index 00000000000..fd643096db3 --- /dev/null +++ b/examples/basic/schelling/server.py @@ -0,0 +1,46 @@ +import mesa + +from model import Schelling + + +def get_happy_agents(model): + """ + Display a text count of how many happy agents there are. + """ + return f"Happy agents: {model.happy}" + + +def schelling_draw(agent): + """ + Portrayal Method for canvas + """ + if agent is None: + return + portrayal = {"Shape": "circle", "r": 0.5, "Filled": "true", "Layer": 0} + + if agent.type == 0: + portrayal["Color"] = ["#FF0000", "#FF9999"] + portrayal["stroke_color"] = "#00FF00" + else: + portrayal["Color"] = ["#0000FF", "#9999FF"] + portrayal["stroke_color"] = "#000000" + return portrayal + + +canvas_element = mesa.visualization.CanvasGrid(schelling_draw, 20, 20, 500, 500) +happy_chart = mesa.visualization.ChartModule([{"Label": "happy", "Color": "Black"}]) + +model_params = { + "height": 20, + "width": 20, + "density": mesa.visualization.Slider("Agent density", 0.8, 0.1, 1.0, 0.1), + "minority_pc": mesa.visualization.Slider("Fraction minority", 0.2, 0.00, 1.0, 0.05), + "homophily": mesa.visualization.Slider("Homophily", 3, 0, 8, 1), +} + +server = mesa.visualization.ModularServer( + Schelling, + [canvas_element, get_happy_agents, happy_chart], + "Schelling", + model_params, +) diff --git a/examples/basic/virus_on_network/README.md b/examples/basic/virus_on_network/README.md new file mode 100644 index 00000000000..b9fd1e94ecb --- /dev/null +++ b/examples/basic/virus_on_network/README.md @@ -0,0 +1,46 @@ +# Virus on a Network + +## Summary + +This model is based on the NetLogo model "Virus on Network". + +For more information about this model, read the NetLogo's web page: http://ccl.northwestern.edu/netlogo/models/VirusonaNetwork. + +JavaScript library used in this example to render the network: [d3.js](https://d3js.org/). + +## 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 interactively, run ``mesa runserver`` in this directory. e.g. + +``` + $ mesa runserver +``` + +Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run. + +## Files + +* ``run.py``: Launches a model visualization server. +* ``model.py``: Contains the agent class, and the overall model class. +* ``server.py``: Defines classes for visualizing the model (network layout) in the browser via Mesa's modular server, and instantiates a visualization server. + +## Further Reading + +The full tutorial describing how the model is built can be found at: +https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html + + +[Stonedahl, F. and Wilensky, U. (2008). NetLogo Virus on a Network model](http://ccl.northwestern.edu/netlogo/models/VirusonaNetwork). +Center for Connected Learning and Computer-Based Modeling, Northwestern University, Evanston, IL. + + +[Wilensky, U. (1999). NetLogo](http://ccl.northwestern.edu/netlogo/) +Center for Connected Learning and Computer-Based Modeling, Northwestern University, Evanston, IL. diff --git a/examples/basic/virus_on_network/requirements.txt b/examples/basic/virus_on_network/requirements.txt new file mode 100644 index 00000000000..9d01589a901 --- /dev/null +++ b/examples/basic/virus_on_network/requirements.txt @@ -0,0 +1,2 @@ +networkx>=2.0 +mesa~=1.1 \ No newline at end of file diff --git a/examples/basic/virus_on_network/run.py b/examples/basic/virus_on_network/run.py new file mode 100644 index 00000000000..9f1ef7292ae --- /dev/null +++ b/examples/basic/virus_on_network/run.py @@ -0,0 +1,3 @@ +from virus_on_network.server import server + +server.launch() diff --git a/examples/basic/virus_on_network/virus_on_network/__init__.py b/examples/basic/virus_on_network/virus_on_network/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/basic/virus_on_network/virus_on_network/model.py b/examples/basic/virus_on_network/virus_on_network/model.py new file mode 100644 index 00000000000..7d1e68f7f15 --- /dev/null +++ b/examples/basic/virus_on_network/virus_on_network/model.py @@ -0,0 +1,160 @@ +import math +from enum import Enum +import networkx as nx + +import mesa + + +class State(Enum): + SUSCEPTIBLE = 0 + INFECTED = 1 + RESISTANT = 2 + + +def number_state(model, state): + return sum(1 for a in model.grid.get_all_cell_contents() if a.state is state) + + +def number_infected(model): + return number_state(model, State.INFECTED) + + +def number_susceptible(model): + return number_state(model, State.SUSCEPTIBLE) + + +def number_resistant(model): + return number_state(model, State.RESISTANT) + + +class VirusOnNetwork(mesa.Model): + """A virus model with some number of agents""" + + def __init__( + self, + num_nodes=10, + avg_node_degree=3, + initial_outbreak_size=1, + virus_spread_chance=0.4, + virus_check_frequency=0.4, + recovery_chance=0.3, + gain_resistance_chance=0.5, + ): + + self.num_nodes = num_nodes + prob = avg_node_degree / self.num_nodes + self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=prob) + self.grid = mesa.space.NetworkGrid(self.G) + self.schedule = mesa.time.RandomActivation(self) + self.initial_outbreak_size = ( + initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes + ) + self.virus_spread_chance = virus_spread_chance + self.virus_check_frequency = virus_check_frequency + self.recovery_chance = recovery_chance + self.gain_resistance_chance = gain_resistance_chance + + self.datacollector = mesa.DataCollector( + { + "Infected": number_infected, + "Susceptible": number_susceptible, + "Resistant": number_resistant, + } + ) + + # Create agents + for i, node in enumerate(self.G.nodes()): + a = VirusAgent( + i, + self, + State.SUSCEPTIBLE, + self.virus_spread_chance, + self.virus_check_frequency, + self.recovery_chance, + self.gain_resistance_chance, + ) + self.schedule.add(a) + # Add the agent to the node + self.grid.place_agent(a, node) + + # Infect some nodes + infected_nodes = self.random.sample(list(self.G), self.initial_outbreak_size) + for a in self.grid.get_cell_list_contents(infected_nodes): + a.state = State.INFECTED + + self.running = True + self.datacollector.collect(self) + + def resistant_susceptible_ratio(self): + try: + return number_state(self, State.RESISTANT) / number_state( + self, State.SUSCEPTIBLE + ) + except ZeroDivisionError: + return math.inf + + def step(self): + self.schedule.step() + # collect data + self.datacollector.collect(self) + + def run_model(self, n): + for i in range(n): + self.step() + + +class VirusAgent(mesa.Agent): + def __init__( + self, + unique_id, + model, + initial_state, + virus_spread_chance, + virus_check_frequency, + recovery_chance, + gain_resistance_chance, + ): + super().__init__(unique_id, model) + + self.state = initial_state + + self.virus_spread_chance = virus_spread_chance + self.virus_check_frequency = virus_check_frequency + self.recovery_chance = recovery_chance + self.gain_resistance_chance = gain_resistance_chance + + def try_to_infect_neighbors(self): + neighbors_nodes = self.model.grid.get_neighbors(self.pos, include_center=False) + susceptible_neighbors = [ + agent + for agent in self.model.grid.get_cell_list_contents(neighbors_nodes) + if agent.state is State.SUSCEPTIBLE + ] + for a in susceptible_neighbors: + if self.random.random() < self.virus_spread_chance: + a.state = State.INFECTED + + def try_gain_resistance(self): + if self.random.random() < self.gain_resistance_chance: + self.state = State.RESISTANT + + def try_remove_infection(self): + # Try to remove + if self.random.random() < self.recovery_chance: + # Success + self.state = State.SUSCEPTIBLE + self.try_gain_resistance() + else: + # Failed + self.state = State.INFECTED + + def try_check_situation(self): + if self.random.random() < self.virus_check_frequency: + # Checking... + if self.state is State.INFECTED: + self.try_remove_infection() + + def step(self): + if self.state is State.INFECTED: + self.try_to_infect_neighbors() + self.try_check_situation() diff --git a/examples/basic/virus_on_network/virus_on_network/server.py b/examples/basic/virus_on_network/virus_on_network/server.py new file mode 100644 index 00000000000..a8f47c61e6b --- /dev/null +++ b/examples/basic/virus_on_network/virus_on_network/server.py @@ -0,0 +1,133 @@ +import math + +import mesa + +from .model import VirusOnNetwork, State, number_infected + + +def network_portrayal(G): + # The model ensures there is always 1 agent per node + + def node_color(agent): + return {State.INFECTED: "#FF0000", State.SUSCEPTIBLE: "#008000"}.get( + agent.state, "#808080" + ) + + def edge_color(agent1, agent2): + if State.RESISTANT in (agent1.state, agent2.state): + return "#000000" + return "#e8e8e8" + + def edge_width(agent1, agent2): + if State.RESISTANT in (agent1.state, agent2.state): + return 3 + return 2 + + def get_agents(source, target): + return G.nodes[source]["agent"][0], G.nodes[target]["agent"][0] + + portrayal = dict() + portrayal["nodes"] = [ + { + "size": 6, + "color": node_color(agents[0]), + "tooltip": f"id: {agents[0].unique_id}
state: {agents[0].state.name}", + } + for (_, agents) in G.nodes.data("agent") + ] + + portrayal["edges"] = [ + { + "source": source, + "target": target, + "color": edge_color(*get_agents(source, target)), + "width": edge_width(*get_agents(source, target)), + } + for (source, target) in G.edges + ] + + return portrayal + + +network = mesa.visualization.NetworkModule(network_portrayal, 500, 500) +chart = mesa.visualization.ChartModule( + [ + {"Label": "Infected", "Color": "#FF0000"}, + {"Label": "Susceptible", "Color": "#008000"}, + {"Label": "Resistant", "Color": "#808080"}, + ] +) + + +def get_resistant_susceptible_ratio(model): + ratio = model.resistant_susceptible_ratio() + ratio_text = "∞" if ratio is math.inf else f"{ratio:.2f}" + infected_text = str(number_infected(model)) + + return "Resistant/Susceptible Ratio: {}
Infected Remaining: {}".format( + ratio_text, infected_text + ) + + +model_params = { + "num_nodes": mesa.visualization.Slider( + "Number of agents", + 10, + 10, + 100, + 1, + description="Choose how many agents to include in the model", + ), + "avg_node_degree": mesa.visualization.Slider( + "Avg Node Degree", 3, 3, 8, 1, description="Avg Node Degree" + ), + "initial_outbreak_size": mesa.visualization.Slider( + "Initial Outbreak Size", + 1, + 1, + 10, + 1, + description="Initial Outbreak Size", + ), + "virus_spread_chance": mesa.visualization.Slider( + "Virus Spread Chance", + 0.4, + 0.0, + 1.0, + 0.1, + description="Probability that susceptible neighbor will be infected", + ), + "virus_check_frequency": mesa.visualization.Slider( + "Virus Check Frequency", + 0.4, + 0.0, + 1.0, + 0.1, + description="Frequency the nodes check whether they are infected by " "a virus", + ), + "recovery_chance": mesa.visualization.Slider( + "Recovery Chance", + 0.3, + 0.0, + 1.0, + 0.1, + description="Probability that the virus will be removed", + ), + "gain_resistance_chance": mesa.visualization.Slider( + "Gain Resistance Chance", + 0.5, + 0.0, + 1.0, + 0.1, + description="Probability that a recovered agent will become " + "resistant to this virus in the future", + ), +} + +server = mesa.visualization.ModularServer( + VirusOnNetwork, + [network, get_resistant_susceptible_ratio, chart], + "Virus Model", + model_params, +) +server.port = 8521 From dcd6132d6544b7bcdde001b85fa9b7a998cc06fd Mon Sep 17 00:00:00 2001 From: ItsQuinnMoore Date: Mon, 24 Apr 2023 11:13:30 -0600 Subject: [PATCH 008/116] Fixed consistent capitalization of files. --- examples/advanced/pd_grid/analysis.ipynb | 231 ++++++++++++++++++ examples/advanced/pd_grid/pd_grid/__init__.py | 0 examples/advanced/pd_grid/pd_grid/agent.py | 49 ++++ examples/advanced/pd_grid/pd_grid/model.py | 62 +++++ .../advanced/pd_grid/pd_grid/portrayal.py | 19 ++ examples/advanced/pd_grid/pd_grid/server.py | 22 ++ examples/advanced/pd_grid/readme.md | 42 ++++ examples/advanced/pd_grid/requirements.txt | 3 + examples/advanced/pd_grid/run.py | 3 + 9 files changed, 431 insertions(+) create mode 100644 examples/advanced/pd_grid/analysis.ipynb create mode 100644 examples/advanced/pd_grid/pd_grid/__init__.py create mode 100644 examples/advanced/pd_grid/pd_grid/agent.py create mode 100644 examples/advanced/pd_grid/pd_grid/model.py create mode 100644 examples/advanced/pd_grid/pd_grid/portrayal.py create mode 100644 examples/advanced/pd_grid/pd_grid/server.py create mode 100644 examples/advanced/pd_grid/readme.md create mode 100644 examples/advanced/pd_grid/requirements.txt create mode 100644 examples/advanced/pd_grid/run.py diff --git a/examples/advanced/pd_grid/analysis.ipynb b/examples/advanced/pd_grid/analysis.ipynb new file mode 100644 index 00000000000..53a63345884 --- /dev/null +++ b/examples/advanced/pd_grid/analysis.ipynb @@ -0,0 +1,231 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Demographic Prisoner's Dilemma\n", + "\n", + "The Demographic Prisoner's Dilemma is a family of variants on the classic two-player [Prisoner's Dilemma](https://en.wikipedia.org/wiki/Prisoner's_dilemma), first developed by [Joshua Epstein](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.8.8629&rep=rep1&type=pdf). The model consists of agents, each with a strategy of either Cooperate or Defect. Each agent's payoff is based on its strategy and the strategies of its spatial neighbors. After each step of the model, the agents adopt the strategy of their neighbor with the highest total score. \n", + "\n", + "The specific variant presented here is adapted from the [Evolutionary Prisoner's Dilemma](http://ccl.northwestern.edu/netlogo/models/PDBasicEvolutionary) model included with NetLogo. Its payoff table is a slight variant of the traditional PD payoff table:\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "
**Cooperate****Defect**
**Cooperate**1, 10, *D*
**Defect***D*, 00, 0
\n", + "\n", + "Where *D* is the defection bonus, generally set higher than 1. In these runs, the defection bonus is set to $D=1.6$.\n", + "\n", + "The Demographic Prisoner's Dilemma demonstrates how simple rules can lead to the emergence of widespread cooperation, despite the Defection strategy dominiating each individual interaction game. However, it is also interesting for another reason: it is known to be sensitive to the activation regime employed in it.\n", + "\n", + "Below, we demonstrate this by instantiating the same model (with the same random seed) three times, with three different activation regimes: \n", + "\n", + "* Sequential activation, where agents are activated in the order they were added to the model;\n", + "* Random activation, where they are activated in random order every step;\n", + "* Simultaneous activation, simulating them all being activated simultaneously.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from pd_grid.model import PdGrid\n", + "\n", + "import numpy as np\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.gridspec\n", + "\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Helper functions" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "bwr = plt.get_cmap(\"bwr\")\n", + "\n", + "\n", + "def draw_grid(model, ax=None):\n", + " \"\"\"\n", + " Draw the current state of the grid, with Defecting agents in red\n", + " and Cooperating agents in blue.\n", + " \"\"\"\n", + " if not ax:\n", + " fig, ax = plt.subplots(figsize=(6, 6))\n", + " grid = np.zeros((model.grid.width, model.grid.height))\n", + " for agent, x, y in model.grid.coord_iter():\n", + " if agent.move == \"D\":\n", + " grid[y][x] = 1\n", + " else:\n", + " grid[y][x] = 0\n", + " ax.pcolormesh(grid, cmap=bwr, vmin=0, vmax=1)\n", + " ax.axis(\"off\")\n", + " ax.set_title(\"Steps: {}\".format(model.schedule.steps))" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "def run_model(model):\n", + " \"\"\"\n", + " Run an experiment with a given model, and plot the results.\n", + " \"\"\"\n", + " fig = plt.figure(figsize=(12, 8))\n", + "\n", + " ax1 = fig.add_subplot(231)\n", + " ax2 = fig.add_subplot(232)\n", + " ax3 = fig.add_subplot(233)\n", + " ax4 = fig.add_subplot(212)\n", + "\n", + " draw_grid(model, ax1)\n", + " model.run(10)\n", + " draw_grid(model, ax2)\n", + " model.run(10)\n", + " draw_grid(model, ax3)\n", + " model.datacollector.get_model_vars_dataframe().plot(ax=ax4)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# Set the random seed\n", + "seed = 21" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Sequential Activation" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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W878SjcAK9dtavJIJElSO/rpT3/KxIkTOf/883dPO+qoo1i0aBEf/ehHOeKII3jpS1/KkiVLgGrwHmz6LbfcwgknnMBZZ53FYYcdxvnnn8+uXbsA+PGPf8xxxx3HggULeNvb3samTdXcMnfuXC699FIWLVrEd77zHb761a/yile8giOPPJK3vOUtbN68mdtuu43rr7+ej370oxx11FHcf//9vPvd7+bqq6/evY2LL76YBQsW8NKXvnT31eT169dzyimnsGDBAt7//vdzyCGH8Pjjjw95HDZt2sQvfvELLr/88ucF6V27dvGBD3yAww8/nDPOOIPTTz99977vvPNOXvWqV3H00Ufzute9jsceewyAE088kQsuuIBjjjmG3t5e/v3f/51t27Zx0UUXsWTJEo466iiWLFnCz372M4466iiOOuooXv7yl7NxY/2qDEZsfxcRVwBnAOtSSkdk0z4HvAHYBtwPvCel9FQ272PA+4CdwH9MKf0om34q8N+A8cDXUkr/XPunUxu9WZBesWYjC14wrcmjkSRJRX3yf9/Lskefqek2DztwHy5+w+HDLnPPPfdw9NFH7zH9mmuu4e677+a3v/0tjz/+OK94xSs44YQTuO222wadDvDrX/+aZcuWccghh3DqqadyzTXXcOKJJ/LpT3+am266ialTp/KZz3yGL3zhC1x00UVAtVfyrbfeCsCGDRv467/+awA+8YlPcPnll/OhD32IN77xjZxxxhm89a1vHfQ5zJgxg7vuuot/+7d/4/Of/zxf+9rX+OQnP8lJJ53Exz72MX74wx9y2WWXDXscrrvuOk499VR6e3vZf//9ueuuu1iwYAHXXHMNDz74IL///e9Zt24dL3nJS3jve9/L9u3b+dCHPsT3vvc9Zs6cyZIlS/j4xz/OFVdcAcCOHTv49a9/zQ033MAnP/lJbrrpJi699FKWLl3Kv/7rvwLwhje8gS9/+cscf/zxbNq0iSlTpgw7xjJG00f6SuBfga/3m3Yj8LGU0o6I+AzwMeCCiDgMOBs4HDgQuCkierN1vgycAqwG7oiI61NKy2hBc6btxV4Tx7NirVekJUlS7dx6662cc845jB8/nkqlwqte9SruuOOOIafvs88+HHPMMRx66KEAnHPOOdx6661MmTKFZcuWcfzxxwOwbds2jjvuuN37+au/+qvd9++55x4+8YlP8NRTT7Fp0yZe97rXjWqsb37zmwE4+uijueaaa3aP/9prrwXg1FNPZdq04S84Ll68mA9/+MMAnH322SxevJgFCxZw66238ra3vY1x48Yxe/ZsXv3qVwOwfPly7rnnHk455RSgWppywAEHDDqmBx98cNB9Hn/88XzkIx/hne98J29+85uZM2fOqJ5vESMG6ZTSzyNi7oBpP+738Hag70+ZM4FvpZS2An+MiFXAMdm8VSmlBwAi4lvZsi0ZpMeNC3oqXaxc5wcOJUlqZyNdOa6Xww8/fHepQn8ppUGXH2o67NmuLSJIKXHKKaewePHiQdeZOnXq7vvvfve7ue666zjyyCO58sorueWWW0bxDGDy5MkAjB8/nh07dow4zoE2bNjAT37yE+655x4igp07dxIRfPaznx32OBx++OH88pe/HPWYBrrwwgt5/etfzw033MCxxx7LTTfdxItf/OJRjzuPWtRIvxf4QXb/IOCRfvNWZ9OGmt6yemZ1s9zOHZIkqYCTTjqJrVu38tWvfnX3tDvuuINp06axZMkSdu7cyfr16/n5z3/OMcccwwknnDDodKiWdvzxj39k165dLFmyhEWLFnHsscfyi1/8glWrVgGwefPmITuCbNy4kQMOOIDt27fzjW98Y/f07u7u3PXDixYt4tvf/jZQrdF+8sknh1z26quv5l3vehcPPfQQDz74II888gjz5s3j1ltvZdGiRXz3u99l165drF27dne4nz9/PuvXr98dpLdv386999477JgGPo/777+fl770pVxwwQUsXLiwbt1CoGSQjoiPAzuAvn+VwTpcp2GmD7bN8yJiaUQsXb9+fZnhldJb6WLdxq08vdnOHZIkKZ+I4Nprr+XGG2/khS98IYcffjiXXHIJ73jHO3jZy17GkUceyUknncRnP/tZZs+ezVlnnTXodIDjjjuOCy+8kCOOOIJ58+Zx1llnMXPmTK688krOOeccXvayl3HssccOGRg/9alP8cpXvpJTTjnleVdmzz77bD73uc/x8pe/nPvvv39Uz+viiy/mxz/+MQsWLOAHP/gBBxxwAN3d3YMuu3jxYs4666znTXvLW97CN7/5Td7ylrcwZ84cjjjiCN7//vfzyle+kn333ZdJkyZx9dVXc8EFF3DkkUdy1FFHcdtttw07ple/+tUsW7Zs94cNv/SlL3HEEUdw5JFHstde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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "m = PdGrid(50, 50, \"Sequential\", seed=seed)\n", + "run_model(m)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Random Activation" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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/oQQJsUYy9PZpuhnpC9/eBrT56/M6j5H7dSdS48fvc3rp06dyr777vuZ5ffffz+TJ0/m7bff5pNPPmG//fZjzJgxvPzyyw0uB3j99deZPn06u+yyC+PHj+f+++9n7Nix/OIXv+Dpp5+mS5cuXH311VxzzTVccsklQF2v5AkTJgBQWVnJt771LQAuvvhibrrpJs477zxOOOGEzYVzQ3r27Mmbb77J9ddfz29+8xv+8pe/cPnll3P44Ydz0UUX8fjjj3PjjTc2+jg9+OCDjB8/nmHDhrHjjjvy5ptvss8++3D//fczd+5cpkyZwuLFixk5ciRnn302Gzdu5LzzzuMf//gHvXr14q677uJnP/sZN998MwDV1dW8/vrrPProo1x++eU8/fTTXHHFFUycOJE//vGPABx//PFcd911HHLIIaxevZpOnTo1OkZJUm7U1kYq12yoK47rFcVb/7xy/Wdnkbt2bEef7h3p26MTB+1WtrlQ3lQ09+vRibKuHSktsThWYSv6QrqQTJgwgTPOOIPS0lL69OnDF77wBd54441tLu/evTv7778/u+66KwBnnHEGEyZMoFOnTkyfPp1DDjkEgA0bNnDQQQdtvp3TTjtt889Tp07l4osvZvny5axevZqjjz66WWM9+eSTAdh33325//77N4//gQceAGD8+PHssMMOje7jjjvu4IILLgDg9NNP54477mCfffZhwoQJfPnLX6akpIS+ffty2GGHAfDuu+8ydepUxo0bB9RFU/r169fgmObOndvgbR5yyCH84Ac/4Gtf+xonn3wyAwYMaNb9lSQls2LtRmYuXMm7i1YxY8EqZi9exfzl61m8aj0ba7acRS4J0LNrXYE8qKwLB+5atsWMcp/U964dLT/UOhT9K7mxmeNcGT16NPfee+9nlm8rJtNYfGbrt6JCCMQYGTduHHfccUeD23Tp0mXzz9/4xjd48MEH2XPPPbnlllt4/vnnm3EPoGPHjgCUlpZSXV3d5Di3VllZybPPPsvUqVMJIVBTU0MIgf/+7/9u9HEYPXo0r7zySrPHtLULL7yQY489lkcffZQDDzyQp59+OicfcpSktmZDdS3vf7KamQtWMXPhqrrieeEqFqz4tDd/j+3aM7xPN/YfvGOqQO64RYHcq2tH2pU26xQVUqvgqz2Bww8/nKqqKv785z9vXvbGG2+www47cNddd1FTU8OSJUt48cUX2X///RkzZkyDy6Eu2vHBBx9QW1vLXXfdxaGHHsqBBx7ISy+9xOzZswFYu3Yts2bNanAsq1atol+/fmzcuJHbbrtt8/Ju3bqxatWqtO7XoYceyt133w3Ak08+ybJly7a57r333suZZ57Jhx9+yNy5c5k3bx6DBw9mwoQJHHroodx3333U1tayaNGizcX98OHDWbJkyeZCeuPGjUyb1ngHxK3vx5w5c9hjjz34yU9+Qnl5+eZctiSpeWKMzF++judmLub652dz/p1vcfRvX2TUJY8z/tp/ccFdk7lpwvssXLGeAwZkxGrAAAAgAElEQVTvyE/Gj+Cv39yPVy46nMmXjOPu7xzEb0/biwu/OIJvHDKY8bv3Y++BO9Cvx3YW0Wpzin5GOh9CCDzwwANccMEFXHXVVXTq1Glz+7vVq1ez5557bp6d7du3LyeddBKvvPLKZ5bPnDmTgw46iAsvvJApU6YwZswYTjrpJEpKSrjllls444wzqKqqAuAXv/hFgx1BrrzySg444AB22WUX9thjj81F5+mnn863vvUtfv/73zc4e96QSy+9lDPOOIO77rqLL3zhC/Tr1+8zH+Tb5I477uDCCy/cYtkpp5zC7bffznXXXcczzzzD7rvvzrBhwzjggAPo0aMHHTp04N577+U//uM/WLFiBdXV1VxwwQWMHr3tdxUOO+wwrrrqKvbaay8uuugiJkyYwHPPPUdpaSmjRo3ii1/8YrPumyS1RavWb2TWotQM84JVvJuaaa6fW96pRydG9OvO4SN7M6JvN0b07c7gnl3o0M6iWGqKXTvy6Pnnn+c3v/kNjzzySL6HAkBVVRWlpaW0a9eOV155he9+97tMnjy56Q0bsHr1arp27UplZSX7778/L730En379s3yiBvWWl4fktRc1TW1fPDJmi0iGTMXrqJi2brN63Tt2I7hfbuliuVujOjXnWF9utFju/Z5HLlUONpk1w5lz0cffcRXvvIVamtr6dChwxbRlXQdd9xxLF++nA0bNvDzn/+8xYpoSWrtYoy8U7GC1z6o3DzTPHvJ6s1n5SstCezaswt77bw9Z+w/kOF9ujG8bzcG7LCdLeKkLLOQzqOxY8cyduzYfA9js6FDh/LWW29tsayyspIjjjjiM+s+88wzlJWVbXNfzf3QoySpaTFG3q5YwaNTFvDolAWbZ5r7dO/I8L7dOXRoT0b0rSuYh/TuSsd2npxDagkW0mpUWVlZ4niHJCm5+sXzP99ZwMfL19G+NHDIkJ6cf8RQDh/Rm7KuHfM9TKlNK9pCOsboW1T6jELP/EtSYzYVz/98Zz6PTlm4uXg+dEhPLjhyKEeN6kuPzmaapUJRlIV0p06dqKyspKyszGJam8UYqays9GyHkopKjJHJ85anYhtbFs/fHzeMcSP7WDxLBaooC+kBAwZQUVHBkiVL8j0UFZhOnTp5tkNJBW9bxfPnh/ayeJaKSJOFdAhhZ+BWoC9QC9wYY/xdCOHXwPHABmAO8M0Y4/IQwiBgBvBuahevxhi/k9rXvsAtwHbAo8D5McF78e3bt2fw4MHpbiZJUt5sKp7/+c4CHpu6ZfH8g3HDOHJUH1vRSUWmOTPS1cAPY4xvhhC6AZNCCE8BTwEXxRirQwhXAxcBP0ltMyfGuFcD+7oBOAd4lbpCejzwWKZ3QpKkQhRj5K15y3l0q+J5jMWz1Co0WUjHGBcAC1I/rwohzAD6xxifrLfaq8Cpje0nhNAP6B5jfCV1+VbgRCykJUmtyKbi+Z/vLOCxKQuYv2I9HUpL+PzQnhbPUiuTVkY6FdvYG3htq6vOBu6qd3lwCOEtYCVwcYzxX0B/oKLeOhWpZZIkFbXa2tTM85Qti+cxw3ryo6OHc+SoPnTvZPEstTbNLqRDCF2B+4ALYowr6y3/GXXxj9tSixYAA2OMlalM9IMhhNFAQ+01GsxHhxDOoS4CwsCBA5s7REmSWsy2Zp4tnqW2o1mFdAihPXVF9G0xxvvrLT8LOA44YtOHBmOMVUBV6udJIYQ5wDDqZqDrt1MYAMxv6PZijDcCNwKUl5fbGFiSVBBijMxcuIqH3p7Pw2/Pp2LZus3F84/HD+eIkRbPUlvSnK4dAbgJmBFjvKbe8vHUfbjwCzHGtfWW9wKWxhhrQgi7AkOB92OMS0MIq0IIB1IXDTkT+EN2744kSdn3UeVaHnr7Y/4xeT7vLV5NaUmqz/ORwxg32uJZaquaMyN9CPB1YEoIYdO5on8K/B7oCDyVOinKpjZ3Y4ArQgjVQA3wnRjj0tR23+XT9neP4QcNJUkFavHK9Tz8zgIeens+b89bDsB+g3bgyhN355jd+3p6bkmEQj+lcnl5eZw4cWK+hyFJagNWrN3IY1PriudX3q8kRhi9U3dO2HMnjttzJ/pvv12+hygpR0IIk2KM5elsU5RnNpQkKVvWbqjmqemLePjt+bwwawkbayKDe3bhvMOHcsKeOzGkd9d8D1FSgbKQliS1ORuqa3lx1hIeens+T01fxLqNNfTt3olvHDyIE/bsz+79u5OKLUrSNllIS5LahJrayGsfVPLQ5Pk8NnUhK9ZtZPvO7Tlpn/6csOdO7D9oR0pKLJ4lNZ+FtCSp1Yox8nbFCh6aPJ9H3pnP4lVVdO5QylGj+vClvfpz6NCetC8tyfcwJRUpC2lJUqvz3qK6Xs8PvT2fDyvX0qG0hLHDe3HCXjtxxIg+bNehNN9DlNQKWEhLklqFeUvX8vA783lo8nxmLlxFSYCDd+vJ98YO4ejd+9JjO3s9S8ouC2lJUtFasW4jD789nwfe+phJHy4DYJ+B23PZ8aM45nP96N2tU55HKKk1s5CWJBWV2trIS3M+4Z6JFTwxbSFV1bUM69OVHx89nBP23Imdd+yc7yFKaiMspCVJReGjyrXcO2ke906qYP6K9XTv1I7T9tuZL++7s+3qJOWFhbQkqWCt3VDNo1MWcs/Eebz2wVJCgM8P7cVFx4xk3Kg+dGrvhwYl5Y+FtCSpoMQYmfjhMu6ZOI9/vrOANRtqGFTWmR8fPZyT9+lPvx6epltSYbCQliQVhIUr1nPfmxXcO6mCDz5ZQ+cOpRy7Rz++st/OlO+yg9ENSQXHQlqSlDdV1TU8NX0R90ys4F/vLaE2wv6Dd+Tfx+7GMXv0o0tH/5uSVLj8DSVJalExRqbNX8k9E+fx4OT5rFi3kX49OvG9w4Zw6r4D2KWsS76HKEnNYiEtSWoRlaureHDyfO6ZOI+ZC1fRoV0JR4/uy5f3HcAhQ3pSWmJ0Q1JxabKQDiHsDNwK9AVqgRtjjL8LIewI3AUMAuYCX4kxLgt1IbbfAccAa4FvxBjfTO3rLODi1K5/EWP8W3bvjiSpkFTX1PLCrCXcPXEez85czMaayJ4DenDlibtzwud2okdnzzYoqXg1Z0a6GvhhjPHNEEI3YFII4SngG8AzMcarQggXAhcCPwG+CAxNfR0A3AAckCq8LwXKgZjaz0MxxmXZvlOSpPyavXgV90ys4P63PmbJqip6du3ANw4exKn77szwvt3yPTxJyoomC+kY4wJgQernVSGEGUB/4EvA2NRqfwOep66Q/hJwa4wxAq+GELYPIfRLrftUjHEpQKoYHw/ckcX7I0nKk5Xr607Xfc/ECibPW067ksBhI3rz5X0HcNiI3rQvLcn3ECUpq9LKSIcQBgF7A68BfVJFNjHGBSGE3qnV+gPz6m1WkVq2reWSpCK1cMV6npqxiKemL+LVOZVsqKlleJ9uXHzsSE7cuz89u3bM9xAlKWeaXUiHELoC9wEXxBhXNtLPs6ErYiPLG7qtc4BzAAYOHNjcIUqScizGyMyFq3hqel3xPOXjFQAMKuvMWQfvwnGf24nPDehhz2dJbUKzCukQQnvqiujbYoz3pxYvCiH0S81G9wMWp5ZXADvX23wAMD+1fOxWy59v6PZijDcCNwKUl5c3WGxLklrGxppa3pi7dHPxXLFsHSHAXjtvz3+OH85Ro/qwW6+uFs+S2pzmdO0IwE3AjBjjNfWuegg4C7gq9f0f9ZafG0K4k7oPG65IFdtPAP8VQtghtd5RwEXZuRuSpGxaXVXNC+8u4anpC3nu3SWsWLeRDu1KOHRIT7532BCOGNmb3t065XuYkpRXzZmRPgT4OjAlhDA5teyn1BXQd4cQ/g34CPhy6rpHqWt9N5u69nffBIgxLg0hXAm8kVrvik0fPJQk5d+mvPPT0xfxSirvvEPn9hw5sg/jRvVhzLCedO7g6QckaZNQ11yjcJWXl8eJEyfmexiS1OrEGHl30SqemraIp2Ys4p2KurzzLmWdGZcqnvfdZQfa2W1DUhsQQpgUYyxPZxunFiSpDamuqeX1VN756RmLmLd0HVCXd/7x0XV55yG9zTtLUnNYSEtSK7cp7/z0jEU8O3PxFnnn735hCEeO7E3v7uadJSldFtKS1AotWrl+c5eNTXnn7Tu354iRvTlqVB8+P7QXXTr6X4AkZcLfopLUSsQYeXzqQv70whzeTuWdB+7Yma8ftAvjRvWh3LyzJGWVhbQktQIzF67k8oem88r7lQzp3ZUfHz2ccaP6MNS8syTljIW0JBWxZWs2cM1Ts7jttQ/pvl17rvzSaM7Yf6Azz5LUAiykJakIVdfUcvvrH3HNU7NYuW4j/+fAXfj+kcPYoUuHfA9NktoMC2lJKjIvz/mEKx6ezsyFqzho1zIuPWEUI/p2z/ewJKnNsZCWpCIxb+la/uvRGTw2dSH9t9+OG762D+N372sGWpLyxEJakgrc2g3V/On5Ofy/F9+nJAR+OG4Y3xqzK53al+Z7aJLUpllIS1KBijHy8DsL+NWjM1iwYj0n7LkTF35xBDttv12+hyZJwkJakgrS1I9XcMXD03l97lJG79Sd35+xN/sN2jHfw5Ik1WMhLUkFpHJ1Fb95chZ3vvERO3TuwK9O3oOvlO9MaYk5aEkqNBbSklQANtbUcusrH3Lt07NYt6GGbx48mPOPHEqP7drne2iSpG2wkJakPHtx1hKueGQ6sxev5vNDe3Lp8aMY0rtbvoclSWpCk4V0COFm4DhgcYxx99Syu4DhqVW2B5bHGPcKIQwCZgDvpq57Ncb4ndQ2+wK3ANsBjwLnxxhj1u6JJBWZDyvXcOUjM3h6xiJ2KevMX84s54iRvW1nJ0lFojkz0rcAfwRu3bQgxnjapp9DCP8DrKi3/pwY414N7OcG4BzgVeoK6fHAY+kPWZKK25qqav743Gxu+tcHtC8N/GT8CM4+dBAd29nOTpKKSZOFdIzxxdRM82eEummTrwCHN7aPEEI/oHuM8ZXU5VuBE7GQltSG1NZGHpz8MVc9NpPFq6o4eZ/+/GT8CPp075TvoUmSEsg0I/15YFGM8b16ywaHEN4CVgIXxxj/BfQHKuqtU5Fa1qAQwjnUzV4zcODADIcoSfn39rzlXPbwNN76aDl7DujBn76+L/sM3CHfw5IkZSDTQvoM4I56lxcAA2OMlalM9IMhhNFAQ4G/beajY4w3AjcClJeXm6OWVLQWr1rPrx9/l3smVdCza0d+fernOGWfAZTYzk6Sil7iQjqE0A44Gdh307IYYxVQlfp5UghhDjCMuhnoAfU2HwDMT3rbklToNlTXcsvLH/D7Z2ZTVV3Dt8fsyrmHD6FbJ9vZSVJrkcmM9JHAzBjj5shGCKEXsDTGWBNC2BUYCrwfY1waQlgVQjgQeA04E/hDJgOXpEL1/LuLueLh6bz/yRqOGNGbnx07kl17dc33sCRJWdac9nd3AGOBniGECuDSGONNwOlsGesAGANcEUKoBmqA78QYl6au+y6ftr97DD9oKKmV+ahyLVf+czpPTV/E4J5d+Os39+Ow4b3zPSxJUo6EQm/lXF5eHidOnJjvYUjSNq3bUMMNz8/mTy++T7uSwHmHD7WdnSQVmRDCpBhjeTrbeGZDSUooxshjUxfyy3/O4OPl6/jSXjtx0RdH0reH7ewkqS2wkJakBN5btIrLHp7GS7MrGdG3G3d/+yD2H7xjvoclSWpBFtKSlIaV6zfyu6ff428vz6Vzh1Ku+NJovrr/QNqVluR7aJKkFmYhLUnNUFsbuf+turMSVq6p4vT9duZHRw2nrGvHfA9NkpQnFtKS1ISpH6/gkn9M5c2PlrP3wO25+RvlfG7A9vkeliQpzyykJWkblq7ZwK+feJc73/iIsi4dPCuhJGkLFtKStJWa2sjtr33Ib56cxeqqas4+ZDDnHzmU7p6VUJJUj4W0JNXzxtylXPKPacxYsJKDdyvjshNGM6xPt3wPS5JUgCykJQlYtHI9v3p0Bg9Ons9OPTpx/df24Yu79yUEYxySpIZZSEtq0zZU13LzSx/wh2feY2Nt5LzDh/DdsbvRuYO/HiVJjfN/Cklt1guzlnD5Q9N4/5M1HDmyNz8/bhS7lHXJ97AkSUXCQlpSmzNv6VqueGQ6T01fxOCeXfjrN/fjsOG98z0sSVKRsZCW1Gas21DDDS/M4U8vzKFdSeA/xw/n3w4dTMd2pfkemiSpCFlIS2r1Yow8MW0hVz4yg4+Xr+OEPXfiomNG0K/HdvkemiSpiJU0tUII4eYQwuIQwtR6yy4LIXwcQpic+jqm3nUXhRBmhxDeDSEcXW/5+NSy2SGEC7N/VyTps2YvXsXXb3qd7/zvm3Tr1I47zzmQ35+xt0W0JCljzZmRvgX4I3DrVst/G2P8Tf0FIYRRwOnAaGAn4OkQwrDU1dcB44AK4I0QwkMxxukZjF2Stmn52g387pn3+PsrH9K5QymXnzCarx0wkHalTc4fSJLULE0W0jHGF0MIg5q5vy8Bd8YYq4APQgizgf1T182OMb4PEEK4M7WuhbSkrNpYU8vtr33Eb5+excp1Gzltv4H86KhhlHXtmO+hSZJamUwy0ueGEM4EJgI/jDEuA/oDr9ZbpyK1DGDeVssPyOC2Jekznnt3Mb94ZDpzlqzhkCFlXHzsKEb2657vYUmSWqmkhfQNwJVATH3/H+BsoKFTgEUazmLHbe08hHAOcA7AwIEDEw5RUlvx3qJV/OKfM3hh1hIG9+zCn88s58iRvT0roSQppxIV0jHGRZt+DiH8GXgkdbEC2LneqgOA+amft7W8of3fCNwIUF5evs2CW1LbtnTNBq59eha3vfYRnTuUcvGxIznzoEF0aGcOWpKUe4kK6RBCvxjjgtTFk4BNHT0eAm4PIVxD3YcNhwKvUzdTPTSEMBj4mLoPJH41k4FLars2VNfy91c/5HdPz2LNhhq+uv9Avj9uGDt26ZDvoUmS2pAmC+kQwh3AWKBnCKECuBQYG0LYi7p4xlzg2wAxxmkhhLup+xBhNfC9GGNNaj/nAk8ApcDNMcZpWb83klq1GCPPzFjMLx+dwQefrOHzQ3vy8+NGMaxPt3wPTZLUBoUYCzs5UV5eHidOnJjvYUjKs5kLV/KLR2YwYfYn7NarCxcfO4qxw3uZg5YkZUUIYVKMsTydbTyzoaSC9snqKq55ahZ3vv4R3Tq157LjR/G1A3ehvf2gJUl5ZiEtqSBVVdfwt5fn8odnZrNuYw1nHTyI848YyvadzUFLkgqDhbSkghJj5Ilpi/jVYzP4sHIth4/ozU+PGcmQ3l3zPTRJkrZgIS2pYEybv4IrH5nOq+8vZVifrtx69v6MGdYr38OSJKlBFtKS8m7xqvVc8+Qs7po4j+23a8+VJ+7OGfvtTDtz0JKkAmYhLSlv1m+s4eaXPuC6Z2ezoaaW/3voYM49fCg9tmuf76FJktQkC2lJLS7GyKNTFvKrx2ZQsWwd40b14afHjGRwzy75HpokSc1mIS2pRU2pWMEVj0zjjbnLGNG3G7f/3wM4eEjPfA9LkqS0WUhLahGLVq7n10+8y31vVlDWpQO/OnkPvlK+M6UlnlBFklScLKQl5dSyNRv460sf8JcJH1BdE/n2mN343mG70a2TOWhJUnGzkJaUE4tXrufP/3qf2177iLUbajhmj75cOH4kA8s653tokiRlhYW0pKyat3Qtf3phDvdMqqCmNnLCnjvx3bG7MaxPt3wPTZKkrLKQlpQVsxev4vrn5/CPyfMpDYFT9h3Ad7+wmzPQkqRWy0JaUkamfryC656bzePTFtKpXSnfOHgQ3/r8rvTt0SnfQ5MkKacspCUl8sbcpfzx2dm8MGsJ3Tq149zDhvCNgwdR1rVjvocmSVKLaLKQDiHcDBwHLI4x7p5a9mvgeGADMAf4ZoxxeQhhEDADeDe1+asxxu+kttkXuAXYDngUOD/GGLN5ZyTlVoyRF9/7hOuenc3rc5dS1qUDPz56OF8/aBe624VDktTGNGdG+hbgj8Ct9ZY9BVwUY6wOIVwNXAT8JHXdnBjjXg3s5wbgHOBV6grp8cBjCcctqQXV1kaenL6Q656bw5SPV9CvRycuPX4Up+83kO06lOZ7eJIk5UWThXSM8cXUTHP9ZU/Wu/gqcGpj+wgh9AO6xxhfSV2+FTiRAi2kH5+6gI7tSzlseO98D0XKq+qaWh5+Zz7XPzeH9xavZpeyzlx18h6cvM8AOrQryffwJEnKq2xkpM8G7qp3eXAI4S1gJXBxjPFfQH+got46FallBSfGyE0TPuCNucs4ZZ8B/Py4kWzfuUO+hyW1qKrqGu6dVMGfXpjDvKXrGN6nG787fS+O3aMf7UotoCVJggwL6RDCz4Bq4LbUogXAwBhjZSoT/WAIYTTQ0DmAt5mPDiGcQ10MhIEDB2YyxLSFEPjf/3sAf3x2Ntc/P4cX31vCL07cnaNH923RcUj5sHZDNbe/9hF//tf7LFpZxZ47b88lx43miBG9KfFU3pIkbSFxIR1COIu6DyEeselDgzHGKqAq9fOkEMIcYBh1M9AD6m0+AJi/rX3HGG8EbgQoLy9v8Q8kdmxXyg+PGs7Ro/vy43vf4dt/n8Rxn+vH5SeMtiOBWqUV6zbyt5fn8teXPmDZ2o0ctGsZ13xlLw7erYwQLKAlSWpIokI6hDCeug8XfiHGuLbe8l7A0hhjTQhhV2Ao8H6McWkIYVUI4UDgNeBM4A+ZDz+3du/fg4fOPYQ/PT+H3z/7Hi/PqeSyE0Zz/Of6WVyoVViyqoqbX/qAv7/yIaurqjl8RG++d9gQ9t1lh3wPTZKkghea6kAXQrgDGAv0BBYBl1LXpaMjUJla7dUY43dCCKcAV1AX96gBLo0xPpzaTzmftr97DDivOe3vysvL48SJE9O+Y9k2a9EqfnzP27xdsYJxo/rwyxN3p3d3Tzih4jR/+TpufPF97nj9IzbU1HLMHv3497G7MXqnHvkemiRJeRFCmBRjLE9rm0Jv5VwohTTUdTC4+aUP+J8nZ9GxXQk/P24Up+47wNlpFY0PPlnDDc/P5oG3PiZGOGnv/nx37G7s2qtrvocmSVJeWUi3kPeXrOYn973DG3OXMWZYL3518h703367fA9L2qaNNbVc99xs/vDsbEpLAqfvtzPnjNmVATt0zvfQJEkqCBbSLai2NvL3Vz/k6sdnEoCLjhnJV/cfaGcDFZzZi1fxg7vf5p2KFZy410789NiR9O5mLEmSpPospPNg3tK1XHj/O7w0u5KDdi3jqlP2YJeyLvkelkRtbeSWl+dy9eMz6dyhlF+etAfH7NEv38OSJKkgWUjnSYyRu96Yxy//OYPq2siPjx7OWQcPotTZaeXJx8vX8aO73+aV9ys5YkRvfnXKHs5CS5LUiCSFdDbObNjmhRA4ff+BfGF4L372wFSueGQ6/5yygKtP+RxDevshLrWcGCP3vfkxlz80jdoYufqUPfhK+c5+IFaSpBzwXL9Z1K/Hdtx0VjnXnrYXc5as5pjf/4sbnp9DdU1tvoemNuCT1VV8+++T+NE9bzOyX3ceO38Mp+030CJakqQccUY6y0IInLh3fw4Z0pNL/jGVqx+fyaNTFvDrL3+OEX2753t4aqWemLaQn94/hVXrq/npMSP4t0N3NVokSVKOOSOdI726deSG/7Mv139tHxasWMfxf5jAtU/PYkO1s9PKnpXrN/LDu9/m23+fRJ/unXj4vEM5Z8xuFtGSJLUAZ6Rz7Jg9+nHgrmVc8fA0rn36PR6fupBfn7onewzwDHLKzMtzPuHH97zDghXrOO/wIZx3+FA6tPNvY0mSWor/67aAHbt04NrT9+YvZ5azbO0GTrz+Ja5+fCbrN9bke2gqQus31nDFw9P56p9fo0O7Eu797sH88KjhFtGSJLUwZ6Rb0JGj+rDf4B35r3/O4Ibn5/DEtIX8+tTPse8uO+Z7aCoS71Qs5/t3TWbOkjWcedAuXPjFEXTu4GEsSVI+OIXVwnps156rT/0cf/+3/anaWMupf3qFKx6eztoN1fkemgrYxppafvvULE66/mXWVNXw93/bnyu+tLtFtCRJeeT/wnny+aG9eOL7Y/jvx2dy80sf8PSMRVx1yh4cvFvPfA9NBWb24lV8/663mfLxCk7auz+XHT+aHp3b53tYkiS1ec5I51HXju244ku7c9c5B1IS4Kt/fo2LH5zCRvtOi7pTfN884QOO/f0EKpat5fqv7cNvT9vLIlqSpALhjHQBOGDXMh47fwz/8+S7/GXCB5R16cj3xw3L97CURxXL1vLje97xFN+SJBWwZs1IhxBuDiEsDiFMrbdsxxDCUyGE91Lfd0gtDyGE34cQZocQ3gkh7FNvm7NS678XQjgr+3eneG3XoZSLjxvFSXv357rnZjNt/op8D0l5EGPknonzGH/tv3inYjlXn7IHfzmr3CJakqQC1Nxoxy3A+K2WXQg8E2McCjyTugzwRWBo6usc4AaoK7yBS4EDgP2BSzcV3/rUpcePYocuHfjh3W978pY2ZtMpvn987zuM6tedxy/wFN+SJBWyZhXSMcYXgaVbLf4S8LfUz38DTqy3/NZY51Vg+xBCP+Bo4KkY49IY4zLgKT5bnLd523fuwK9O2oOZC1fxx+dm53s4aiFPTFvI0b99keffXcLPjhnJHeccyM47ds73sCRJUiMyyUj3iTEuAIgxLggh9E4t7w/Mq7deRWrZtpZ/RgjhHOpmsxk4cGAGQyxOR47qw8mpiMdRo/qwe3/PgtharVy/kcsfms59b1Yweqfu3P6tvRjet1u+hyVJkpohF107GnofOjay/LMLY7wxxlgeYyzv1atXVgdXLC49fjRlXTrwo3uMeLRWL8/5hC9e+y8eeKuC8w4fwgP/fohFtCRJRSSTQnpRKrJB6vvi1PIKYOd66w0A5jeyXA3o0bk9vzq5LuLxh2ffy/dwlEXrN9Zw+cPTPMNlHD0AABF5SURBVMW3JElFLpP/uR8CNnXeOAv4R73lZ6a6dxwIrEhFQJ4Ajgoh7JD6kOFRqWXahiNG9uGUfQZw/fNzmFJhF4/WYN7StZx8/cv89aW5nHXQLjz6H59nn4F+5laSpGLU3PZ3dwCvAMNDCBUhhH8DrgLGhRDeA8alLgM8CrzP/2/v3qOkKM88jn+fuXEXud9HQFFEREJaGC6auCYKKBLFC7hGJGbRNRg9JxrdXDxustlE1NzEkIWIGGOImsQNZjWCRqNgEAcEvDEyMCAIMigKAuLcnv2ja5LO2D3SM5Op7q7f55w6XVP11swzz3m7+jnVT1dDObAQuAbA3fcC3wVeDJbvBNukEbdMGUb3jvEWj49qasMOR5rhuU17mDJvBdvfO8SiK2L859ThtCvKDzssERERaSJzT9qmnDFisZiXlpaGHUaont5YyazFLzLnjOO44ewTwg5H0uTuLHh2C7f9aSPH9ezIgi/GGNi9Q9hhiYiISAIzW+PusXSOUVNmFjhjaE8u/HR/5v9lMxt2vB92OJKGQ1U1zFnyEt9/fCMTh/fmkWvGq4gWERHJESqks8S3zx1Gj45t1OKRRba9e5ALfvY8j7+8i5snDeXuS0fRoU1z7jgpIiIimUSFdJbo3K6Q7087mTd2H+CnT+kuHpnumbJKpty1gl37DrN41miu/syx+oZCERGRHKNCOouccUJPLvp0f37+ly1q8chQ7s7dT5cza/GL9D26HY/OmcDpx0fzXugiIiK5ToV0lvlW0OLxtYfU4pFpDnxUw7//ai23P1HGlBF9+f014yjupq/5FhERyVUqpLNMfYvHpsoD/PhJtXhkii17DnD+3StZ9trbfOucE/nJ9JG0L1I/tIiISC5TIZ2FzjihJxfH+vM/f9nMuu1q8QjbU6/vZuq8lbx7sIpfXTmGL582WP3QIiIiEaBCOkt969xh9DqqLTc8vJ7D1WrxCENdnfOTJzdx5X2lHNO9PUvnjGfccd3DDktERERaiQrpLHVU20J+MG0E5WrxCMX+w9XMvn8NP3ryDS4Y1Y/fXj2O/l3UDy0iIhIlauLMYp85vgfTTx3Agmc3c/ZJvfhUcZewQ4qE8soPmH3/Gra9e4hbpwxj5riBauUQERGJIF2RznLfPOdEeqvFo9U88erbTJ23kv0fVvPAl8dwxfhBKqJFREQiSoV0lusUtHhs3nOQHz35Rtjh5Ky6OufOZWVcdf8ajuvZkUevnUDJ4G5hhyUiIiIhUiGdA04/vgczRg9g4bNbWPvme2GHk3P2fVjNlfe9yF1/LufiWH8evGosfTq3CzssERERCZkK6Rzxjckn0qdzO25Ui0eLKnv7A6bOW8GK8nf4ry8M57ZpI2hbmB92WCIiIpIBmlxIm9kJZrYuYdlvZteb2a1m9lbC9skJx/yHmZWbWZmZnd0y/4JAfYvHyfEWj+Vq8WgJj728i/N/tpKDVbUs+bcSLis5Rv3QIiIi8jdNvmuHu5cBIwHMLB94C3gEmAX8yN3vSBxvZsOA6cBJQF/gSTM73t11+bSFnDakBzNGF7PwuS2cPbw3o3QXjyaprXPuWFbG/Gc2M6r4aOZf9ml6HdU27LBEREQkw7RUa8eZwGZ339bImKnAb9z9I3evAMqB0S309yXwjclD6dO5ne7i0UTvH6riintXM/+ZzVw6ppgls0tURIuIiEhSLVVITweWJPw8x8w2mNkiM6u/LNoP2J4wZkewTVpQp7aF3DZtBFv2HOTOZWVhh5NVXtu5nynzVvDClr384IKT+e/zT6ZNgfqhRUREJLlmF9JmVgScBzwcbJoPHEu87WMXcGf90CSHe4rfOdvMSs2sdM+ePc0NMXImDOnOpWOK+cWKCtZs2xt2OFlh6fqdXDB/JVU1dTx4VQnTRxeHHZKIiIhkuJa4Ij0JWOvuuwHcfbe717p7HbCQv7dv7AAGJBzXH9iZ7Be6+wJ3j7l7rEePHi0QYvR8Y/KJ9O3cjhsf3qAWj0bU1Nbxvf97ja8ueYmT+3Xm0Wsn6BsiRURE5Ii0RCE9g4S2DjPrk7DvfOCVYH0pMN3M2pjZIGAIsLoF/r4k0bFNAbdfOIIt7xzkjifU4pHM3oNVXL5oNQufq2Dm2GN44Msl9OykfmgRERE5Mk2+aweAmbUHPg9clbB5rpmNJN62sbV+n7u/amYPAa8BNcBXdMeOf65xx3XnspJi7llZwcThvYkN7Bp2SBnh/UNVLFm9nXtXVvD+h9XcfuEILooN+OQDRURERBKYe9I25YwRi8W8tLQ07DCy1sGPajj7x89SmJ/HY189jXZF0f3wXHnlAe5dWcHv1u7gcHUdE47rzk0Th3Jy/85hhyYiIiIhM7M17h5L55hmXZGWzNehTQFzLxzBpQtf4I5lZXz73GFhh9Sq3J3nNr3DopUVPFO2h6KCPM4f2Y9ZEwYytPdRYYcnIiIiWUyFdASMO7Y7Xyw5hkVBi8epEWjxOFxdyyMvvcWiFRVsqjxAj05t+Nrnj+fSMcV069gm7PBEREQkB6iQjoibJw3lmTcqufHh9Tx+3ek52+Lx9r7D3L9qK79+4U3eO1TNSX2P4ocXn8I5I/rontAiIiLSolRIR0SHNgXMnXYKMxau4vYnyrhlSm61eGzY8T6LVlTwxw27qHXnrGG9uHLCYE4d2AWzZLcwFxEREWkeFdIRMvbYblw+9hjufT7e4jF6UHa3eNTU1rH8td3cs6KC0m3v0bFNATPHDWTm2IEUd2sfdngiIiKS41RIR8xNE4fydFklN/52PY9fdxrti7JvCuz7sJqHXtzO4ue38tb7H1LctT23nDuMi2L96dS2MOzwREREJCKyr4qSZunQpoDbLzyF6QtWMfdPZdx63klhh3TEKt45yOKVFTy8ZgeHqmoZM6grt0wZxudO7EV+nto3REREpHWpkI6gksHduGLcQBY/v5VJw3szZnC3sENKyd356+Z3WbSygqc2VlKQZ5x3Sj9mjR/I8H66/7OIiIiER4V0RH194glBi8cG/nR95rV4HK6uZen6nSxaUcHGtz+gW4cirv2XIVxWUqyv8RYREZGMkFnVk7Sa9kUFzJ02gksyrMWj8oPD/GrVmzywahvvHqxiaO9OzJ02gvNG9qVtoW5fJyIiIplDhXSEjUlo8Zg4vDclIbZ4vLpzH4tWbOXR9TuprqvjzKE9+dL4QYw9tptuXyciIiIZSYV0xNW3eHw9ocXD3ampc2pqnaraOmpq66iudapr64Ll7+s1dU51TR3VwWNNXR1Vtf+4XtPguJrgsSpYL9v9Aasr9tK+KJ8ZowdwxfhBDOreIezUiIiIiDTK3D3sGBoVi8W8tLQ07DBy2uqKvVyy4K8U5ufh7lTX/nPnRH6eUZBnFOXnUViQR5f2hVxy6gAuiRXTub1uXyciIiKtz8zWuHssnWN0RVoYPagr82aM4qU336MgP4+ifKMgP4/C/DwK8y14zKMgP178FgTbEtcTx9WvFwTrRQnrhXl55OlWdSIiIpIDVEgLAOeM6MM5I/qEHYaIiIhI1shr7i8ws61m9rKZrTOz0mBbVzNbbmabgscuwXYzs5+aWbmZbTCzUc39+yIiIiIiYWh2IR04w91HJvSV3Aw85e5DgKeCnwEmAUOCZTYwv4X+voiIiIhIq2qpQrqhqcB9wfp9wBcStv/S41YBR5uZ+glEREREJOu0RCHtwDIzW2Nms4Ntvdx9F0Dw2DPY3g/YnnDsjmCbiIiIiEhWaYkPG453951m1hNYbmYbGxmb7HYNH7vXWlCQzwYoLi5ugRBFRERERFpWs69Iu/vO4LESeAQYDeyub9kIHiuD4TuAAQmH9wd2JvmdC9w95u6xHj16NDdEEREREZEW16xC2sw6mFmn+nXgLOAVYCkwMxg2E/hDsL4UuDy4e0cJsK++BUREREREJJs065sNzWww8avQEG8T+bW7f8/MugEPAcXAm8BF7r7XzAyYB0wEDgGz3L3Rry00sz3AtiYH2XTdgXdC+LvZSvlKj/KVHuUrPcpXepSv9Cln6VG+0hNWvo5x97RaITL+K8LDYmal6X5NZJQpX+lRvtKjfKVH+UqP8pU+5Sw9yld6silf/6zb34mIiIiI5DQV0iIiIiIiTaBCOrUFYQeQZZSv9Chf6VG+0qN8pUf5Sp9ylh7lKz1Zky/1SIuIiIiINIGuSIuIiIiINEHkC2kzm2hmZWZWbmY3J9nfxsweDPa/YGYDWz/KzGBmA8zsaTN73cxeNbPrkoz5rJntM7N1wXJLGLFmCjPbamYvB7n42K0eg3uq/zSYXxvMbFQYcWYCMzshYd6sM7P9ZnZ9gzGRnl9mtsjMKs3slYRtXc1suZltCh67pDh2ZjBmk5nNTDYm16TI1+1mtjF4vj1iZkenOLbR526uSpGzW83srYTn3eQUxzb6epqLUuTrwYRcbTWzdSmOjdQcS1VDZP05zN0juwD5wGZgMFAErAeGNRhzDfDzYH068GDYcYeYrz7AqGC9E/BGknx9Fvhj2LFmygJsBbo3sn8y8DhgQAnwQtgxZ8ISPDffJn5Pz8TtkZ5fwOnAKOCVhG1zgZuD9ZuB25Ic1xXYEjx2Cda7hP3/hJSvs4CCYP22ZPkK9jX63M3VJUXObgVu+ITjPvH1NBeXZPlqsP9O4JYU+yI1x1LVENl+Dov6FenRQLm7b3H3KuA3wNQGY6YC9wXrvwXODL5YJnLcfZe7rw3WPwBeB/qFG1XWmwr80uNWAUebWZ+wg8oAZwKb3T2ML2PKWO7+LLC3webEc9R9wBeSHHo2sNzd97r7e8By4l+MldOS5cvdl7l7TfDjKqB/qweWwVLMsSNxJK+nOaexfAW1wsXAklYNKkM1UkNk9Tks6oV0P2B7ws87+Hhh+Lcxwcl3H9CtVaLLYEGLy6eAF5LsHmtm683scTM7qVUDyzwOLDOzNWY2O8n+I5mDUTSd1C8+ml//qJe774L4CxXQM8kYzbPkvkT8HaFkPum5GzVzgnaYRSneetcc+7jTgN3uvinF/sjOsQY1RFafw6JeSCe7stzwNiZHMiZSzKwj8Dvgenff32D3WuJvx58C3AX8b2vHl2HGu/soYBLwFTM7vcF+za8GzKwIOA94OMluza+m0TxrwMy+CdQAD6QY8knP3SiZDxwLjAR2EW9XaEhz7ONm0PjV6EjOsU+oIVIelmRbRsyvqBfSO4ABCT/3B3amGmNmBUBnmva2V04ws0LiT4AH3P33Dfe7+353PxCsPwYUmln3Vg4zY7j7zuCxEniE+NufiY5kDkbNJGCtu+9uuEPzK6nd9e1AwWNlkjGaZwmCDyqdC/yrBw2YDR3Bczcy3H23u9e6ex2wkOS50BxLENQLFwAPphoTxTmWoobI6nNY1AvpF4EhZjYouAo2HVjaYMxSoP7ToRcCf0514s11Qb/XPcDr7v7DFGN61/eQm9lo4nPs3daLMnOYWQcz61S/TvxDTq80GLYUuNziSoB99W9xRVjKqziaX0klnqNmAn9IMuYJ4Cwz6xK8LX9WsC1yzGwicBNwnrsfSjHmSJ67kdHgcxvnkzwXR/J6GiWfAza6+45kO6M4xxqpIbL7HBb2px3DXojfNeEN4p82/maw7TvET7IAbYm/xVwOrAYGhx1ziLmaQPytlA3AumCZDFwNXB2MmQO8SvwT26uAcWHHHWK+Bgd5WB/kpH5+JebLgLuD+fcyEAs77pBz1p54Ydw5YZvm199zsYT4W+vVxK/QXEn8MxtPAZuCx67B2Bjwi4RjvxScx8qBWWH/LyHmq5x4r2X9Oaz+rkx9gceC9aTP3SgsKXJ2f3B+2kC86OnTMGfBzx97Pc31JVm+gu2L689bCWMjPccaqSGy+hymbzYUEREREWmCqLd2iIiIiIg0iQppEREREZEmUCEtIiIiItIEKqRFRERERJpAhbSIiIiISBOokBYRERERaQIV0iIiIiIiTaBCWkRERESkCf4fmWiLNPVe5dQAAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "m = PdGrid(50, 50, \"Random\", seed=seed)\n", + "run_model(m)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "## Simultaneous Activation" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "m = PdGrid(50, 50, \"Simultaneous\", seed=seed)\n", + "run_model(m)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:mesa]", + "language": "python", + "name": "conda-env-mesa-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.6" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/examples/advanced/pd_grid/pd_grid/__init__.py b/examples/advanced/pd_grid/pd_grid/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/advanced/pd_grid/pd_grid/agent.py b/examples/advanced/pd_grid/pd_grid/agent.py new file mode 100644 index 00000000000..57e247240ea --- /dev/null +++ b/examples/advanced/pd_grid/pd_grid/agent.py @@ -0,0 +1,49 @@ +import mesa + + +class PDAgent(mesa.Agent): + """Agent member of the iterated, spatial prisoner's dilemma model.""" + + def __init__(self, pos, model, starting_move=None): + """ + Create a new Prisoner's Dilemma agent. + + Args: + pos: (x, y) tuple of the agent's position. + model: model instance + starting_move: If provided, determines the agent's initial state: + C(ooperating) or D(efecting). Otherwise, random. + """ + super().__init__(pos, model) + self.pos = pos + self.score = 0 + if starting_move: + self.move = starting_move + else: + self.move = self.random.choice(["C", "D"]) + self.next_move = None + + @property + def isCooroperating(self): + return self.move == "C" + + def step(self): + """Get the best neighbor's move, and change own move accordingly if better than own score.""" + neighbors = self.model.grid.get_neighbors(self.pos, True, include_center=True) + best_neighbor = max(neighbors, key=lambda a: a.score) + self.next_move = best_neighbor.move + + if self.model.schedule_type != "Simultaneous": + self.advance() + + def advance(self): + self.move = self.next_move + self.score += self.increment_score() + + def increment_score(self): + neighbors = self.model.grid.get_neighbors(self.pos, True) + if self.model.schedule_type == "Simultaneous": + moves = [neighbor.next_move for neighbor in neighbors] + else: + moves = [neighbor.move for neighbor in neighbors] + return sum(self.model.payoff[(self.move, move)] for move in moves) diff --git a/examples/advanced/pd_grid/pd_grid/model.py b/examples/advanced/pd_grid/pd_grid/model.py new file mode 100644 index 00000000000..d2445c88d61 --- /dev/null +++ b/examples/advanced/pd_grid/pd_grid/model.py @@ -0,0 +1,62 @@ +import mesa + +from .agent import PDAgent + + +class PdGrid(mesa.Model): + """Model class for iterated, spatial prisoner's dilemma model.""" + + schedule_types = { + "Sequential": mesa.time.BaseScheduler, + "Random": mesa.time.RandomActivation, + "Simultaneous": mesa.time.SimultaneousActivation, + } + + # This dictionary holds the payoff for this agent, + # keyed on: (my_move, other_move) + + payoff = {("C", "C"): 1, ("C", "D"): 0, ("D", "C"): 1.6, ("D", "D"): 0} + + def __init__( + self, width=50, height=50, schedule_type="Random", payoffs=None, seed=None + ): + """ + Create a new Spatial Prisoners' Dilemma Model. + + Args: + width, height: Grid size. There will be one agent per grid cell. + schedule_type: Can be "Sequential", "Random", or "Simultaneous". + Determines the agent activation regime. + payoffs: (optional) Dictionary of (move, neighbor_move) payoffs. + """ + self.grid = mesa.space.SingleGrid(width, height, torus=True) + self.schedule_type = schedule_type + self.schedule = self.schedule_types[self.schedule_type](self) + + # Create agents + for x in range(width): + for y in range(height): + agent = PDAgent((x, y), self) + self.grid.place_agent(agent, (x, y)) + self.schedule.add(agent) + + self.datacollector = mesa.DataCollector( + { + "Cooperating_Agents": lambda m: len( + [a for a in m.schedule.agents if a.move == "C"] + ) + } + ) + + self.running = True + self.datacollector.collect(self) + + def step(self): + self.schedule.step() + # collect data + self.datacollector.collect(self) + + def run(self, n): + """Run the model for n steps.""" + for _ in range(n): + self.step() diff --git a/examples/advanced/pd_grid/pd_grid/portrayal.py b/examples/advanced/pd_grid/pd_grid/portrayal.py new file mode 100644 index 00000000000..a7df44a439f --- /dev/null +++ b/examples/advanced/pd_grid/pd_grid/portrayal.py @@ -0,0 +1,19 @@ +def portrayPDAgent(agent): + """ + This function is registered with the visualization server to be called + each tick to indicate how to draw the agent in its current state. + :param agent: the agent in the simulation + :return: the portrayal dictionary + """ + if agent is None: + raise AssertionError + return { + "Shape": "rect", + "w": 1, + "h": 1, + "Filled": "true", + "Layer": 0, + "x": agent.pos[0], + "y": agent.pos[1], + "Color": "blue" if agent.isCooroperating else "red", + } diff --git a/examples/advanced/pd_grid/pd_grid/server.py b/examples/advanced/pd_grid/pd_grid/server.py new file mode 100644 index 00000000000..50095311ac5 --- /dev/null +++ b/examples/advanced/pd_grid/pd_grid/server.py @@ -0,0 +1,22 @@ +import mesa + +from .portrayal import portrayPDAgent +from .model import PdGrid + + +# Make a world that is 50x50, on a 500x500 display. +canvas_element = mesa.visualization.CanvasGrid(portrayPDAgent, 50, 50, 500, 500) + +model_params = { + "height": 50, + "width": 50, + "schedule_type": mesa.visualization.Choice( + "Scheduler type", + value="Random", + choices=list(PdGrid.schedule_types.keys()), + ), +} + +server = mesa.visualization.ModularServer( + PdGrid, [canvas_element], "Prisoner's Dilemma", model_params +) diff --git a/examples/advanced/pd_grid/readme.md b/examples/advanced/pd_grid/readme.md new file mode 100644 index 00000000000..8b4bc40c88f --- /dev/null +++ b/examples/advanced/pd_grid/readme.md @@ -0,0 +1,42 @@ +# Demographic Prisoner's Dilemma on a Grid + +## Summary + +The Demographic Prisoner's Dilemma is a family of variants on the classic two-player [Prisoner's Dilemma]. The model consists of agents, each with a strategy of either Cooperate or Defect. Each agent's payoff is based on its strategy and the strategies of its spatial neighbors. After each step of the model, the agents adopt the strategy of their neighbor with the highest total score. + +The model payoff table is: + +| | Cooperate | Defect| +|:-------------:|:---------:|:-----:| +| **Cooperate** | 1, 1 | 0, D | +| **Defect** | D, 0 | 0, 0 | + +Where *D* is the defection bonus, generally set higher than 1. In these runs, the defection bonus is set to $D=1.6$. + +The Demographic Prisoner's Dilemma demonstrates how simple rules can lead to the emergence of widespread cooperation, despite the Defection strategy dominating each individual interaction game. However, it is also interesting for another reason: it is known to be sensitive to the activation regime employed in it. + +## How to Run + +##### Web based model simulation + +To run the model interactively, run ``mesa runserver`` in this directory. + +##### Jupyter Notebook + +Launch the ``Demographic Prisoner's Dilemma Activation Schedule.ipynb`` notebook and run the code. + +## Files + +* ``run.py`` is the entry point for the font-end simulations. +* ``pd_grid/``: contains the model and agent classes; the model takes a ``schedule_type`` string as an argument, which determines what schedule type the model uses: Sequential, Random or Simultaneous. +* ``Demographic Prisoner's Dilemma Activation Schedule.ipynb``: Jupyter Notebook for running the scheduling experiment. This runs the model three times, one for each activation type, and demonstrates how the activation regime drives the model to different outcomes. + +## Further Reading + +This model is adapted from: + +Wilensky, U. (2002). NetLogo PD Basic Evolutionary model. http://ccl.northwestern.edu/netlogo/models/PDBasicEvolutionary. Center for Connected Learning and Computer-Based Modeling, Northwestern University, Evanston, IL. + +The Demographic Prisoner's Dilemma originates from: + +[Epstein, J. Zones of Cooperation in Demographic Prisoner's Dilemma. 1998.](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.8.8629&rep=rep1&type=pdf) diff --git a/examples/advanced/pd_grid/requirements.txt b/examples/advanced/pd_grid/requirements.txt new file mode 100644 index 00000000000..19b805acb1f --- /dev/null +++ b/examples/advanced/pd_grid/requirements.txt @@ -0,0 +1,3 @@ +jupyter +matplotlib +mesa~=1.1 diff --git a/examples/advanced/pd_grid/run.py b/examples/advanced/pd_grid/run.py new file mode 100644 index 00000000000..ec7d04bebfa --- /dev/null +++ b/examples/advanced/pd_grid/run.py @@ -0,0 +1,3 @@ +from pd_grid.server import server + +server.launch() From a95df266f9b923cb702b358e83ad25d01298d98f Mon Sep 17 00:00:00 2001 From: Houssam Kherraz Date: Mon, 24 Apr 2023 14:53:02 -0400 Subject: [PATCH 009/116] Update outdated readme to be more accurate + clean up requirements.txt of unnecessary dependencies --- .../basic/boltzmann_wealth_model/Readme.md | 22 ++++++++++++------- .../boltzmann_wealth_model/requirements.txt | 3 --- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/examples/basic/boltzmann_wealth_model/Readme.md b/examples/basic/boltzmann_wealth_model/Readme.md index 785a0946a24..84b6b28ad1f 100644 --- a/examples/basic/boltzmann_wealth_model/Readme.md +++ b/examples/basic/boltzmann_wealth_model/Readme.md @@ -2,18 +2,26 @@ ## Summary -A simple model of agents exchanging wealth. All agents start with the same amount of money. Every step, each agent with one unit of money or more gives one unit of wealth to another random agent. This is the model described in the [Intro Tutorial](https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html). +A simple model of agents exchanging wealth. All agents start with the same amount of money. Every step, each agent with one unit of money or more gives one unit of wealth to another random agent. This is the model described in the [Intro Tutorial](https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html), with the completed code. + +If you want to go over the step-by-step tutorial, please go and run the [Jupyter Notebook](https://github.com/projectmesa/mesa/blob/main/docs/tutorials/intro_tutorial.ipynb). The code here runs the finalized code in the last cells directly. As the model runs, the distribution of wealth among agents goes from being perfectly uniform (all agents have the same starting wealth), to highly skewed -- a small number have high wealth, more have none at all. ## How to Run -To follow the tutorial examples, launch the Jupyter Notebook and run the code in ``Introduction to Mesa Tutorial Code.ipynb``. +To follow the tutorial example, launch the Jupyter Notebook and run the code in ``Introduction to Mesa Tutorial Code.ipynb`` which you can find in the main mesa repo [here](https://github.com/projectmesa/mesa/blob/main/docs/tutorials/intro_tutorial.ipynb) To launch the interactive server, as described in the [last section of the tutorial](https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html#adding-visualization), run: ``` - $ python viz_money_model.py + $ python server.py +``` + +Make sure to install the requirements first: + +``` + pip install -r requirements.txt ``` If your browser doesn't open automatically, point it to [http://127.0.0.1:8521/](http://127.0.0.1:8521/). When the visualization loads, press Reset, then Run. @@ -21,9 +29,9 @@ If your browser doesn't open automatically, point it to [http://127.0.0.1:8521/] ## Files -* ``Introduction to Mesa Tutorial Code.ipynb``: Jupyter Notebook with all the steps as described in the tutorial. -* ``money_model.py``: Final version of the model. -* ``viz_money_model.py``: Creates and launches interactive visualization. +* ``boltzmann_wealth_model/model.py``: Final version of the model. +* ``boltzmann_wealth_model/server.py``: Code for the interactive visualization. +* ``run.py``: Launches the server. ## Further Reading @@ -35,5 +43,3 @@ This model is drawn from econophysics and presents a statistical mechanics appro [Milakovic, M. A Statistical Equilibrium Model of Wealth Distribution. February, 2001.](https://editorialexpress.com/cgi-bin/conference/download.cgi?db_name=SCE2001&paper_id=214) [Dragulescu, A and Yakovenko, V. Statistical Mechanics of Money, Income, and Wealth: A Short Survey. November, 2002](http://arxiv.org/pdf/cond-mat/0211175v1.pdf) -____ -You will need to open the file as a Jupyter (aka iPython) notebook with an iPython 3 kernel. Required dependencies are listed in the provided `requirements.txt` file which can be installed by running `pip install -r requirements.txt` diff --git a/examples/basic/boltzmann_wealth_model/requirements.txt b/examples/basic/boltzmann_wealth_model/requirements.txt index b93c188674f..63b0d24e76d 100644 --- a/examples/basic/boltzmann_wealth_model/requirements.txt +++ b/examples/basic/boltzmann_wealth_model/requirements.txt @@ -1,4 +1 @@ -jupyter -matplotlib mesa~=1.1 -numpy From 8621d5919e74383242c94691104e35db52e3a52c Mon Sep 17 00:00:00 2001 From: Catherine Devlin Date: Tue, 25 Apr 2023 12:05:47 -0600 Subject: [PATCH 010/116] Use pre-commit (#27) * Use Pathlib Code by Phil Robare (versilimidude2) * Use pre-commit * Dropped unused imports * used pre-commit to run pyupgrade, trim whitespace * Remove pathlib changes These belong in a separate PR. * remove redundant black, move comment as per rht's suggestions --------- Co-authored-by: Catherine Devlin --- .../epstein_civil_violence/model.py | 2 +- examples/advanced/pd_grid/pd_grid/agent.py | 4 +++- examples/advanced/sugarscape_g1mt/Readme.md | 24 +++++++++---------- examples/advanced/sugarscape_g1mt/run.py | 6 ++--- .../sugarscape_g1mt/sugarscape_g1mt/model.py | 4 +--- .../sugarscape_g1mt/sugarscape_g1mt/server.py | 2 +- .../sugarscape_g1mt/trader_agents.py | 3 ++- .../advanced/wolf_sheep/wolf_sheep/model.py | 2 -- .../wolf_sheep/wolf_sheep/scheduler.py | 3 ++- 9 files changed, 25 insertions(+), 25 deletions(-) diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py index 760767c26d9..9f5bf4750db 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py @@ -79,7 +79,7 @@ def __init__( unique_id = 0 if self.cop_density + self.citizen_density > 1: raise ValueError("Cop density + citizen density must be less than 1") - for (contents, x, y) in self.grid.coord_iter(): + for contents, x, y in self.grid.coord_iter(): if self.random.random() < self.cop_density: cop = Cop(unique_id, self, (x, y), vision=self.cop_vision) unique_id += 1 diff --git a/examples/advanced/pd_grid/pd_grid/agent.py b/examples/advanced/pd_grid/pd_grid/agent.py index 57e247240ea..e289169f482 100644 --- a/examples/advanced/pd_grid/pd_grid/agent.py +++ b/examples/advanced/pd_grid/pd_grid/agent.py @@ -28,7 +28,9 @@ def isCooroperating(self): return self.move == "C" def step(self): - """Get the best neighbor's move, and change own move accordingly if better than own score.""" + """Get the best neighbor's move, and change own move accordingly + if better than own score.""" + neighbors = self.model.grid.get_neighbors(self.pos, True, include_center=True) best_neighbor = max(neighbors, key=lambda a: a.score) self.next_move = best_neighbor.move diff --git a/examples/advanced/sugarscape_g1mt/Readme.md b/examples/advanced/sugarscape_g1mt/Readme.md index 36f1f5332df..23013b52d5f 100644 --- a/examples/advanced/sugarscape_g1mt/Readme.md +++ b/examples/advanced/sugarscape_g1mt/Readme.md @@ -5,19 +5,19 @@ This is Epstein & Axtell's Sugarscape model with Traders, a detailed description is in Chapter four of *Growing Artificial Societies: Social Science from the Bottom Up.* (1996) -This code generally matches the code in the Complexity Explorer Tutorial, but in `.py` instead of `.ipynb` format. +This code generally matches the code in the Complexity Explorer Tutorial, but in `.py` instead of `.ipynb` format. -### Agents: +### Agents: - **Sugar**: Sugar agents grow back at one unit per time step and can be harvested and traded by the trader agents. Sugar is unequally distributed across the landscape with sugar hills in the upper left and lower right of the space. (green if you do the interactive run) - **Spice**: Spice agents grow back at one unit per time step and can be harvested and traded by the trader agents. Spice -is unequally distributed across the landscape with spice hills in the upper right and lower left of the space. +is unequally distributed across the landscape with spice hills in the upper right and lower left of the space. (yellow if you do the interactive run) - **Traders**: Trader agents have the following attributes: (1) metabolism for sugar, (2) metabolism for spice, (3) vision, - (4) initial sugar endowment and (5) initial spice endowment. The traverse the landscape harvesting sugar and spice and -trading with other agents. If they run out of sugar or spice then they are removed from the model. + (4) initial sugar endowment and (5) initial spice endowment. The traverse the landscape harvesting sugar and spice and +trading with other agents. If they run out of sugar or spice then they are removed from the model. The trader agents traverse the landscape according to rule **M**: - Look out as far as vision permits in the four principal lattice directions and identify the unoccupied site(s). @@ -26,15 +26,15 @@ The trader agents traverse the landscape according to rule **M**: - Collect all the resources (sugar and spice) at that location (Epstein and Axtell, 1996, p. 99) -The traders trade according to rule **T**: +The traders trade according to rule **T**: - Agents and potential trade partner compute their marginal rates of substitution (MRS), if they are equal *end*. -- Exchange resources, with spice flowing from the agent with the higher MRS to the agent with the lower MRS and sugar +- Exchange resources, with spice flowing from the agent with the higher MRS to the agent with the lower MRS and sugar flowing the opposite direction. - The price (p) is calculated by taking the geometric mean of the agents' MRS. - If p > 1 then p units of spice are traded for 1 unit of sugar; if p < 1 then 1/p units of sugar for 1 unit of spice -- The trade occurs if it will (a) make both agent better off (increases MRS) and (b) does not cause the agents' MRS to +- The trade occurs if it will (a) make both agent better off (increases MRS) and (b) does not cause the agents' MRS to cross over one another otherwise *end*. -- This process then repeats until an *end* condition is met. +- This process then repeats until an *end* condition is met. (Epstein and Axtell, 1996, p. 105) The model demonstrates several Mesa concepts and features: @@ -54,13 +54,13 @@ To install the dependencies use pip and the requirements.txt in this directory. ## How to Run -To run the model a single instance of the model: +To run the model a single instance of the model: ``` $ python run.py -s ``` -To run the model with BatchRunner: +To run the model with BatchRunner: ``` $ python run.py -b @@ -85,5 +85,5 @@ Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and p ## Additional Resources -- [Growing Artificial Societies](https://mitpress.mit.edu/9780262550253/growing-artificial-societies/) +- [Growing Artificial Societies](https://mitpress.mit.edu/9780262550253/growing-artificial-societies/) - [Complexity Explorer Sugarscape with Traders Tutorial](https://www.complexityexplorer.org/courses/172-agent-based-models-with-python-an-introduction-to-mesa) diff --git a/examples/advanced/sugarscape_g1mt/run.py b/examples/advanced/sugarscape_g1mt/run.py index a5757d64a9b..114e3cf2e99 100644 --- a/examples/advanced/sugarscape_g1mt/run.py +++ b/examples/advanced/sugarscape_g1mt/run.py @@ -44,9 +44,9 @@ def assess_results(results, single_agent): G.add_edge(row["AgentID"], agent) # Get Basic Network Statistics - print("Node Connectivity {}".format(nx.node_connectivity(G))) - print("Average Clustering {}".format(nx.average_clustering(G))) - print("Global Efficiency {}".format(nx.global_efficiency(G))) + 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) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index 57f1599b338..1e3dbb60786 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -1,11 +1,11 @@ import numpy as np -import math import mesa from .trader_agents import Trader from .resource_agents import Sugar, Spice + # Helper Functions def flatten(list_of_lists): """ @@ -51,7 +51,6 @@ def __init__( vision_min=1, vision_max=5, ): - # Initiate width and heigh of sugarscape self.width = width self.height = height @@ -190,6 +189,5 @@ def step(self): self.datacollector.collect(self) def run_model(self, step_count=1000): - for i in range(step_count): self.step() diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py index a0e1a2f1a05..b5a8c32faaa 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py @@ -19,7 +19,7 @@ def Agent_portrayal(agent): "Filled": "true", "r": 0.5, "Layer": 0, - "Color": "#FF0A01" + "Color": "#FF0A01", } elif isinstance(agent, Sugar): diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py index 7287d484ac5..477a0308a91 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py @@ -127,7 +127,8 @@ def calculate_welfare(self, sugar, spice): # calculate total resources m_total = self.metabolism_sugar + self.metabolism_spice - # Cobb-Douglas functional form; starting on p. 97 on Growing Artificial Societies + # Cobb-Douglas functional form; starting on p. 97 + # on Growing Artificial Societies return sugar ** (self.metabolism_sugar / m_total) * spice ** ( self.metabolism_spice / m_total ) diff --git a/examples/advanced/wolf_sheep/wolf_sheep/model.py b/examples/advanced/wolf_sheep/wolf_sheep/model.py index 2b8fdbdeed1..160a1e93e3c 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/model.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/model.py @@ -114,7 +114,6 @@ def __init__( # Create grass patches if self.grass: for agent, x, y in self.grid.coord_iter(): - fully_grown = self.random.choice([True, False]) if fully_grown: @@ -144,7 +143,6 @@ def step(self): ) def run_model(self, step_count=200): - if self.verbose: print("Initial number wolves: ", self.schedule.get_type_count(Wolf)) print("Initial number sheep: ", self.schedule.get_type_count(Sheep)) diff --git a/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py b/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py index e2a59fc7c80..cd3dac61f9d 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py @@ -19,7 +19,8 @@ def get_type_count( filter_func: Callable[[mesa.Agent], bool] = None, ) -> int: """ - Returns the current number of agents of certain type in the queue that satisfy the filter function. + Returns the current number of agents of certain type in the queue + that satisfy the filter function. """ count = 0 for agent in self.agents_by_type[type_class].values(): From 7627f03ec89458e697c2ea6c52e8c31ac63071bd Mon Sep 17 00:00:00 2001 From: Catherine Devlin Date: Tue, 25 Apr 2023 12:05:47 -0600 Subject: [PATCH 011/116] Use pre-commit (#27) * Use Pathlib Code by Phil Robare (versilimidude2) * Use pre-commit * Dropped unused imports * used pre-commit to run pyupgrade, trim whitespace * Remove pathlib changes These belong in a separate PR. * remove redundant black, move comment as per rht's suggestions --------- Co-authored-by: Catherine Devlin --- .../basic/conways_game_of_life/conways_game_of_life/model.py | 2 +- examples/basic/virus_on_network/virus_on_network/model.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/basic/conways_game_of_life/conways_game_of_life/model.py b/examples/basic/conways_game_of_life/conways_game_of_life/model.py index 635ccaa959d..03fa52a07b1 100644 --- a/examples/basic/conways_game_of_life/conways_game_of_life/model.py +++ b/examples/basic/conways_game_of_life/conways_game_of_life/model.py @@ -27,7 +27,7 @@ def __init__(self, width=50, height=50): # Place a cell at each location, with some initialized to # ALIVE and some to DEAD. - for (contents, x, y) in self.grid.coord_iter(): + for contents, x, y in self.grid.coord_iter(): cell = Cell((x, y), self) if self.random.random() < 0.1: cell.state = cell.ALIVE diff --git a/examples/basic/virus_on_network/virus_on_network/model.py b/examples/basic/virus_on_network/virus_on_network/model.py index 7d1e68f7f15..6e079ffbefe 100644 --- a/examples/basic/virus_on_network/virus_on_network/model.py +++ b/examples/basic/virus_on_network/virus_on_network/model.py @@ -40,7 +40,6 @@ def __init__( recovery_chance=0.3, gain_resistance_chance=0.5, ): - self.num_nodes = num_nodes prob = avg_node_degree / self.num_nodes self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=prob) From 36a407e99b6d89e1129986366a26f9087b02412e Mon Sep 17 00:00:00 2001 From: Jeremy Silver Date: Tue, 25 Apr 2023 12:38:25 -0600 Subject: [PATCH 012/116] Update examples to version 1.2 (using SingleGrid instead of Grid) (#30) Co-authored-by: Jeremy Silver --- .../epstein_civil_violence/epstein_civil_violence/model.py | 2 +- examples/advanced/epstein_civil_violence/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py index 9f5bf4750db..4784f08b116 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py @@ -59,7 +59,7 @@ def __init__( self.max_iters = max_iters self.iteration = 0 self.schedule = mesa.time.RandomActivation(self) - self.grid = mesa.space.Grid(width, height, torus=True) + self.grid = mesa.space.SingleGrid(width, height, torus=True) model_reporters = { "Quiescent": lambda m: self.count_type_citizens(m, "Quiescent"), "Active": lambda m: self.count_type_citizens(m, "Active"), diff --git a/examples/advanced/epstein_civil_violence/requirements.txt b/examples/advanced/epstein_civil_violence/requirements.txt index 19b805acb1f..42445310f44 100644 --- a/examples/advanced/epstein_civil_violence/requirements.txt +++ b/examples/advanced/epstein_civil_violence/requirements.txt @@ -1,3 +1,3 @@ jupyter matplotlib -mesa~=1.1 +mesa~=1.2 From 8b2295d2e13eb8388e4c12d1bcc6d3feeb1c4d96 Mon Sep 17 00:00:00 2001 From: Jeremy Silver Date: Tue, 25 Apr 2023 12:38:25 -0600 Subject: [PATCH 013/116] Update examples to version 1.2 (using SingleGrid instead of Grid) (#30) Co-authored-by: Jeremy Silver --- .../basic/conways_game_of_life/conways_game_of_life/model.py | 2 +- examples/basic/conways_game_of_life/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/basic/conways_game_of_life/conways_game_of_life/model.py b/examples/basic/conways_game_of_life/conways_game_of_life/model.py index 03fa52a07b1..581541c11d3 100644 --- a/examples/basic/conways_game_of_life/conways_game_of_life/model.py +++ b/examples/basic/conways_game_of_life/conways_game_of_life/model.py @@ -23,7 +23,7 @@ def __init__(self, width=50, height=50): self.schedule = mesa.time.SimultaneousActivation(self) # Use a simple grid, where edges wrap around. - self.grid = mesa.space.Grid(width, height, torus=True) + self.grid = mesa.space.SingleGrid(width, height, torus=True) # Place a cell at each location, with some initialized to # ALIVE and some to DEAD. diff --git a/examples/basic/conways_game_of_life/requirements.txt b/examples/basic/conways_game_of_life/requirements.txt index 0d2d0bc66aa..1a7fa12e2a4 100644 --- a/examples/basic/conways_game_of_life/requirements.txt +++ b/examples/basic/conways_game_of_life/requirements.txt @@ -1 +1 @@ -mesa~=1.1 \ No newline at end of file +mesa~=1.2 \ No newline at end of file From 1b0375257bdec972c479ea75683ece877321c17f Mon Sep 17 00:00:00 2001 From: Stephen Mubita Date: Mon, 24 Apr 2023 21:05:35 -0500 Subject: [PATCH 014/116] Add a 'headless' option to run.py to allow wolf_sheep to run headless --- examples/advanced/wolf_sheep/run.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/advanced/wolf_sheep/run.py b/examples/advanced/wolf_sheep/run.py index dc5d367e89d..0f51c273120 100644 --- a/examples/advanced/wolf_sheep/run.py +++ b/examples/advanced/wolf_sheep/run.py @@ -1,3 +1,9 @@ from wolf_sheep.server import server +import sys -server.launch() +open_browser: bool = True + +if sys.argv[1] == 'headless': + open_browser = False + +server.launch(open_browser=open_browser) From 4fb3eec623076ded9fade8c435f7556d8007b6c8 Mon Sep 17 00:00:00 2001 From: houssam7737 Date: Tue, 25 Apr 2023 17:35:07 -0400 Subject: [PATCH 015/116] Fix bug boltzman model agent giving money to itself (#28) * fix bug where agent gives money to itself in the boltzmann model example * [pre-commit.ci] auto fixes from pre-commit.com hooks --------- Co-authored-by: Houssam Kherraz Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../boltzmann_wealth_model/boltzmann_wealth_model/model.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py index 76ebc516b36..0f61b8838d6 100644 --- a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -62,7 +62,10 @@ def move(self): def give_money(self): cellmates = self.model.grid.get_cell_list_contents([self.pos]) - if len(cellmates) > 1: + cellmates.pop( + cellmates.index(self) + ) # Ensure agent is not giving money to itself + if len(cellmates) > 0: other = self.random.choice(cellmates) other.wealth += 1 self.wealth -= 1 From 13331fe1f9af4db6a40a0fe5e77b94c19b5fa03f Mon Sep 17 00:00:00 2001 From: Stephen Mubita Date: Mon, 24 Apr 2023 21:22:50 -0500 Subject: [PATCH 016/116] check to make sure sys.argv actually has two or more items --- examples/advanced/wolf_sheep/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/advanced/wolf_sheep/run.py b/examples/advanced/wolf_sheep/run.py index 0f51c273120..8426a52dce9 100644 --- a/examples/advanced/wolf_sheep/run.py +++ b/examples/advanced/wolf_sheep/run.py @@ -3,7 +3,7 @@ open_browser: bool = True -if sys.argv[1] == 'headless': +if sys.argc >= 2 and sys.argv[1] == 'headless': open_browser = False server.launch(open_browser=open_browser) From 4b19116cff0d8766863883629aba250056944036 Mon Sep 17 00:00:00 2001 From: Stephen Mubita Date: Tue, 25 Apr 2023 14:44:46 -0500 Subject: [PATCH 017/116] modified wolf_sheep edits to just include the one liine explicit true option; modified most other examples in the same way --- examples/basic/boid_flockers/run.py | 2 +- examples/basic/boltzmann_wealth_model/run.py | 2 +- examples/basic/conways_game_of_life/run.py | 2 +- examples/basic/schelling/run.py | 2 +- examples/basic/virus_on_network/run.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/basic/boid_flockers/run.py b/examples/basic/boid_flockers/run.py index be0c1c75c58..0d9ca624248 100644 --- a/examples/basic/boid_flockers/run.py +++ b/examples/basic/boid_flockers/run.py @@ -1,3 +1,3 @@ from boid_flockers.server import server -server.launch() +server.launch(open_browser=True) diff --git a/examples/basic/boltzmann_wealth_model/run.py b/examples/basic/boltzmann_wealth_model/run.py index ea57809eb0a..f17675937cc 100644 --- a/examples/basic/boltzmann_wealth_model/run.py +++ b/examples/basic/boltzmann_wealth_model/run.py @@ -1,3 +1,3 @@ from boltzmann_wealth_model.server import server -server.launch() +server.launch(open_browser=True) diff --git a/examples/basic/conways_game_of_life/run.py b/examples/basic/conways_game_of_life/run.py index 2854fdee59d..7095816577c 100644 --- a/examples/basic/conways_game_of_life/run.py +++ b/examples/basic/conways_game_of_life/run.py @@ -1,3 +1,3 @@ from conways_game_of_life.server import server -server.launch() +server.launch(open_browser=True) diff --git a/examples/basic/schelling/run.py b/examples/basic/schelling/run.py index a25f3b1294e..f20cebcbd5f 100644 --- a/examples/basic/schelling/run.py +++ b/examples/basic/schelling/run.py @@ -1,3 +1,3 @@ from server import server -server.launch() +server.launch(open_browser=True) diff --git a/examples/basic/virus_on_network/run.py b/examples/basic/virus_on_network/run.py index 9f1ef7292ae..c911c372a9a 100644 --- a/examples/basic/virus_on_network/run.py +++ b/examples/basic/virus_on_network/run.py @@ -1,3 +1,3 @@ from virus_on_network.server import server -server.launch() +server.launch(open_browser=True) From b903296b7a5227ae1f2d676ce05fab3e8141fb65 Mon Sep 17 00:00:00 2001 From: Stephen Mubita Date: Tue, 25 Apr 2023 14:44:46 -0500 Subject: [PATCH 018/116] modified wolf_sheep edits to just include the one liine explicit true option; modified most other examples in the same way --- examples/advanced/epstein_civil_violence/run.py | 2 +- examples/advanced/pd_grid/run.py | 2 +- examples/advanced/wolf_sheep/run.py | 8 +------- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/examples/advanced/epstein_civil_violence/run.py b/examples/advanced/epstein_civil_violence/run.py index 5aa2644ac3d..a4b62c855d8 100644 --- a/examples/advanced/epstein_civil_violence/run.py +++ b/examples/advanced/epstein_civil_violence/run.py @@ -1,3 +1,3 @@ from epstein_civil_violence.server import server -server.launch() +server.launch(open_browser=True) diff --git a/examples/advanced/pd_grid/run.py b/examples/advanced/pd_grid/run.py index ec7d04bebfa..ae142aaaeea 100644 --- a/examples/advanced/pd_grid/run.py +++ b/examples/advanced/pd_grid/run.py @@ -1,3 +1,3 @@ from pd_grid.server import server -server.launch() +server.launch(open_browser=True) diff --git a/examples/advanced/wolf_sheep/run.py b/examples/advanced/wolf_sheep/run.py index 8426a52dce9..89e3b5488df 100644 --- a/examples/advanced/wolf_sheep/run.py +++ b/examples/advanced/wolf_sheep/run.py @@ -1,9 +1,3 @@ from wolf_sheep.server import server -import sys -open_browser: bool = True - -if sys.argc >= 2 and sys.argv[1] == 'headless': - open_browser = False - -server.launch(open_browser=open_browser) +server.launch(open_browser=True) From ff30c87bcda31c9f54418460bbda68ea4adf002c Mon Sep 17 00:00:00 2001 From: rht Date: Wed, 17 May 2023 06:09:12 -0400 Subject: [PATCH 019/116] Apply Black to boid_flockers --- examples/basic/boid_flockers/boid_flockers/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/basic/boid_flockers/boid_flockers/model.py b/examples/basic/boid_flockers/boid_flockers/model.py index 00a08d765d5..d694265bb75 100644 --- a/examples/basic/boid_flockers/boid_flockers/model.py +++ b/examples/basic/boid_flockers/boid_flockers/model.py @@ -67,7 +67,7 @@ def make_agents(self): velocity, self.vision, self.separation, - **self.factors + **self.factors, ) self.space.place_agent(boid, pos) self.schedule.add(boid) From baa448ceb4fb0f932cfbc70941b55cf4b55f614c Mon Sep 17 00:00:00 2001 From: Tom Pike Date: Fri, 5 May 2023 18:39:59 -0400 Subject: [PATCH 020/116] Fix bug in tutorial to calculate MRS with potential trade (#33) - update maybe_sell_spice() so calculate_MRS() takes potential sugar and spice based on trade - update calculate_MRS() with kwargs so it can take potential trade but defaults to agents sugar and spice Update increases agent_reporter due to number of trades creating memory issues - Update data collection so non-relevant data rmeoved on each step (i.e. sugar and spice agents don't have trade partners) --- examples/advanced/sugarscape_g1mt/run.py | 4 ++-- .../sugarscape_g1mt/sugarscape_g1mt/model.py | 18 ++++++++++++++ .../sugarscape_g1mt/trader_agents.py | 24 ++++++++++--------- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/run.py b/examples/advanced/sugarscape_g1mt/run.py index 114e3cf2e99..2f95cac7c19 100644 --- a/examples/advanced/sugarscape_g1mt/run.py +++ b/examples/advanced/sugarscape_g1mt/run.py @@ -88,8 +88,8 @@ def assess_results(results, single_agent): params = { "width": 50, "height": 50, - "vision_min": range(1, 3), - "metabolism_max": [3, 5], + "vision_min": range(1, 4), + "metabolism_max": [2, 3, 4, 5], } results_batch = mesa.batch_run( diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index 1e3dbb60786..78dc275bd78 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -187,6 +187,24 @@ def step(self): # collect model level data self.datacollector.collect(self) + """ + Mesa is working on updating datacollector agent reporter + so it can collect information on specific agents from + mesa.time.RandomActivationByType. + + Please see issue #1419 at + https://github.com/projectmesa/mesa/issues/1419 + (contributions welcome) + + Below is one way to update agent_records to get specific Trader agent data + """ + # Need to remove excess data + # Create local variable to store trade data + agent_trades = self.datacollector._agent_records[self.schedule.steps] + # Get rid of all None to reduce data storage needs + agent_trades = [agent for agent in agent_trades if agent[2] is not None] + # Reassign the dictionary value with lean trade data + self.datacollector._agent_records[self.schedule.steps] = agent_trades def run_model(self, step_count=1000): for i in range(step_count): diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py index 477a0308a91..7607a69d5dd 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py @@ -140,16 +140,16 @@ def is_starved(self): return (self.sugar <= 0) or (self.spice <= 0) - def calculate_MRS(self): + def calculate_MRS(self, sugar, spice): """ - Helper function for self.trade() + Helper function for + - self.trade() + - self.maybe_self_spice() - Determines what trader agent is needs and can give up + Determines what trader agent needs and can give up """ - return (self.spice / self.metabolism_spice) / ( - self.sugar / self.metabolism_sugar - ) + return (spice / self.metabolism_spice) / (sugar / self.metabolism_sugar) def calculate_sell_spice_amount(self, price): """ @@ -205,8 +205,10 @@ def maybe_sell_spice(self, other, price, welfare_self, welfare_other): welfare_self < self.calculate_welfare(self_sugar, self_spice) ) and (welfare_other < other.calculate_welfare(other_sugar, other_spice)) - # trade criteria #2 is their mrs crossing - mrs_not_crossing = self.calculate_MRS() > other.calculate_MRS() + # trade criteria #2 is their mrs crossing with potential trade + mrs_not_crossing = self.calculate_MRS( + self_sugar, self_spice + ) > other.calculate_MRS(other_sugar, other_spice) if not (both_agents_better_off and mrs_not_crossing): return False @@ -229,9 +231,9 @@ def trade(self, other): assert other.sugar > 0 assert other.spice > 0 - # calculate marginal rate of subsitution in Growing Artificial Socieites p. 101 - mrs_self = self.calculate_MRS() - mrs_other = other.calculate_MRS() + # calculate marginal rate of substitution in Growing Artificial Societies p. 101 + mrs_self = self.calculate_MRS(self.sugar, self.spice) + mrs_other = other.calculate_MRS(other.sugar, other.spice) # calculate each agents welfare welfare_self = self.calculate_welfare(self.sugar, self.spice) From 803e41228cce8b68e7353ad02a05664de10c7155 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Sun, 11 Jun 2023 17:33:34 +0200 Subject: [PATCH 021/116] Implement Streamlit UI for Boltzmann wealth model (#36) * Implement Streamlit UI for Boltzmann wealth model * change slider text * change slider text * update readme --------- Co-authored-by: Ankit Kumar --- .../basic/boltzmann_wealth_model/Readme.md | 6 + examples/basic/boltzmann_wealth_model/app.py | 117 ++++++++++++++++++ examples/basic/conways_game_of_life/app.py | 80 ++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 examples/basic/boltzmann_wealth_model/app.py create mode 100644 examples/basic/conways_game_of_life/app.py diff --git a/examples/basic/boltzmann_wealth_model/Readme.md b/examples/basic/boltzmann_wealth_model/Readme.md index 84b6b28ad1f..4a6e21f142a 100644 --- a/examples/basic/boltzmann_wealth_model/Readme.md +++ b/examples/basic/boltzmann_wealth_model/Readme.md @@ -33,6 +33,12 @@ If your browser doesn't open automatically, point it to [http://127.0.0.1:8521/] * ``boltzmann_wealth_model/server.py``: Code for the interactive visualization. * ``run.py``: Launches the server. +## Optional + +* ``boltzmann_wealth_model/app.py``: can be used to run the simulation via the streamlit interface. +* For this some additional packages like ``streamlit`` and ``altair`` needs to be installed. +* Once installed, the app can be opened in the browser using : ``streamlit run app.py`` + ## Further Reading The full tutorial describing how the model is built can be found at: diff --git a/examples/basic/boltzmann_wealth_model/app.py b/examples/basic/boltzmann_wealth_model/app.py new file mode 100644 index 00000000000..8118cc70a76 --- /dev/null +++ b/examples/basic/boltzmann_wealth_model/app.py @@ -0,0 +1,117 @@ +from boltzmann_wealth_model.model import BoltzmannWealthModel + +import streamlit as st + +import time + +import pandas as pd + +import altair as alt + + +model = st.title("Boltzman Wealth Model") +num_agents = st.slider( + "Choose how many agents to include in the model", + min_value=1, + max_value=100, + value=50, +) +num_ticks = st.slider( + "Select number of Simulation Runs", min_value=1, max_value=100, value=50 +) +height = st.slider("Select Grid Height", min_value=10, max_value=100, step=10, value=15) +width = st.slider("Select Grid Width", min_value=10, max_value=100, step=10, value=20) +model = BoltzmannWealthModel(num_agents, height, width) + + +status_text = st.empty() +run = st.button("Run Simulation") + + +if run: + tick = time.time() + step = 0 + # init grid + df_grid = pd.DataFrame() + df_gini = pd.DataFrame({"step": [0], "gini": [-1]}) + for x in range(width): + for y in range(height): + df_grid = pd.concat( + [df_grid, pd.DataFrame({"x": [x], "y": [y], "agent_count": 0})], + ignore_index=True, + ) + + heatmap = ( + alt.Chart(df_grid) + .mark_point(size=100) + .encode(x="x", y="y", color=alt.Color("agent_count")) + .interactive() + .properties(width=800, height=600) + ) + + line = ( + alt.Chart(df_gini) + .mark_line(point=True) + .encode(x="step", y="gini") + .properties(width=800, height=600) + ) + + # init progress bar + my_bar = st.progress(0, text="Simulation Progress") # progress + placeholder = st.empty() + st.subheader("Agent Grid") + chart = st.altair_chart(heatmap) + st.subheader("Gini Values") + line_chart = st.altair_chart(line) + + color_scale = alt.Scale( + domain=[0, 1, 2, 3, 4], range=["red", "cyan", "white", "white", "blue"] + ) + for i in range(num_ticks): + model.step() + my_bar.progress((i / num_ticks), text="Simulation progress") + placeholder.text("Step = %d" % i) + for cell in model.grid.coord_iter(): + cell_content, x, y = cell + agent_count = len(cell_content) + selected_row = df_grid[(df_grid["x"] == x) & (df_grid["y"] == y)] + df_grid.loc[ + selected_row.index, "agent_count" + ] = agent_count # random.choice([1,2]) + + df_gini = pd.concat( + [ + df_gini, + pd.DataFrame( + {"step": [i], "gini": [model.datacollector.model_vars["Gini"][i]]} + ), + ] + ) + # st.table(df_grid) + heatmap = ( + alt.Chart(df_grid) + .mark_circle(size=100) + .encode(x="x", y="y", color=alt.Color("agent_count", scale=color_scale)) + .interactive() + .properties(width=800, height=600) + ) + chart.altair_chart(heatmap) + + line = ( + alt.Chart(df_gini) + .mark_line(point=True) + .encode(x="step", y="gini") + .properties(width=800, height=600) + ) + line_chart.altair_chart(line) + + time.sleep(0.01) + + tock = time.time() + st.success(f"Simulation completed in {tock - tick:.2f} secs") + + # st.subheader('Agent Grid') + # fig = px.imshow(agent_counts,labels={'color':'Agent Count'}) + # st.plotly_chart(fig) + # st.subheader('Gini value over sim ticks (Plotly)') + # chart = st.line_chart(model.datacollector.model_vars['Gini']) diff --git a/examples/basic/conways_game_of_life/app.py b/examples/basic/conways_game_of_life/app.py new file mode 100644 index 00000000000..78a55539ee8 --- /dev/null +++ b/examples/basic/conways_game_of_life/app.py @@ -0,0 +1,80 @@ +import mesa + +import streamlit as st + +import time + +import pandas as pd + +import altair as alt + + +import numpy as np +from conways_game_of_life.model import ConwaysGameOfLife + +import pandas as pd + + +model = st.title("Boltzman Wealth Model") +num_ticks = st.slider("Select number of Steps", min_value=1, max_value=100, value=50) +height = st.slider("Select Grid Height", min_value=10, max_value=100, step=10, value=15) +width = st.slider("Select Grid Width", min_value=10, max_value=100, step=10, value=20) +model = ConwaysGameOfLife(height, width) + +col1, col2, col3 = st.columns(3) +status_text = st.empty() +# step_mode = st.checkbox('Run Step-by-Step') +run = st.button("Run Simulation") + + +if run: + tick = time.time() + step = 0 + # init grid + df_grid = pd.DataFrame() + agent_counts = np.zeros((model.grid.width, model.grid.height)) + for x in range(width): + for y in range(height): + df_grid = pd.concat( + [df_grid, pd.DataFrame({"x": [x], "y": [y], "state": [0]})], + ignore_index=True, + ) + + heatmap = ( + alt.Chart(df_grid) + .mark_point(size=100) + .encode(x="x", y="y", color=alt.Color("state")) + .interactive() + .properties(width=800, height=600) + ) + + # init progress bar + my_bar = st.progress(0, text="Simulation Progress") # progress + placeholder = st.empty() + st.subheader("Agent Grid") + chart = st.altair_chart(heatmap, use_container_width=True) + color_scale = alt.Scale(domain=[0, 1], range=["red", "yellow"]) + for i in range(num_ticks): + model.step() + my_bar.progress((i / num_ticks), text="Simulation progress") + placeholder.text("Step = %d" % i) + for contents, x, y in model.grid.coord_iter(): + # print('x:',x,'y:',y, 'state:',contents) + selected_row = df_grid[(df_grid["x"] == x) & (df_grid["y"] == y)] + df_grid.loc[ + selected_row.index, "state" + ] = contents.state # random.choice([1,2]) + + heatmap = ( + alt.Chart(df_grid) + .mark_circle(size=100) + .encode(x="x", y="y", color=alt.Color("state", scale=color_scale)) + .interactive() + .properties(width=800, height=600) + ) + chart.altair_chart(heatmap) + + time.sleep(0.1) + + tock = time.time() + st.success(f"Simulation completed in {tock - tick:.2f} secs") From 1960a2b5e94c1d96433d61d2c15e52c4c013f3a6 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Sun, 11 Jun 2023 19:02:46 +0200 Subject: [PATCH 022/116] Update Readme.md for Conway's Game of Life (#37) See PR #36 for the app.py implementation if it. * Initialize Game of Life example * set slider text * udpate readme * Implement Streamlit UI for Boltzmann wealth model * resolve merge conflicts --------- Co-authored-by: Ankit Kumar --- examples/basic/conways_game_of_life/Readme.md | 15 +++++++++++---- examples/basic/conways_game_of_life/app.py | 1 - 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/examples/basic/conways_game_of_life/Readme.md b/examples/basic/conways_game_of_life/Readme.md index 686afb4065a..85b591aa713 100644 --- a/examples/basic/conways_game_of_life/Readme.md +++ b/examples/basic/conways_game_of_life/Readme.md @@ -19,12 +19,19 @@ Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and p ## Files -* ``game_of_life/cell.py``: Defines the behavior of an individual cell, which can be in two states: DEAD or ALIVE. -* ``game_of_life/model.py``: Defines the model itself, initialized with a random configuration of alive and dead cells. -* ``game_of_life/portrayal.py``: Describes for the front end how to render a cell. -* ``game_of_live/server.py``: Defines an interactive visualization. +* ``conways_game_of_life/cell.py``: Defines the behavior of an individual cell, which can be in two states: DEAD or ALIVE. +* ``conways_game_of_life/model.py``: Defines the model itself, initialized with a random configuration of alive and dead cells. +* ``conways_game_of_life/portrayal.py``: Describes for the front end how to render a cell. +* ``conways_game_of_life/server.py``: Defines an interactive visualization. * ``run.py``: Launches the visualization +## Optional + +* ``conways_game_of_life/app.py``: can be used to run the simulation via the streamlit interface. +* For this some additional packages like ``streamlit`` and ``altair`` needs to be installed. +* Once installed, the app can be opened in the browser using : ``streamlit run app.py`` + + ## Further Reading [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) diff --git a/examples/basic/conways_game_of_life/app.py b/examples/basic/conways_game_of_life/app.py index 78a55539ee8..be739caa2cc 100644 --- a/examples/basic/conways_game_of_life/app.py +++ b/examples/basic/conways_game_of_life/app.py @@ -1,5 +1,4 @@ import mesa - import streamlit as st import time From 734519987a3ff6d1e2e64d53177302a59e4f9d89 Mon Sep 17 00:00:00 2001 From: rht Date: Sat, 17 Jun 2023 03:14:34 -0400 Subject: [PATCH 023/116] Package Schelling and civil_violence examples --- .../epstein_civil_violence/epstein_civil_violence/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/advanced/epstein_civil_violence/epstein_civil_violence/__init__.py diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/__init__.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From bd91c31fa82fdb7d20f12f90b9a21d40796ff1a0 Mon Sep 17 00:00:00 2001 From: rht Date: Sat, 17 Jun 2023 03:14:34 -0400 Subject: [PATCH 024/116] Package Schelling and civil_violence examples --- examples/basic/schelling/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/basic/schelling/__init__.py diff --git a/examples/basic/schelling/__init__.py b/examples/basic/schelling/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From c999c7a700d2b1ad9a29612e8fa5b47c7e3ecf80 Mon Sep 17 00:00:00 2001 From: rht Date: Sat, 17 Jun 2023 03:52:59 -0400 Subject: [PATCH 025/116] Package wolf_sheep example --- examples/advanced/wolf_sheep/__init__.py | 0 examples/advanced/wolf_sheep/wolf_sheep/agents.py | 2 +- examples/advanced/wolf_sheep/wolf_sheep/model.py | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 examples/advanced/wolf_sheep/__init__.py diff --git a/examples/advanced/wolf_sheep/__init__.py b/examples/advanced/wolf_sheep/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/advanced/wolf_sheep/wolf_sheep/agents.py b/examples/advanced/wolf_sheep/wolf_sheep/agents.py index fe62192bf60..91a511fd636 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/agents.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/agents.py @@ -1,5 +1,5 @@ import mesa -from wolf_sheep.random_walk import RandomWalker +from .random_walk import RandomWalker class Sheep(RandomWalker): diff --git a/examples/advanced/wolf_sheep/wolf_sheep/model.py b/examples/advanced/wolf_sheep/wolf_sheep/model.py index 160a1e93e3c..ec23c5a693f 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/model.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/model.py @@ -11,8 +11,8 @@ import mesa -from wolf_sheep.scheduler import RandomActivationByTypeFiltered -from wolf_sheep.agents import Sheep, Wolf, GrassPatch +from .scheduler import RandomActivationByTypeFiltered +from .agents import Sheep, Wolf, GrassPatch class WolfSheep(mesa.Model): From 0eefba9ae756f2a8194cbb7e9e175f86394d04d1 Mon Sep 17 00:00:00 2001 From: rht Date: Sun, 18 Jun 2023 06:05:34 -0400 Subject: [PATCH 026/116] boltzmann wealth model: Apply isort to app.py --- examples/basic/boltzmann_wealth_model/app.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/basic/boltzmann_wealth_model/app.py b/examples/basic/boltzmann_wealth_model/app.py index 8118cc70a76..4e279f26c23 100644 --- a/examples/basic/boltzmann_wealth_model/app.py +++ b/examples/basic/boltzmann_wealth_model/app.py @@ -1,13 +1,10 @@ -from boltzmann_wealth_model.model import BoltzmannWealthModel - -import streamlit as st - import time -import pandas as pd - import altair as alt +import pandas as pd +import streamlit as st +from boltzmann_wealth_model.model import BoltzmannWealthModel model = st.title("Boltzman Wealth Model") num_agents = st.slider( From afd8e177acf78e0af0f64772bd335e31ba3c1add Mon Sep 17 00:00:00 2001 From: rht Date: Sun, 25 Jun 2023 08:52:48 -0400 Subject: [PATCH 027/116] Apply isort --- examples/basic/boltzmann_wealth_model/app.py | 1 - examples/basic/conways_game_of_life/app.py | 13 +++---------- .../conways_game_of_life/server.py | 3 +-- examples/basic/schelling/run_ascii.py | 1 - examples/basic/schelling/server.py | 1 - .../virus_on_network/virus_on_network/model.py | 2 +- .../virus_on_network/virus_on_network/server.py | 2 +- 7 files changed, 6 insertions(+), 17 deletions(-) diff --git a/examples/basic/boltzmann_wealth_model/app.py b/examples/basic/boltzmann_wealth_model/app.py index 4e279f26c23..f2dd6da91ab 100644 --- a/examples/basic/boltzmann_wealth_model/app.py +++ b/examples/basic/boltzmann_wealth_model/app.py @@ -3,7 +3,6 @@ import altair as alt import pandas as pd import streamlit as st - from boltzmann_wealth_model.model import BoltzmannWealthModel model = st.title("Boltzman Wealth Model") diff --git a/examples/basic/conways_game_of_life/app.py b/examples/basic/conways_game_of_life/app.py index be739caa2cc..c1623d359da 100644 --- a/examples/basic/conways_game_of_life/app.py +++ b/examples/basic/conways_game_of_life/app.py @@ -1,18 +1,11 @@ -import mesa -import streamlit as st - import time -import pandas as pd - import altair as alt - - +import mesa import numpy as np -from conways_game_of_life.model import ConwaysGameOfLife - import pandas as pd - +import streamlit as st +from conways_game_of_life.model import ConwaysGameOfLife model = st.title("Boltzman Wealth Model") num_ticks = st.slider("Select number of Steps", min_value=1, max_value=100, value=50) diff --git a/examples/basic/conways_game_of_life/conways_game_of_life/server.py b/examples/basic/conways_game_of_life/conways_game_of_life/server.py index 4167b3d01bd..6da932f3ee6 100644 --- a/examples/basic/conways_game_of_life/conways_game_of_life/server.py +++ b/examples/basic/conways_game_of_life/conways_game_of_life/server.py @@ -1,8 +1,7 @@ import mesa -from .portrayal import portrayCell from .model import ConwaysGameOfLife - +from .portrayal import portrayCell # Make a world that is 50x50, on a 250x250 display. canvas_element = mesa.visualization.CanvasGrid(portrayCell, 50, 50, 250, 250) diff --git a/examples/basic/schelling/run_ascii.py b/examples/basic/schelling/run_ascii.py index 8d70c39756e..460fabbb746 100644 --- a/examples/basic/schelling/run_ascii.py +++ b/examples/basic/schelling/run_ascii.py @@ -1,5 +1,4 @@ import mesa - from model import Schelling diff --git a/examples/basic/schelling/server.py b/examples/basic/schelling/server.py index fd643096db3..1396e9c7fed 100644 --- a/examples/basic/schelling/server.py +++ b/examples/basic/schelling/server.py @@ -1,5 +1,4 @@ import mesa - from model import Schelling diff --git a/examples/basic/virus_on_network/virus_on_network/model.py b/examples/basic/virus_on_network/virus_on_network/model.py index 6e079ffbefe..27753937657 100644 --- a/examples/basic/virus_on_network/virus_on_network/model.py +++ b/examples/basic/virus_on_network/virus_on_network/model.py @@ -1,8 +1,8 @@ import math from enum import Enum -import networkx as nx import mesa +import networkx as nx class State(Enum): diff --git a/examples/basic/virus_on_network/virus_on_network/server.py b/examples/basic/virus_on_network/virus_on_network/server.py index a8f47c61e6b..e325ad43c69 100644 --- a/examples/basic/virus_on_network/virus_on_network/server.py +++ b/examples/basic/virus_on_network/virus_on_network/server.py @@ -2,7 +2,7 @@ import mesa -from .model import VirusOnNetwork, State, number_infected +from .model import State, VirusOnNetwork, number_infected def network_portrayal(G): From 6d037b59a62ad0871781b6561104663d188fb3d6 Mon Sep 17 00:00:00 2001 From: rht Date: Sun, 25 Jun 2023 08:56:58 -0400 Subject: [PATCH 028/116] Apply ruff --fix --- examples/basic/boid_flockers/boid_flockers/model.py | 2 +- examples/basic/conways_game_of_life/app.py | 1 - examples/basic/schelling/model.py | 5 +---- examples/basic/virus_on_network/virus_on_network/server.py | 4 ++-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/examples/basic/boid_flockers/boid_flockers/model.py b/examples/basic/boid_flockers/boid_flockers/model.py index d694265bb75..3f7428f0924 100644 --- a/examples/basic/boid_flockers/boid_flockers/model.py +++ b/examples/basic/boid_flockers/boid_flockers/model.py @@ -46,7 +46,7 @@ def __init__( self.separation = separation self.schedule = mesa.time.RandomActivation(self) self.space = mesa.space.ContinuousSpace(width, height, True) - self.factors = dict(cohere=cohere, separate=separate, match=match) + self.factors = {"cohere": cohere, "separate": separate, "match": match} self.make_agents() self.running = True diff --git a/examples/basic/conways_game_of_life/app.py b/examples/basic/conways_game_of_life/app.py index c1623d359da..0977b6f3b43 100644 --- a/examples/basic/conways_game_of_life/app.py +++ b/examples/basic/conways_game_of_life/app.py @@ -1,7 +1,6 @@ import time import altair as alt -import mesa import numpy as np import pandas as pd import streamlit as st diff --git a/examples/basic/schelling/model.py b/examples/basic/schelling/model.py index 821b68af951..ccc5699e9c4 100644 --- a/examples/basic/schelling/model.py +++ b/examples/basic/schelling/model.py @@ -64,10 +64,7 @@ def __init__(self, width=20, height=20, density=0.8, minority_pc=0.2, homophily= x = cell[1] y = cell[2] if self.random.random() < self.density: - if self.random.random() < self.minority_pc: - agent_type = 1 - else: - agent_type = 0 + agent_type = 1 if self.random.random() < self.minority_pc else 0 agent = SchellingAgent((x, y), self, agent_type) self.grid.place_agent(agent, (x, y)) diff --git a/examples/basic/virus_on_network/virus_on_network/server.py b/examples/basic/virus_on_network/virus_on_network/server.py index e325ad43c69..c49306a9867 100644 --- a/examples/basic/virus_on_network/virus_on_network/server.py +++ b/examples/basic/virus_on_network/virus_on_network/server.py @@ -26,7 +26,7 @@ def edge_width(agent1, agent2): def get_agents(source, target): return G.nodes[source]["agent"][0], G.nodes[target]["agent"][0] - portrayal = dict() + portrayal = {} portrayal["nodes"] = [ { "size": 6, @@ -103,7 +103,7 @@ def get_resistant_susceptible_ratio(model): 0.0, 1.0, 0.1, - description="Frequency the nodes check whether they are infected by " "a virus", + description="Frequency the nodes check whether they are infected by a virus", ), "recovery_chance": mesa.visualization.Slider( "Recovery Chance", From de4e17bd608613f39443047c2e9a2b907f2fbee0 Mon Sep 17 00:00:00 2001 From: rht Date: Sun, 25 Jun 2023 08:52:48 -0400 Subject: [PATCH 029/116] Apply isort --- .../epstein_civil_violence/epstein_civil_violence/model.py | 2 +- .../epstein_civil_violence/epstein_civil_violence/server.py | 3 +-- examples/advanced/pd_grid/pd_grid/server.py | 3 +-- examples/advanced/sugarscape_g1mt/run.py | 5 +++-- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py | 5 ++--- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py | 5 ++--- .../sugarscape_g1mt/sugarscape_g1mt/trader_agents.py | 4 +++- examples/advanced/wolf_sheep/wolf_sheep/agents.py | 1 + examples/advanced/wolf_sheep/wolf_sheep/model.py | 2 +- examples/advanced/wolf_sheep/wolf_sheep/scheduler.py | 2 +- examples/advanced/wolf_sheep/wolf_sheep/server.py | 3 +-- examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py | 3 +-- 12 files changed, 18 insertions(+), 20 deletions(-) diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py index 4784f08b116..4ba70ecba17 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py @@ -1,6 +1,6 @@ import mesa -from .agent import Cop, Citizen +from .agent import Citizen, Cop class EpsteinCivilViolence(mesa.Model): diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py index 6b835bd2b14..394ea68bbbf 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py @@ -1,8 +1,7 @@ import mesa -from .model import EpsteinCivilViolence from .agent import Citizen, Cop - +from .model import EpsteinCivilViolence COP_COLOR = "#000000" AGENT_QUIET_COLOR = "#0066CC" diff --git a/examples/advanced/pd_grid/pd_grid/server.py b/examples/advanced/pd_grid/pd_grid/server.py index 50095311ac5..f2447da37c1 100644 --- a/examples/advanced/pd_grid/pd_grid/server.py +++ b/examples/advanced/pd_grid/pd_grid/server.py @@ -1,8 +1,7 @@ import mesa -from .portrayal import portrayPDAgent from .model import PdGrid - +from .portrayal import portrayPDAgent # Make a world that is 50x50, on a 500x500 display. canvas_element = mesa.visualization.CanvasGrid(portrayPDAgent, 50, 50, 500, 500) diff --git a/examples/advanced/sugarscape_g1mt/run.py b/examples/advanced/sugarscape_g1mt/run.py index 2f95cac7c19..7f2eb274eaf 100644 --- a/examples/advanced/sugarscape_g1mt/run.py +++ b/examples/advanced/sugarscape_g1mt/run.py @@ -1,8 +1,9 @@ import sys -import pandas as pd + import matplotlib.pyplot as plt -import networkx as nx import mesa +import networkx as nx +import pandas as pd from sugarscape_g1mt.model import SugarscapeG1mt from sugarscape_g1mt.server import server diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index 78dc275bd78..a7ed520016a 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -1,9 +1,8 @@ +import mesa import numpy as np - -import mesa +from .resource_agents import Spice, Sugar from .trader_agents import Trader -from .resource_agents import Sugar, Spice # Helper Functions diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py index b5a8c32faaa..1cda2cd615c 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py @@ -1,9 +1,8 @@ import mesa -from .resource_agents import Sugar, Spice -from .trader_agents import Trader from .model import SugarscapeG1mt - +from .resource_agents import Spice, Sugar +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"} diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py index 7607a69d5dd..9eaf8f35306 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py @@ -1,6 +1,8 @@ import math + import mesa -from .resource_agents import Sugar, Spice + +from .resource_agents import Spice, Sugar # Helper function diff --git a/examples/advanced/wolf_sheep/wolf_sheep/agents.py b/examples/advanced/wolf_sheep/wolf_sheep/agents.py index 91a511fd636..eef30d5475f 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/agents.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/agents.py @@ -1,4 +1,5 @@ import mesa + from .random_walk import RandomWalker diff --git a/examples/advanced/wolf_sheep/wolf_sheep/model.py b/examples/advanced/wolf_sheep/wolf_sheep/model.py index ec23c5a693f..ac44beff42b 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/model.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/model.py @@ -11,8 +11,8 @@ import mesa +from .agents import GrassPatch, Sheep, Wolf from .scheduler import RandomActivationByTypeFiltered -from .agents import Sheep, Wolf, GrassPatch class WolfSheep(mesa.Model): diff --git a/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py b/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py index cd3dac61f9d..2f5b1c9e05c 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py @@ -1,4 +1,4 @@ -from typing import Type, Callable +from typing import Callable, Type import mesa diff --git a/examples/advanced/wolf_sheep/wolf_sheep/server.py b/examples/advanced/wolf_sheep/wolf_sheep/server.py index bccf4ec849d..112c1a2dfda 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/server.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/server.py @@ -1,6 +1,5 @@ import mesa - -from wolf_sheep.agents import Wolf, Sheep, GrassPatch +from wolf_sheep.agents import GrassPatch, Sheep, Wolf from wolf_sheep.model import WolfSheep diff --git a/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py b/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py index ab3b044ab1e..d2340fedba3 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py @@ -6,8 +6,7 @@ from mesa import Model from mesa.space import MultiGrid from mesa.time import RandomActivation -from mesa.visualization.TextVisualization import TextVisualization, TextGrid - +from mesa.visualization.TextVisualization import TextGrid, TextVisualization from wolf_sheep.random_walk import RandomWalker From 656abaf06ba027f92e2d0fd5bff17d5622eeda92 Mon Sep 17 00:00:00 2001 From: rht Date: Sun, 25 Jun 2023 09:25:49 -0400 Subject: [PATCH 030/116] Fix remaining Ruff errors manually --- examples/basic/virus_on_network/virus_on_network/model.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/basic/virus_on_network/virus_on_network/model.py b/examples/basic/virus_on_network/virus_on_network/model.py index 27753937657..6f9524900f6 100644 --- a/examples/basic/virus_on_network/virus_on_network/model.py +++ b/examples/basic/virus_on_network/virus_on_network/model.py @@ -148,10 +148,10 @@ def try_remove_infection(self): self.state = State.INFECTED def try_check_situation(self): - if self.random.random() < self.virus_check_frequency: - # Checking... - if self.state is State.INFECTED: - self.try_remove_infection() + if (self.random.random() < self.virus_check_frequency) and ( + self.state is State.INFECTED + ): + self.try_remove_infection() def step(self): if self.state is State.INFECTED: From fd7edb1805b37a3641cb36d316ba1d505f72b65e Mon Sep 17 00:00:00 2001 From: rht Date: Sun, 25 Jun 2023 08:56:58 -0400 Subject: [PATCH 031/116] Apply ruff --fix --- .../epstein_civil_violence/server.py | 20 +++++++++---------- .../sugarscape_g1mt/sugarscape_g1mt/server.py | 20 ++++--------------- .../sugarscape_g1mt/trader_agents.py | 6 +----- .../wolf_sheep/wolf_sheep/scheduler.py | 4 ++-- .../advanced/wolf_sheep/wolf_sheep/server.py | 1 + .../wolf_sheep/wolf_sheep/test_random_walk.py | 1 + 6 files changed, 19 insertions(+), 33 deletions(-) diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py index 394ea68bbbf..04412835b90 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py @@ -36,16 +36,16 @@ def citizen_cop_portrayal(agent): return portrayal -model_params = dict( - height=40, - width=40, - citizen_density=0.7, - cop_density=0.074, - citizen_vision=7, - cop_vision=7, - legitimacy=0.8, - max_jail_term=1000, -) +model_params = { + "height": 40, + "width": 40, + "citizen_density": 0.7, + "cop_density": 0.074, + "citizen_vision": 7, + "cop_vision": 7, + "legitimacy": 0.8, + "max_jail_term": 1000, +} canvas_element = mesa.visualization.CanvasGrid(citizen_cop_portrayal, 40, 40, 480, 480) server = mesa.visualization.ModularServer( diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py index 1cda2cd615c..fbc10ce6c54 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py @@ -22,14 +22,8 @@ def Agent_portrayal(agent): } elif isinstance(agent, Sugar): - if agent.amount != 0: - color = sugar_dic[agent.amount] - else: - color = "#D6F5D6" - if agent.amount > 2: - layer = 1 - else: - layer = 0 + color = sugar_dic[agent.amount] if agent.amount != 0 else "#D6F5D6" + layer = 1 if agent.amount > 2 else 0 return { "Color": color, "Shape": "rect", @@ -40,14 +34,8 @@ def Agent_portrayal(agent): } elif isinstance(agent, Spice): - if agent.amount != 0: - color = spice_dic[agent.amount] - else: - color = "#D6F5D6" - if agent.amount > 2: - layer = 1 - else: - layer = 0 + color = spice_dic[agent.amount] if agent.amount != 0 else "#D6F5D6" + layer = 1 if agent.amount > 2 else 0 return { "Color": color, "Shape": "rect", diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py index 9eaf8f35306..e396f72ff1d 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py @@ -113,11 +113,7 @@ def is_occupied_by_other(self, pos): return False # get contents of each cell in neighborhood this_cell = self.model.grid.get_cell_list_contents(pos) - for a in this_cell: - # see if occupied by another agent - if isinstance(a, Trader): - return True - return False + return any(isinstance(a, Trader) for a in this_cell) def calculate_welfare(self, sugar, spice): """ diff --git a/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py b/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py index 2f5b1c9e05c..4279de716d0 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py @@ -1,4 +1,4 @@ -from typing import Callable, Type +from typing import Callable, Optional, Type import mesa @@ -16,7 +16,7 @@ class RandomActivationByTypeFiltered(mesa.time.RandomActivationByType): def get_type_count( self, type_class: Type[mesa.Agent], - filter_func: Callable[[mesa.Agent], bool] = None, + filter_func: Optional[Callable[[mesa.Agent], bool]] = None, ) -> int: """ Returns the current number of agents of certain type in the queue diff --git a/examples/advanced/wolf_sheep/wolf_sheep/server.py b/examples/advanced/wolf_sheep/wolf_sheep/server.py index 112c1a2dfda..7b1b831a574 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/server.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/server.py @@ -1,4 +1,5 @@ import mesa + from wolf_sheep.agents import GrassPatch, Sheep, Wolf from wolf_sheep.model import WolfSheep diff --git a/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py b/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py index d2340fedba3..0ba480ccdae 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py @@ -7,6 +7,7 @@ from mesa.space import MultiGrid from mesa.time import RandomActivation from mesa.visualization.TextVisualization import TextGrid, TextVisualization + from wolf_sheep.random_walk import RandomWalker From 315101a292f5e3fd2c8e25bb44cb934686384717 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 29 Jun 2023 01:45:50 -0400 Subject: [PATCH 032/116] boltzmann wealth model: Replace Streamlit viz with Solara --- .../basic/boltzmann_wealth_model/Readme.md | 6 +- examples/basic/boltzmann_wealth_model/app.py | 126 +++--------------- 2 files changed, 19 insertions(+), 113 deletions(-) diff --git a/examples/basic/boltzmann_wealth_model/Readme.md b/examples/basic/boltzmann_wealth_model/Readme.md index 4a6e21f142a..9f952ef20f7 100644 --- a/examples/basic/boltzmann_wealth_model/Readme.md +++ b/examples/basic/boltzmann_wealth_model/Readme.md @@ -35,9 +35,9 @@ If your browser doesn't open automatically, point it to [http://127.0.0.1:8521/] ## Optional -* ``boltzmann_wealth_model/app.py``: can be used to run the simulation via the streamlit interface. -* For this some additional packages like ``streamlit`` and ``altair`` needs to be installed. -* Once installed, the app can be opened in the browser using : ``streamlit run app.py`` +* ``boltzmann_wealth_model/app.py``: can be used to run the simulation via the Solara interface. +* For this, an additional packages ``solara`` needs to be installed. +* Once installed, the app can be opened in the browser after running ``solara run app.py`` ## Further Reading diff --git a/examples/basic/boltzmann_wealth_model/app.py b/examples/basic/boltzmann_wealth_model/app.py index f2dd6da91ab..c1b5aab3b5b 100644 --- a/examples/basic/boltzmann_wealth_model/app.py +++ b/examples/basic/boltzmann_wealth_model/app.py @@ -1,113 +1,19 @@ -import time - -import altair as alt -import pandas as pd -import streamlit as st +from mesa_models.experimental import JupyterViz from boltzmann_wealth_model.model import BoltzmannWealthModel -model = st.title("Boltzman Wealth Model") -num_agents = st.slider( - "Choose how many agents to include in the model", - min_value=1, - max_value=100, - value=50, -) -num_ticks = st.slider( - "Select number of Simulation Runs", min_value=1, max_value=100, value=50 +model_params = { + "N": { + "type": "SliderInt", + "value": 50, + "label": "Number of agents:", + "min": 10, + "max": 100, + "step": 1, + }, + "width": 10, + "height": 10, +} + +page = JupyterViz( + BoltzmannWealthModel, model_params, measures=["Gini"], name="Money Model" ) -height = st.slider("Select Grid Height", min_value=10, max_value=100, step=10, value=15) -width = st.slider("Select Grid Width", min_value=10, max_value=100, step=10, value=20) -model = BoltzmannWealthModel(num_agents, height, width) - - -status_text = st.empty() -run = st.button("Run Simulation") - - -if run: - tick = time.time() - step = 0 - # init grid - df_grid = pd.DataFrame() - df_gini = pd.DataFrame({"step": [0], "gini": [-1]}) - for x in range(width): - for y in range(height): - df_grid = pd.concat( - [df_grid, pd.DataFrame({"x": [x], "y": [y], "agent_count": 0})], - ignore_index=True, - ) - - heatmap = ( - alt.Chart(df_grid) - .mark_point(size=100) - .encode(x="x", y="y", color=alt.Color("agent_count")) - .interactive() - .properties(width=800, height=600) - ) - - line = ( - alt.Chart(df_gini) - .mark_line(point=True) - .encode(x="step", y="gini") - .properties(width=800, height=600) - ) - - # init progress bar - my_bar = st.progress(0, text="Simulation Progress") # progress - placeholder = st.empty() - st.subheader("Agent Grid") - chart = st.altair_chart(heatmap) - st.subheader("Gini Values") - line_chart = st.altair_chart(line) - - color_scale = alt.Scale( - domain=[0, 1, 2, 3, 4], range=["red", "cyan", "white", "white", "blue"] - ) - for i in range(num_ticks): - model.step() - my_bar.progress((i / num_ticks), text="Simulation progress") - placeholder.text("Step = %d" % i) - for cell in model.grid.coord_iter(): - cell_content, x, y = cell - agent_count = len(cell_content) - selected_row = df_grid[(df_grid["x"] == x) & (df_grid["y"] == y)] - df_grid.loc[ - selected_row.index, "agent_count" - ] = agent_count # random.choice([1,2]) - - df_gini = pd.concat( - [ - df_gini, - pd.DataFrame( - {"step": [i], "gini": [model.datacollector.model_vars["Gini"][i]]} - ), - ] - ) - # st.table(df_grid) - heatmap = ( - alt.Chart(df_grid) - .mark_circle(size=100) - .encode(x="x", y="y", color=alt.Color("agent_count", scale=color_scale)) - .interactive() - .properties(width=800, height=600) - ) - chart.altair_chart(heatmap) - - line = ( - alt.Chart(df_gini) - .mark_line(point=True) - .encode(x="step", y="gini") - .properties(width=800, height=600) - ) - line_chart.altair_chart(line) - - time.sleep(0.01) - - tock = time.time() - st.success(f"Simulation completed in {tock - tick:.2f} secs") - - # st.subheader('Agent Grid') - # fig = px.imshow(agent_counts,labels={'color':'Agent Count'}) - # st.plotly_chart(fig) - # st.subheader('Gini value over sim ticks (Plotly)') - # chart = st.line_chart(model.datacollector.model_vars['Gini']) From 8f947d8f348ccea5f1178cac70a3ab5eda5dbce5 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 29 Jun 2023 01:48:53 -0400 Subject: [PATCH 033/116] Simplify boltzmann_wealth_model frontend setup --- .../basic/boltzmann_wealth_model/Readme.md | 21 ++++------ .../{boltzmann_wealth_model => }/__init__.py | 0 examples/basic/boltzmann_wealth_model/app.py | 4 +- .../boltzmann_wealth_model/server.py | 40 ------------------- .../{boltzmann_wealth_model => }/model.py | 0 .../boltzmann_wealth_model/requirements.txt | 1 + examples/basic/boltzmann_wealth_model/run.py | 3 -- 7 files changed, 11 insertions(+), 58 deletions(-) rename examples/basic/boltzmann_wealth_model/{boltzmann_wealth_model => }/__init__.py (100%) delete mode 100644 examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/server.py rename examples/basic/boltzmann_wealth_model/{boltzmann_wealth_model => }/model.py (100%) delete mode 100644 examples/basic/boltzmann_wealth_model/run.py diff --git a/examples/basic/boltzmann_wealth_model/Readme.md b/examples/basic/boltzmann_wealth_model/Readme.md index 9f952ef20f7..fc27fdb2c28 100644 --- a/examples/basic/boltzmann_wealth_model/Readme.md +++ b/examples/basic/boltzmann_wealth_model/Readme.md @@ -12,32 +12,25 @@ As the model runs, the distribution of wealth among agents goes from being perfe To follow the tutorial example, launch the Jupyter Notebook and run the code in ``Introduction to Mesa Tutorial Code.ipynb`` which you can find in the main mesa repo [here](https://github.com/projectmesa/mesa/blob/main/docs/tutorials/intro_tutorial.ipynb) -To launch the interactive server, as described in the [last section of the tutorial](https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html#adding-visualization), run: +Make sure to install the requirements first: ``` - $ python server.py + pip install -r requirements.txt ``` -Make sure to install the requirements first: +To launch the interactive server, as described in the [last section of the tutorial](https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html#adding-visualization), run: ``` - pip install -r requirements.txt + $ solara run app.py ``` -If your browser doesn't open automatically, point it to [http://127.0.0.1:8521/](http://127.0.0.1:8521/). When the visualization loads, press Reset, then Run. +If your browser doesn't open automatically, point it to [http://127.0.0.1:8765/](http://127.0.0.1:8765/). When the visualization loads, click on the Play button. ## Files -* ``boltzmann_wealth_model/model.py``: Final version of the model. -* ``boltzmann_wealth_model/server.py``: Code for the interactive visualization. -* ``run.py``: Launches the server. - -## Optional - -* ``boltzmann_wealth_model/app.py``: can be used to run the simulation via the Solara interface. -* For this, an additional packages ``solara`` needs to be installed. -* Once installed, the app can be opened in the browser after running ``solara run app.py`` +* ``model.py``: Final version of the model. +* ``app.py``: Code for the interactive visualization. ## Further Reading diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py b/examples/basic/boltzmann_wealth_model/__init__.py similarity index 100% rename from examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py rename to examples/basic/boltzmann_wealth_model/__init__.py diff --git a/examples/basic/boltzmann_wealth_model/app.py b/examples/basic/boltzmann_wealth_model/app.py index c1b5aab3b5b..b87da8fef98 100644 --- a/examples/basic/boltzmann_wealth_model/app.py +++ b/examples/basic/boltzmann_wealth_model/app.py @@ -1,5 +1,6 @@ from mesa_models.experimental import JupyterViz -from boltzmann_wealth_model.model import BoltzmannWealthModel + +from model import BoltzmannWealthModel model_params = { "N": { @@ -17,3 +18,4 @@ page = JupyterViz( BoltzmannWealthModel, model_params, measures=["Gini"], name="Money Model" ) +page diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/server.py b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/server.py deleted file mode 100644 index a49546ce741..00000000000 --- a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/server.py +++ /dev/null @@ -1,40 +0,0 @@ -import mesa - -from .model import BoltzmannWealthModel - - -def agent_portrayal(agent): - portrayal = {"Shape": "circle", "Filled": "true", "r": 0.5} - - if agent.wealth > 0: - portrayal["Color"] = "red" - portrayal["Layer"] = 0 - else: - portrayal["Color"] = "grey" - portrayal["Layer"] = 1 - portrayal["r"] = 0.2 - return portrayal - - -grid = mesa.visualization.CanvasGrid(agent_portrayal, 10, 10, 500, 500) -chart = mesa.visualization.ChartModule( - [{"Label": "Gini", "Color": "#0000FF"}], data_collector_name="datacollector" -) - -model_params = { - "N": mesa.visualization.Slider( - "Number of agents", - 100, - 2, - 200, - 1, - description="Choose how many agents to include in the model", - ), - "width": 10, - "height": 10, -} - -server = mesa.visualization.ModularServer( - BoltzmannWealthModel, [grid, chart], "Money Model", model_params -) -server.port = 8521 diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/basic/boltzmann_wealth_model/model.py similarity index 100% rename from examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py rename to examples/basic/boltzmann_wealth_model/model.py diff --git a/examples/basic/boltzmann_wealth_model/requirements.txt b/examples/basic/boltzmann_wealth_model/requirements.txt index 63b0d24e76d..4d93614c9d4 100644 --- a/examples/basic/boltzmann_wealth_model/requirements.txt +++ b/examples/basic/boltzmann_wealth_model/requirements.txt @@ -1 +1,2 @@ mesa~=1.1 +solara diff --git a/examples/basic/boltzmann_wealth_model/run.py b/examples/basic/boltzmann_wealth_model/run.py deleted file mode 100644 index f17675937cc..00000000000 --- a/examples/basic/boltzmann_wealth_model/run.py +++ /dev/null @@ -1,3 +0,0 @@ -from boltzmann_wealth_model.server import server - -server.launch(open_browser=True) From 99d42f93339b986ab9f70d0be45bc5a7f8d30b5e Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 29 Jun 2023 03:13:43 -0400 Subject: [PATCH 034/116] fix: Move agent_portrayal to example-specific file --- examples/basic/boltzmann_wealth_model/app.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/examples/basic/boltzmann_wealth_model/app.py b/examples/basic/boltzmann_wealth_model/app.py index b87da8fef98..04f0b52ca47 100644 --- a/examples/basic/boltzmann_wealth_model/app.py +++ b/examples/basic/boltzmann_wealth_model/app.py @@ -2,6 +2,13 @@ from model import BoltzmannWealthModel + +def agent_portrayal(agent): + if agent.wealth > 0: + return 50 + return 10 + + model_params = { "N": { "type": "SliderInt", @@ -16,6 +23,10 @@ } page = JupyterViz( - BoltzmannWealthModel, model_params, measures=["Gini"], name="Money Model" + BoltzmannWealthModel, + model_params, + measures=["Gini"], + name="Money Model", + agent_portrayal=agent_portrayal, ) page From bd0d54227d317b17d10612328002506b969c3bf9 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 29 Jun 2023 03:21:15 -0400 Subject: [PATCH 035/116] feat: Support color in Jupyter viz --- examples/basic/boltzmann_wealth_model/app.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/basic/boltzmann_wealth_model/app.py b/examples/basic/boltzmann_wealth_model/app.py index 04f0b52ca47..0e263c927b7 100644 --- a/examples/basic/boltzmann_wealth_model/app.py +++ b/examples/basic/boltzmann_wealth_model/app.py @@ -4,9 +4,12 @@ def agent_portrayal(agent): + size = 10 + color = "tab:red" if agent.wealth > 0: - return 50 - return 10 + size = 50 + color = "tab:blue" + return {"size": size, "color": color} model_params = { From 65f201873ef57365e735f22a6eeefd465fc57450 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 29 Jun 2023 04:38:09 -0400 Subject: [PATCH 036/116] schelling: Switch to Jupyter viz --- examples/basic/schelling/README.md | 9 +++-- examples/basic/schelling/app.py | 54 ++++++++++++++++++++++++++++++ examples/basic/schelling/run.py | 3 -- examples/basic/schelling/server.py | 45 ------------------------- 4 files changed, 58 insertions(+), 53 deletions(-) create mode 100644 examples/basic/schelling/app.py delete mode 100644 examples/basic/schelling/run.py delete mode 100644 examples/basic/schelling/server.py diff --git a/examples/basic/schelling/README.md b/examples/basic/schelling/README.md index 64cc9c83295..b0116b55e76 100644 --- a/examples/basic/schelling/README.md +++ b/examples/basic/schelling/README.md @@ -16,13 +16,13 @@ To install the dependencies use pip and the requirements.txt in this directory. ## How to Run -To run the model interactively, run ``mesa runserver`` in this directory. e.g. +To run the model interactively, in this directory, run the following command ``` - $ 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. +Then open your browser to [http://127.0.0.1:8765/](http://127.0.0.1:8765/) and click the Play button. To view and run some example model analyses, launch the IPython Notebook and open ``analysis.ipynb``. Visualizing the analysis also requires [matplotlib](http://matplotlib.org/). @@ -32,10 +32,9 @@ To run the model with the grid displayed as an ASCII text, run `python run_ascii ## Files -* ``run.py``: Launches a model visualization server. +* ``app.py``: Code for the interactive visualization. * ``run_ascii.py``: Run the model in text mode. * ``schelling.py``: Contains the agent class, and the overall model class. -* ``server.py``: Defines classes for visualizing the model in the browser via Mesa's modular server, and instantiates a visualization server. * ``analysis.ipynb``: Notebook demonstrating how to run experiments and parameter sweeps on the model. ## Further Reading diff --git a/examples/basic/schelling/app.py b/examples/basic/schelling/app.py new file mode 100644 index 00000000000..8f2e242a68b --- /dev/null +++ b/examples/basic/schelling/app.py @@ -0,0 +1,54 @@ +from mesa_models.experimental import JupyterViz + +from model import Schelling + + +def get_happy_agents(model): + """ + Display a text count of how many happy agents there are. + """ + return f"Happy agents: {model.happy}" + + +def agent_portrayal(agent): + color = "tab:orange" if agent.type == 0 else "tab:blue" + return {"color": color} + + +model_params = { + "density": { + "type": "SliderFloat", + "value": 0.8, + "label": "Agent density", + "min": 0.1, + "max": 1.0, + "step": 0.1, + }, + "minority_pc": { + "type": "SliderFloat", + "value": 0.2, + "label": "Fraction minority", + "min": 0.0, + "max": 1.0, + "step": 0.05, + }, + "homophily": { + "type": "SliderInt", + "value": 3, + "label": "Homophily", + "min": 0, + "max": 8, + "step": 1, + }, + "width": 20, + "height": 20, +} + +page = JupyterViz( + Schelling, + model_params, + measures=["happy", get_happy_agents], + name="Schelling", + agent_portrayal=agent_portrayal, +) +page diff --git a/examples/basic/schelling/run.py b/examples/basic/schelling/run.py deleted file mode 100644 index f20cebcbd5f..00000000000 --- a/examples/basic/schelling/run.py +++ /dev/null @@ -1,3 +0,0 @@ -from server import server - -server.launch(open_browser=True) diff --git a/examples/basic/schelling/server.py b/examples/basic/schelling/server.py deleted file mode 100644 index 1396e9c7fed..00000000000 --- a/examples/basic/schelling/server.py +++ /dev/null @@ -1,45 +0,0 @@ -import mesa -from model import Schelling - - -def get_happy_agents(model): - """ - Display a text count of how many happy agents there are. - """ - return f"Happy agents: {model.happy}" - - -def schelling_draw(agent): - """ - Portrayal Method for canvas - """ - if agent is None: - return - portrayal = {"Shape": "circle", "r": 0.5, "Filled": "true", "Layer": 0} - - if agent.type == 0: - portrayal["Color"] = ["#FF0000", "#FF9999"] - portrayal["stroke_color"] = "#00FF00" - else: - portrayal["Color"] = ["#0000FF", "#9999FF"] - portrayal["stroke_color"] = "#000000" - return portrayal - - -canvas_element = mesa.visualization.CanvasGrid(schelling_draw, 20, 20, 500, 500) -happy_chart = mesa.visualization.ChartModule([{"Label": "happy", "Color": "Black"}]) - -model_params = { - "height": 20, - "width": 20, - "density": mesa.visualization.Slider("Agent density", 0.8, 0.1, 1.0, 0.1), - "minority_pc": mesa.visualization.Slider("Fraction minority", 0.2, 0.00, 1.0, 0.05), - "homophily": mesa.visualization.Slider("Homophily", 3, 0, 8, 1), -} - -server = mesa.visualization.ModularServer( - Schelling, - [canvas_element, get_happy_agents, happy_chart], - "Schelling", - model_params, -) From cdf47cf07f85dac691e66f84a7bc7765ad8ed9b8 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 29 Jun 2023 06:17:03 -0400 Subject: [PATCH 037/116] Fix Ruff errors --- examples/basic/boltzmann_wealth_model/app.py | 3 +-- examples/basic/schelling/app.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/basic/boltzmann_wealth_model/app.py b/examples/basic/boltzmann_wealth_model/app.py index 0e263c927b7..0fea75775a8 100644 --- a/examples/basic/boltzmann_wealth_model/app.py +++ b/examples/basic/boltzmann_wealth_model/app.py @@ -1,5 +1,4 @@ from mesa_models.experimental import JupyterViz - from model import BoltzmannWealthModel @@ -32,4 +31,4 @@ def agent_portrayal(agent): name="Money Model", agent_portrayal=agent_portrayal, ) -page +page # noqa diff --git a/examples/basic/schelling/app.py b/examples/basic/schelling/app.py index 8f2e242a68b..9db977a723c 100644 --- a/examples/basic/schelling/app.py +++ b/examples/basic/schelling/app.py @@ -1,5 +1,4 @@ from mesa_models.experimental import JupyterViz - from model import Schelling @@ -51,4 +50,4 @@ def agent_portrayal(agent): name="Schelling", agent_portrayal=agent_portrayal, ) -page +page # noqa From 0bbe16be983d4e3f37148fd8ad36f5d0eb3cf0c2 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 29 Jun 2023 06:21:30 -0400 Subject: [PATCH 038/116] schelling: Add solara as requirement --- examples/basic/schelling/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/basic/schelling/requirements.txt b/examples/basic/schelling/requirements.txt index 19b805acb1f..8768b8939c9 100644 --- a/examples/basic/schelling/requirements.txt +++ b/examples/basic/schelling/requirements.txt @@ -1,3 +1,4 @@ jupyter matplotlib mesa~=1.1 +solara From 48807f332eaea38599c22b051a4ab85800b84fa9 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 29 Jun 2023 06:37:43 -0400 Subject: [PATCH 039/116] Add mesa_models to requirements.txt --- examples/basic/boltzmann_wealth_model/requirements.txt | 1 + examples/basic/schelling/requirements.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/examples/basic/boltzmann_wealth_model/requirements.txt b/examples/basic/boltzmann_wealth_model/requirements.txt index 4d93614c9d4..cd191a90de6 100644 --- a/examples/basic/boltzmann_wealth_model/requirements.txt +++ b/examples/basic/boltzmann_wealth_model/requirements.txt @@ -1,2 +1,3 @@ mesa~=1.1 solara +git+https://github.com/projectmesa/mesa-examples diff --git a/examples/basic/schelling/requirements.txt b/examples/basic/schelling/requirements.txt index 8768b8939c9..2d40f057b50 100644 --- a/examples/basic/schelling/requirements.txt +++ b/examples/basic/schelling/requirements.txt @@ -2,3 +2,4 @@ jupyter matplotlib mesa~=1.1 solara +git+https://github.com/projectmesa/mesa-examples From 5cf28c8cf19096dd3080878e55c09224c418f34b Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 29 Jun 2023 06:42:05 -0400 Subject: [PATCH 040/116] Make separate copies for experimental --- .../basic/boltzmann_wealth_model/Readme.md | 44 -- .../basic/boltzmann_wealth_model/__init__.py | 0 examples/basic/boltzmann_wealth_model/app.py | 34 -- .../basic/boltzmann_wealth_model/model.py | 76 --- .../boltzmann_wealth_model/requirements.txt | 3 - examples/basic/schelling/README.md | 48 -- examples/basic/schelling/__init__.py | 0 examples/basic/schelling/analysis.ipynb | 457 ------------------ examples/basic/schelling/app.py | 53 -- examples/basic/schelling/model.py | 86 ---- examples/basic/schelling/requirements.txt | 5 - examples/basic/schelling/run_ascii.py | 48 -- 12 files changed, 854 deletions(-) delete mode 100644 examples/basic/boltzmann_wealth_model/Readme.md delete mode 100644 examples/basic/boltzmann_wealth_model/__init__.py delete mode 100644 examples/basic/boltzmann_wealth_model/app.py delete mode 100644 examples/basic/boltzmann_wealth_model/model.py delete mode 100644 examples/basic/boltzmann_wealth_model/requirements.txt delete mode 100644 examples/basic/schelling/README.md delete mode 100644 examples/basic/schelling/__init__.py delete mode 100644 examples/basic/schelling/analysis.ipynb delete mode 100644 examples/basic/schelling/app.py delete mode 100644 examples/basic/schelling/model.py delete mode 100644 examples/basic/schelling/requirements.txt delete mode 100644 examples/basic/schelling/run_ascii.py diff --git a/examples/basic/boltzmann_wealth_model/Readme.md b/examples/basic/boltzmann_wealth_model/Readme.md deleted file mode 100644 index fc27fdb2c28..00000000000 --- a/examples/basic/boltzmann_wealth_model/Readme.md +++ /dev/null @@ -1,44 +0,0 @@ -# Boltzmann Wealth Model (Tutorial) - -## Summary - -A simple model of agents exchanging wealth. All agents start with the same amount of money. Every step, each agent with one unit of money or more gives one unit of wealth to another random agent. This is the model described in the [Intro Tutorial](https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html), with the completed code. - -If you want to go over the step-by-step tutorial, please go and run the [Jupyter Notebook](https://github.com/projectmesa/mesa/blob/main/docs/tutorials/intro_tutorial.ipynb). The code here runs the finalized code in the last cells directly. - -As the model runs, the distribution of wealth among agents goes from being perfectly uniform (all agents have the same starting wealth), to highly skewed -- a small number have high wealth, more have none at all. - -## How to Run - -To follow the tutorial example, launch the Jupyter Notebook and run the code in ``Introduction to Mesa Tutorial Code.ipynb`` which you can find in the main mesa repo [here](https://github.com/projectmesa/mesa/blob/main/docs/tutorials/intro_tutorial.ipynb) - -Make sure to install the requirements first: - -``` - pip install -r requirements.txt -``` - -To launch the interactive server, as described in the [last section of the tutorial](https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html#adding-visualization), run: - -``` - $ solara run app.py -``` - -If your browser doesn't open automatically, point it to [http://127.0.0.1:8765/](http://127.0.0.1:8765/). When the visualization loads, click on the Play button. - - -## Files - -* ``model.py``: Final version of the model. -* ``app.py``: Code for the interactive visualization. - -## Further Reading - -The full tutorial describing how the model is built can be found at: -https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html - -This model is drawn from econophysics and presents a statistical mechanics approach to wealth distribution. Some examples of further reading on the topic can be found at: - -[Milakovic, M. A Statistical Equilibrium Model of Wealth Distribution. February, 2001.](https://editorialexpress.com/cgi-bin/conference/download.cgi?db_name=SCE2001&paper_id=214) - -[Dragulescu, A and Yakovenko, V. Statistical Mechanics of Money, Income, and Wealth: A Short Survey. November, 2002](http://arxiv.org/pdf/cond-mat/0211175v1.pdf) diff --git a/examples/basic/boltzmann_wealth_model/__init__.py b/examples/basic/boltzmann_wealth_model/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/examples/basic/boltzmann_wealth_model/app.py b/examples/basic/boltzmann_wealth_model/app.py deleted file mode 100644 index 0fea75775a8..00000000000 --- a/examples/basic/boltzmann_wealth_model/app.py +++ /dev/null @@ -1,34 +0,0 @@ -from mesa_models.experimental import JupyterViz -from model import BoltzmannWealthModel - - -def agent_portrayal(agent): - size = 10 - color = "tab:red" - if agent.wealth > 0: - size = 50 - color = "tab:blue" - return {"size": size, "color": color} - - -model_params = { - "N": { - "type": "SliderInt", - "value": 50, - "label": "Number of agents:", - "min": 10, - "max": 100, - "step": 1, - }, - "width": 10, - "height": 10, -} - -page = JupyterViz( - BoltzmannWealthModel, - model_params, - measures=["Gini"], - name="Money Model", - agent_portrayal=agent_portrayal, -) -page # noqa diff --git a/examples/basic/boltzmann_wealth_model/model.py b/examples/basic/boltzmann_wealth_model/model.py deleted file mode 100644 index 0f61b8838d6..00000000000 --- a/examples/basic/boltzmann_wealth_model/model.py +++ /dev/null @@ -1,76 +0,0 @@ -import mesa - - -def compute_gini(model): - agent_wealths = [agent.wealth for agent in model.schedule.agents] - x = sorted(agent_wealths) - N = model.num_agents - B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x)) - return 1 + (1 / N) - 2 * B - - -class BoltzmannWealthModel(mesa.Model): - """A simple model of an economy where agents exchange currency at random. - - All the agents begin with one unit of currency, and each time step can give - a unit of currency to another agent. Note how, over time, this produces a - highly skewed distribution of wealth. - """ - - def __init__(self, N=100, width=10, height=10): - self.num_agents = N - self.grid = mesa.space.MultiGrid(width, height, True) - self.schedule = mesa.time.RandomActivation(self) - self.datacollector = mesa.DataCollector( - model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} - ) - # Create agents - for i in range(self.num_agents): - a = MoneyAgent(i, self) - self.schedule.add(a) - # Add the agent to a random grid cell - x = self.random.randrange(self.grid.width) - y = self.random.randrange(self.grid.height) - self.grid.place_agent(a, (x, y)) - - self.running = True - self.datacollector.collect(self) - - def step(self): - self.schedule.step() - # collect data - self.datacollector.collect(self) - - def run_model(self, n): - for i in range(n): - self.step() - - -class MoneyAgent(mesa.Agent): - """An agent with fixed initial wealth.""" - - def __init__(self, unique_id, model): - super().__init__(unique_id, model) - self.wealth = 1 - - def move(self): - possible_steps = self.model.grid.get_neighborhood( - self.pos, moore=True, include_center=False - ) - new_position = self.random.choice(possible_steps) - self.model.grid.move_agent(self, new_position) - - def give_money(self): - cellmates = self.model.grid.get_cell_list_contents([self.pos]) - cellmates.pop( - cellmates.index(self) - ) # Ensure agent is not giving money to itself - if len(cellmates) > 0: - other = self.random.choice(cellmates) - other.wealth += 1 - self.wealth -= 1 - - def step(self): - self.move() - if self.wealth > 0: - self.give_money() diff --git a/examples/basic/boltzmann_wealth_model/requirements.txt b/examples/basic/boltzmann_wealth_model/requirements.txt deleted file mode 100644 index cd191a90de6..00000000000 --- a/examples/basic/boltzmann_wealth_model/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -mesa~=1.1 -solara -git+https://github.com/projectmesa/mesa-examples diff --git a/examples/basic/schelling/README.md b/examples/basic/schelling/README.md deleted file mode 100644 index b0116b55e76..00000000000 --- a/examples/basic/schelling/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Schelling Segregation Model - -## Summary - -The Schelling segregation model is a classic agent-based model, demonstrating how even a mild preference for similar neighbors can lead to a much higher degree of segregation than we would intuitively expect. The model consists of agents on a square grid, where each grid cell can contain at most one agent. Agents come in two colors: red and blue. They are happy if a certain number of their eight possible neighbors are of the same color, and unhappy otherwise. Unhappy agents will pick a random empty cell to move to each step, until they are happy. The model keeps running until there are no unhappy agents. - -By default, the number of similar neighbors the agents need to be happy is set to 3. That means the agents would be perfectly happy with a majority of their neighbors being of a different color (e.g. a Blue agent would be happy with five Red neighbors and three Blue ones). Despite this, the model consistently leads to a high degree of segregation, with most agents ending up with no neighbors of a different color. - -## 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 interactively, in this directory, run the following command - -``` - $ solara run app.py -``` - -Then open your browser to [http://127.0.0.1:8765/](http://127.0.0.1:8765/) and click the Play button. - -To view and run some example model analyses, launch the IPython Notebook and open ``analysis.ipynb``. Visualizing the analysis also requires [matplotlib](http://matplotlib.org/). - -## How to Run without the GUI - -To run the model with the grid displayed as an ASCII text, run `python run_ascii.py` in this directory. - -## Files - -* ``app.py``: Code for the interactive visualization. -* ``run_ascii.py``: Run the model in text mode. -* ``schelling.py``: Contains the agent class, and the overall model class. -* ``analysis.ipynb``: Notebook demonstrating how to run experiments and parameter sweeps on the model. - -## Further Reading - -Schelling's original paper describing the model: - -[Schelling, Thomas C. Dynamic Models of Segregation. Journal of Mathematical Sociology. 1971, Vol. 1, pp 143-186.](https://www.stat.berkeley.edu/~aldous/157/Papers/Schelling_Seg_Models.pdf) - -An interactive, browser-based explanation and implementation: - -[Parable of the Polygons](http://ncase.me/polygons/), by Vi Hart and Nicky Case. diff --git a/examples/basic/schelling/__init__.py b/examples/basic/schelling/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/examples/basic/schelling/analysis.ipynb b/examples/basic/schelling/analysis.ipynb deleted file mode 100644 index 50f382c66a0..00000000000 --- a/examples/basic/schelling/analysis.ipynb +++ /dev/null @@ -1,457 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Schelling Segregation Model\n", - "\n", - "## Background\n", - "\n", - "The Schelling (1971) segregation model is a classic of agent-based modeling, demonstrating how agents following simple rules lead to the emergence of qualitatively different macro-level outcomes. Agents are randomly placed on a grid. There are two types of agents, one constituting the majority and the other the minority. All agents want a certain number (generally, 3) of their 8 surrounding neighbors to be of the same type in order for them to be happy. Unhappy agents will move to a random available grid space. While individual agents do not have a preference for a segregated outcome (e.g. they would be happy with 3 similar neighbors and 5 different ones), the aggregate outcome is nevertheless heavily segregated.\n", - "\n", - "## Implementation\n", - "\n", - "This is a demonstration of running a Mesa model in an IPython Notebook. The actual model and agent code are implemented in Schelling.py, in the same directory as this notebook. Below, we will import the model class, instantiate it, run it, and plot the time series of the number of happy agents." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "%matplotlib inline\n", - "\n", - "from model import Schelling" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we instantiate a model instance: a 10x10 grid, with an 80% change of an agent being placed in each cell, approximately 20% of agents set as minorities, and agents wanting at least 3 similar neighbors." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "model = Schelling(10, 10, 0.8, 0.2, 3)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We want to run the model until all the agents are happy with where they are. However, there's no guarantee that a given model instantiation will *ever* settle down. So let's run it for either 100 steps or until it stops on its own, whichever comes first:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "100\n" - ] - } - ], - "source": [ - "while model.running and model.schedule.steps < 100:\n", - " model.step()\n", - "print(model.schedule.steps) # Show how many steps have actually run" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The model has a DataCollector object, which checks and stores how many agents are happy at the end of each step. It can also generate a pandas DataFrame of the data it has collected:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "model_out = model.datacollector.get_model_vars_dataframe()" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
happy
00
173
267
372
472
\n", - "
" - ], - "text/plain": [ - " happy\n", - "0 0\n", - "1 73\n", - "2 72\n", - "3 73\n", - "4 72" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "model_out.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally, we can plot the 'happy' series:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXQAAAD8CAYAAABn919SAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvIxREBQAAGw5JREFUeJzt3XuYFPWd7/H3dy5cRS4yIIIROCHew8U2IRrjUXRXoxGexHjJhUkeIm7WE43mbI5J9niyuznPak6MutmsCUrMaAyiRBfW+OgSvGTNBR0EFUWDIgKCMCIjCALTVd/zR9fAwHR1NzPTM/yaz+t5eLqrupr5VlfPZ379raouc3dERCR8VT1dgIiIdA0FuohIhVCgi4hUCAW6iEiFUKCLiFQIBbqISIVQoIuIVAgFuohIhVCgi4hUiJru/GFDhw710aNHd+ePFBEJ3pIlS95x97piy3VroI8ePZrGxsbu/JEiIsEzszdLWa6klouZXWtmL5nZcjObY2Z9zGyMmS02s5VmNtfMenWuZBER6YyigW5mI4GrgYy7nwRUA5cBNwG3uPs4YAswo5yFiohIYaXuFK0B+ppZDdAP2ACcDcxLHm8ApnV9eSIiUqqige7ubwE/AtaQC/L3gCVAs7tnk8XWASPzPd/MZppZo5k1NjU1dU3VIiLSTiktl8HAVGAMcBTQHzg/z6J5v1jd3We5e8bdM3V1RXfSiohIB5XScjkHeMPdm9y9BXgQOA0YlLRgAEYB68tUo4iIlKCUQF8DTDazfmZmwBTgZeAJ4OJkmXpgfnlKFBGRUhQ9Dt3dF5vZPOA5IAssBWYBvwXuM7MfJPNml7PQSufuPPbS24wZehjHHjmgp8uRg9TvXt7IC+uacxNmXDR+BB8epveL5Fh3XlM0k8m4TixqryWKuWH+S8x5Zg1HD+nLwmvPpE9tdU+XJQeZ59c2M+3f/oA7mIE7jBzUl99ddyZ9e+n9UsnMbIm7Z4otp+9y6WFbtu9m+uxnmPPMGi786AjWvvsBtz/5ek+XJQeZKHb+9/zlDD2sNy9+/694458vYO7MybzV/AG3P/laT5cnB4luPfX/UPX82mauvm8pG5p3tnsscqfajB9fMp7PThpFlS3l9qde57OTRnLMEf3LXlscOzcvfJVfPL2aKM59WhvSvxc3XzKe0z88tOw/v5g1m3dw1a+fY8zQ/tz4uZPp1yv3lt20dSffmLOUfr2qufXSiQzsVwvAeztauO7+ZWzbmeUnX5jI8MP79GT5XWbus2t5Yd173HrpBAb0ya3rx8cewbQJR/Gzp1bx2UmjGD00/f3y3o4WvvXAMt77oIV//cKkvK/LrmzEDf/+Es+va+Ynl09k3PDSWznuzuyn3+CuP6zmH6eeyJTjhx/4SgagJYr5wcMv84fXN3PbZRM48aiB7ZZxd376xGvc9+xa/vmzJ3PGuO47uk8tlzL7j+fX8z8feJ66Ab258KNHYbbv4wacd9KRfHTUIAA2bt3J2T96ksljj2D2V04ta23bd2X55txlLHx5I58++cg9f0AWrdjI603b+f5FJ/LlyceUtYZC/rxqM1//1RKykbN9d5bjRxzOHdMzvLt9N1fc3Ujzjhai2Bk5uC931mcw4GsNjazdsoPa6ioG9KnhzumncvKo9r90IdmyfTdn3fwkHxk+gLkzJ2Nt3kSbtu7k7JufIjN6MHd95dR9Hmv1xjvbmfHLZ/d5Xe6YntnzngN45/1dXHnPEpa8uYXD+9TgDv/yhYmcdeywovXtykb8/UPLeWDJOgb2rWXrzha+c/5xXHHG2Lz1hKp5x27+9t7n+OPrmxnYt5bd2ZhbLp3AeScduWeZnS0R3573AgueX8/AvrW8vyvLDReewPRPHNOp16LUlosCHfhgd8RDS9/ig5aoS//fNzdv5+4/vcnHRg/h9i9N4ojDepf0vDt+v4r/+8gKrjxzLMMGlGeE6e7MW7KOv2zcxv/5zIn7vOG27WzhmvuW8fgrm/jcpFGccNThZamhkHe37+LnT63imCP6Mbv+VN54ZzvfmLOUPrVVvL8ry5B+vbijPsMHuyOuvGcJu6MYA2qqq/jZl05hQJ8avtbQyObtu/ibM//bnlFtiP742js8+ZcmHrn6jLw7zO/8r1X84LcruPJTYxm238h7Vzbi50+torrKuP2Lkzi8b22718XduesPq9m8fRc/vmQC448exBUNjbzy9la+dsbYop9yHl2+gWdXb+HqKeP4mzPH8ncPvMBvX9zAZ8YfxYSjBxV8bijcnV/9+U3WN+/kxs+dzCc/PJSZ9yxh2dpmvnr6aEYN7gfAgufX88K6Zv7ur4/ly5OP4dq5y/jdik188eMf4vsXnUhtdce63Ar0A/CdB19kzjNryvJ/X5o5mn+adhK9akrfkC1RzCU//xNL1zSXpaZWA/vW8pPLJ/Kpj7T/SBjFzk2PvsId/7WKbnyL7OOsY+u47fKJHJ6E8cqN27ji7kaGHtab2790CnUDcn8g123Zwcy7lxC7c8f0DEcPyf1yvfP+Lv72V8/xzOp3e2YFutDVU8Zx3bkfyftYSxRz6c//xHMp75fjjhzQ/nW59zmeeWPv63LUwD7Mmp7hpJG5TzM7dmf3BHMxfWur+eHFH+Uz448Ccm282xat5CePryTuofdOOdQN6M3PvnQKpxwzGMiNxr/74Is8uPStPcsc1ruGmy8Zz1+fmBu1R7Hz/x57ldlPr+I3Xz9tn09FB0KBXqLWIwe+ctpovnlO/l+YjqquMg7r3bHdFFHsvL8rW3zBTuhbW130D832XVmyPfBbacaeIG8rip0qo93H1zipsapq3/nuztad5X0dy63KKPoJo9D7ZUDvmqKvS/9e1dTkGT1u29lSNJR711TlPSprx+4sLVHlJHq/XtV5R9jv78ru2f/Up7aK3jXtX4vV72wvuI+jmFID/ZDeKdr2yIHrzv3IQfWxvLrKGNi35+vp38E/SOVSXZW/D7l/YLUyOzhex3I70PdLqa9LZ34nWndgV7pSBm2dCfMDcUgftth65MDfX3D8QRXmIiIdccgG+pbtu/nhY6/w8TFDuCjp/YmIhOyQDfQfPvYq23Zm+cepJ1XUoVUicug6JAN92dpm7nt2DV89bbS+N0VEKkZwgf7rxWu4du4ytu1s2TMvjp1/fXwlV97TyDvv7yr4/Ch2bpi/nLrDenPNOePKXa6ISLcJbjf07//SxKMvvc3yt95jdv2p1A3ozbceWMYjL75NlcHyt7Yy+ysZjjsy/8kw9z27hhfWvcdtl03QjlARqSjBBXo2dob078WmbbuY+tOnOXJgX155eyvf+/TxfHzsEK64u5HP/dsf+e4FxzN8v7Mss7Hzw0df1Y5QEalIAQZ6zKjBfbntsonMaHiWNZu3c+f0zJ4vA5p/1SeZeU8j33toed7n96qp0o5QEalIwQV6FDvVVcaYof155Ooz2LE7Ykj/XnseP3JgH37z9dN49e1teU9ZH35473bfdyEiUgmKBrqZHQvMbTNrLHADcHcyfzSwGrjE3bd0fYn7ykZOTXJWYJ/a6rynHNdWV+35TgoRkUNF0aNc3P1Vd5/g7hOAU4AdwEPA9cAidx8HLEqmy651hC4iIvs60MMWpwCvu/ubwFSgIZnfAEzrysLSZOOYmqrgjrYUESm7A03Gy4A5yf3h7r4BILkt/k34XSAbOzXVGqGLiOyv5EA3s17ARcADB/IDzGymmTWaWWNTU9OB1tdO2x66iIjsdSAj9POB59x9YzK90cxGACS3m/I9yd1nuXvG3TN1dZ2/tp566CIi+R1IoF/O3nYLwAKgPrlfD8zvqqIKUQ9dRCS/kpLRzPoB5wIPtpl9I3Cuma1MHrux68trL1IPXUQkr5JOLHL3HcAR+83bTO6ol27VEqnlIiKST3C9iyjWTlERkXyCC/Rs7FSrhy4i0k5wyRjFsUboIiJ5BBfoOrFIRCS/8AJdJxaJiOQVXKBH6qGLiOQVXDJm1UMXEckrqECPYyd2dBy6iEgeQQV6Ns5dgqhWO0VFRNoJKtCjJNDVQxcRaS+oZMzGMYB66CIieQQV6HtH6Ap0EZH9BRXo6qGLiKQLK9Aj9dBFRNIElYzqoYuIpAsq0NVDFxFJV+oViwaZ2Twze8XMVpjZJ8xsiJktNLOVye3gchfb2kPXl3OJiLRX6gj9NuBRdz8OGA+sAK4HFrn7OGBRMl1WrSN0XVNURKS9osloZocDnwJmA7j7bndvBqYCDcliDcC0chXZqiXK9dDVchERaa+Uoe5YoAm4y8yWmtmdZtYfGO7uGwCS22FlrBNoO0JXoIuI7K+UQK8BJgG3u/tEYDsH0F4xs5lm1mhmjU1NTR0sM6e1h16tHrqISDulBPo6YJ27L06m55EL+I1mNgIgud2U78nuPsvdM+6eqaur61SxGqGLiKQrGuju/jaw1syOTWZNAV4GFgD1ybx6YH5ZKmyj9cQi7RQVEWmvpsTlvgHca2a9gFXAV8n9MbjfzGYAa4DPl6fEvfacWKSWi4hIOyUFursvAzJ5HprSteUUltWJRSIiqYLqXUSReugiImmCCvSsTiwSEUkVVDKqhy4iki6oQNeXc4mIpAsq0LPqoYuIpAoq0DVCFxFJF1Sg770EXVBli4h0i6CSsXWnqEboIiLthRXo6qGLiKQKKtDVQxcRSRdUoOvEIhGRdEElY6QTi0REUgUV6C1JD73aFOgiIvsLKtCj2KkyqFIPXUSknaACPRu7+uciIimCSscojtU/FxFJEVSgZ2PXIYsiIilKumKRma0GtgERkHX3jJkNAeYCo4HVwCXuvqU8ZeZkI9dJRSIiKQ5khH6Wu09w99ZL0V0PLHL3ccCiZLqsciP0oD5UiIh0m86k41SgIbnfAEzrfDmFRXGsEbqISIpSA92B/zSzJWY2M5k33N03ACS3w/I90cxmmlmjmTU2NTV1qths7NopKiKSoqQeOnC6u683s2HAQjN7pdQf4O6zgFkAmUzGO1DjHuqhi4ikK2mE7u7rk9tNwEPAx4CNZjYCILndVK4iW0U6ykVEJFXRQDez/mY2oPU+8FfAcmABUJ8sVg/ML1eRrbJxrBOLRERSlNJyGQ48ZLnvT6kBfu3uj5rZs8D9ZjYDWAN8vnxl5miELiKSrmigu/sqYHye+ZuBKeUoKk02dmq1U1REJK+g+hfZSCN0EZE0YQW6eugiIqmCSkf10EVE0gUV6DqxSEQkXVCBHsU6sUhEJE1Qgd4S6cu5RETSBJWO+nIuEZF0QQV6Nnaq1UMXEckrqECPYqdWI3QRkbyCCvSseugiIqmCSseseugiIqmCCvRIPXQRkVRBBXpWx6GLiKQKKtCjyPVdLiIiKYJKR536LyKSLrBAj/XlXCIiKUoOdDOrNrOlZvZwMj3GzBab2Uozm2tmvcpXZo566CIi6Q5khH4NsKLN9E3ALe4+DtgCzOjKwvYXx447GqGLiKQoKdDNbBRwAXBnMm3A2cC8ZJEGYFo5CmyVjR2A2uqgukQiIt2m1HS8Ffg2ECfTRwDN7p5NptcBI7u4tn1k49yP1ghdRCS/ooFuZhcCm9x9SdvZeRb1lOfPNLNGM2tsamrqYJl7R+jqoYuI5FfKCP104CIzWw3cR67VciswyMxqkmVGAevzPdndZ7l7xt0zdXV1HS40inKBrhG6iEh+RQPd3b/j7qPcfTRwGfC4u38ReAK4OFmsHphftippM0JXD11EJK/OpOP/Aq4zs9fI9dRnd01J+UVquYiIFFRTfJG93P1J4Mnk/irgY11fUn4tkXaKiogUEkz/QiN0EZHCggn01h66RugiIvkFE+iRTiwSESkomHTUiUUiIoWFE+iReugiIoWEE+jqoYuIFBRMoO89yiWYkkVEulUw6djaQ9cVi0RE8gsm0HUcuohIYcEEelZfziUiUlA4ga4euohIQcGkY6QeuohIQcEEui5wISJSWDiBrh66iEhB4QS6eugiIgUFk46tPfRq9dBFRPIq5SLRfczsGTN73sxeMrN/SOaPMbPFZrbSzOaaWa9yFto6Qq9Vy0VEJK9SRui7gLPdfTwwATjPzCYDNwG3uPs4YAswo3xl7j2xSD10EZH8SrlItLv7+8lkbfLPgbOBecn8BmBaWSpMtETqoYuIFFJSOppZtZktAzYBC4HXgWZ3zyaLrANGlqfEHPXQRUQKKynQ3T1y9wnAKHIXhj4+32L5nmtmM82s0cwam5qaOlyojkMXESnsgPoX7t4MPAlMBgaZWU3y0ChgfcpzZrl7xt0zdXV1HS400gUuREQKKuUolzozG5Tc7wucA6wAngAuTharB+aXq0jQBS5ERIqpKb4II4AGM6sm9wfgfnd/2MxeBu4zsx8AS4HZZayTbBxTXWWYKdBFRPIpGuju/gIwMc/8VeT66d0iG7tG5yIiBQRzDGAUuU4qEhEpIJhA1whdRKSwYAI9ip2a6mDKFRHpdsEkZOtOURERyS+cQI9cx6CLiBQQTKDnWi4KdBGRNMEEejZ2fTGXiEgBwSSkeugiIoWFE+jqoYuIFBRMoEc6Dl1EpKBgAj2r49BFRAoKJiGjWC0XEZFCggn0lkg7RUVECgkm0DVCFxEpLJhA15dziYgUFkygR7FTq52iIiKpgklIjdBFRAor5ZqiR5vZE2a2wsxeMrNrkvlDzGyhma1MbgeXs9BsFKuHLiJSQCkj9CzwLXc/HpgMXGVmJwDXA4vcfRywKJkuG51YJCJSWNFAd/cN7v5ccn8bsAIYCUwFGpLFGoBp5SoSci0X9dBFRNIdUEKa2WhyF4xeDAx39w2QC31gWMpzZppZo5k1NjU1dbhQjdBFRAorOdDN7DDgN8A33X1rqc9z91nunnH3TF1dXUdqBHLftqgeuohIupIC3cxqyYX5ve7+YDJ7o5mNSB4fAWwqT4k52UgjdBGRQko5ysWA2cAKd/9xm4cWAPXJ/XpgfteXt1dWZ4qKiBRUU8IypwNfBl40s2XJvO8CNwL3m9kMYA3w+fKUmBPp2xZFRAoqGuju/jSQNjSe0rXlpFMPXUSksGCGvOqhi4gUFkSgu7t66CIiRQQR6LHnbqurgihXRKRHBJGQ2TgGoKZaI3QRkTRBBHqUDNHVchERSRdEoLdEuUDXTlERkXRBBLpG6CIixQUR6Ht76EGUKyLSI4JISI3QRUSKCyLQs+qhi4gUFUagt47QddiiiEiqIAI9SnroOrFIRCRdEAnZOkKvVctFRCRVGIGuHrqISFFBBHqkHrqISFFBBHpWPXQRkaJKuQTdL8xsk5ktbzNviJktNLOVye3gchbZ2nLRcegiIulKGfL+Ejhvv3nXA4vcfRywKJkuG51YJCJSXNFAd/ffA+/uN3sq0JDcbwCmdXFd+9Bx6CIixXW0KT3c3TcAJLfDuq6k9tRDFxEpruwJaWYzzazRzBqbmpo69H+ohy4iUlxHA32jmY0ASG43pS3o7rPcPePumbq6ug79MB22KCJSXEcDfQFQn9yvB+Z3TTn5ZbVTVESkqFIOW5wD/Ak41szWmdkM4EbgXDNbCZybTJdN6whdPXQRkXQ1xRZw98tTHprSxbWkaomSC1xohC4ikiqIIe/eEboCXUQkTRCBruPQRUSKCyLQ954pGkS5IiI9IoiEzKrlIiJSVBiBrp2iIiJFhRHoGqGLiBQVRKC39tBrq4MoV0SkRwSRkK0jdA3QRUTSBRHoURxTU2WYKdFFRNIEEejZyNU/FxEpIoxAj139cxGRIoJIySjWCF1EpJggAj2b9NBFRCRdEIGuEbqISHFBBHpL5Bqhi4gUEUSgR7FTo52iIiIFdSolzew8M3vVzF4zs+u7qqj9ZWON0EVEiulwoJtZNfBT4HzgBOByMzuhqwprK4pj9dBFRIrozAj9Y8Br7r7K3XcD9wFTu6asfbXoxCIRkaI6E+gjgbVtptcl87pcroeuQBcRKaToRaILyJew3m4hs5nATIAPfehDHfpBpxwzmG07sx16rojIoaIzgb4OOLrN9Chg/f4LufssYBZAJpNpF/iluOqsD3fkaSIih5TOtFyeBcaZ2Rgz6wVcBizomrJERORAdXiE7u5ZM/sfwGNANfALd3+pyyoTEZED0pmWC+7+CPBIF9UiIiKdoNMvRUQqhAJdRKRCKNBFRCqEAl1EpEIo0EVEKoS5d+hcn479MLMm4M0OPn0o8E4XlhMCrfOhQetc+Tq7vse4e12xhbo10DvDzBrdPdPTdXQnrfOhQetc+bprfdVyERGpEAp0EZEKEVKgz+rpAnqA1vnQoHWufN2yvsH00EVEpLCQRugiIlJAEIHeXRej7ilmdrSZPWFmK8zsJTO7Jpk/xMwWmtnK5HZwT9fa1cys2syWmtnDyfQYM1ucrPPc5KuZK4aZDTKzeWb2SrK9P1Hp29nMrk3e18vNbI6Z9am07WxmvzCzTWa2vM28vNvVcv4lybMXzGxSV9Vx0Ad6d16MugdlgW+5+/HAZOCqZB2vBxa5+zhgUTJdaa4BVrSZvgm4JVnnLcCMHqmqfG4DHnX344Dx5Na9YrezmY0ErgYy7n4Sua/avozK286/BM7bb17adj0fGJf8mwnc3lVFHPSBTjdejLqnuPsGd38uub+N3C/5SHLr2ZAs1gBM65kKy8PMRgEXAHcm0wacDcxLFqmodTazw4FPAbMB3H23uzdT4duZ3Nd09zWzGqAfsIEK287u/nvg3f1mp23XqcDdnvNnYJCZjeiKOkII9G67GPXBwMxGAxOBxcBwd98AudAHhvVcZWVxK/BtIE6mjwCa3b31ArKVtq3HAk3AXUmb6U4z608Fb2d3fwv4EbCGXJC/Byyhsrdzq7TtWrZMCyHQS7oYdSUws8OA3wDfdPetPV1POZnZhcAmd1/SdnaeRStpW9cAk4Db3X0isJ0Kaq/kk/SNpwJjgKOA/uRaDvurpO1cTNne5yEEekkXow6dmdWSC/N73f3BZPbG1o9iye2mnqqvDE4HLjKz1eTaaGeTG7EPSj6aQ+Vt63XAOndfnEzPIxfwlbydzwHecPcmd28BHgROo7K3c6u07Vq2TAsh0Cv+YtRJ73g2sMLdf9zmoQVAfXK/Hpjf3bWVi7t/x91Huftoctv0cXf/IvAEcHGyWKWt89vAWjM7Npk1BXiZCt7O5Fotk82sX/I+b13nit3ObaRt1wXA9ORol8nAe62tmU5z94P+H/Bp4C/A68D3erqeMqzfJ8l95HoBWJb8+zS5nvIiYGVyO6Snay3T+v934OHk/ljgGeA14AGgd0/X18XrOgFoTLb1vwODK307A/8AvAIsB+4BelfadgbmkNtH0EJuBD4jbbuSa7n8NMmzF8kdAdQldehMURGRChFCy0VEREqgQBcRqRAKdBGRCqFAFxGpEAp0EZEKoUAXEakQCnQRkQqhQBcRqRD/H51mCpU1j1b7AAAAAElFTkSuQmCC\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "model_out.happy.plot()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For testing purposes, here is a table giving each agent's x and y values at each step." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "x_positions = model.datacollector.get_agent_vars_dataframe()" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
xy
StepAgentID
0(0, 0)01
(0, 1)89
(0, 2)52
(0, 3)00
(0, 4)17
\n", - "
" - ], - "text/plain": [ - " x y\n", - "Step AgentID \n", - "0 (0, 0) 0 1\n", - " (0, 1) 8 9\n", - " (0, 2) 5 2\n", - " (0, 3) 0 0\n", - " (0, 4) 1 7" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "x_positions.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Effect of Homophily on segregation\n", - "\n", - "Now, we can do a parameter sweep to see how segregation changes with homophily.\n", - "\n", - "First, we create a function which takes a model instance and returns what fraction of agents are segregated -- that is, have no neighbors of the opposite type." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "from mesa.batchrunner import BatchRunner" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "def get_segregation(model):\n", - " \"\"\"\n", - " Find the % of agents that only have neighbors of their same type.\n", - " \"\"\"\n", - " segregated_agents = 0\n", - " for agent in model.schedule.agents:\n", - " segregated = True\n", - " for neighbor in model.grid.iter_neighbors(agent.pos, True):\n", - " if neighbor.type != agent.type:\n", - " segregated = False\n", - " break\n", - " if segregated:\n", - " segregated_agents += 1\n", - " return segregated_agents / model.schedule.get_agent_count()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we set up the batch run, with a dictionary of fixed and changing parameters. Let's hold everything fixed except for Homophily." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "fixed_params = {\"height\": 10, \"width\": 10, \"density\": 0.8, \"minority_pc\": 0.2}\n", - "variable_parms = {\"homophily\": range(1, 9)}" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "model_reporters = {\"Segregated_Agents\": get_segregation}" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "param_sweep = BatchRunner(\n", - " Schelling,\n", - " variable_parameters=variable_parms,\n", - " fixed_parameters=fixed_params,\n", - " iterations=10,\n", - " max_steps=200,\n", - " model_reporters=model_reporters,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "80it [00:15, 3.13it/s]\n" - ] - } - ], - "source": [ - "param_sweep.run_all()" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "df = param_sweep.get_model_vars_dataframe()" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "plt.scatter(df.homophily, df.Segregated_Agents)\n", - "plt.grid(True)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.9" - }, - "widgets": { - "state": {}, - "version": "1.1.2" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/examples/basic/schelling/app.py b/examples/basic/schelling/app.py deleted file mode 100644 index 9db977a723c..00000000000 --- a/examples/basic/schelling/app.py +++ /dev/null @@ -1,53 +0,0 @@ -from mesa_models.experimental import JupyterViz -from model import Schelling - - -def get_happy_agents(model): - """ - Display a text count of how many happy agents there are. - """ - return f"Happy agents: {model.happy}" - - -def agent_portrayal(agent): - color = "tab:orange" if agent.type == 0 else "tab:blue" - return {"color": color} - - -model_params = { - "density": { - "type": "SliderFloat", - "value": 0.8, - "label": "Agent density", - "min": 0.1, - "max": 1.0, - "step": 0.1, - }, - "minority_pc": { - "type": "SliderFloat", - "value": 0.2, - "label": "Fraction minority", - "min": 0.0, - "max": 1.0, - "step": 0.05, - }, - "homophily": { - "type": "SliderInt", - "value": 3, - "label": "Homophily", - "min": 0, - "max": 8, - "step": 1, - }, - "width": 20, - "height": 20, -} - -page = JupyterViz( - Schelling, - model_params, - measures=["happy", get_happy_agents], - name="Schelling", - agent_portrayal=agent_portrayal, -) -page # noqa diff --git a/examples/basic/schelling/model.py b/examples/basic/schelling/model.py deleted file mode 100644 index ccc5699e9c4..00000000000 --- a/examples/basic/schelling/model.py +++ /dev/null @@ -1,86 +0,0 @@ -import mesa - - -class SchellingAgent(mesa.Agent): - """ - Schelling segregation agent - """ - - def __init__(self, pos, model, agent_type): - """ - Create a new Schelling agent. - - Args: - unique_id: Unique identifier for the agent. - x, y: Agent initial location. - agent_type: Indicator for the agent's type (minority=1, majority=0) - """ - super().__init__(pos, model) - self.pos = pos - self.type = agent_type - - def step(self): - similar = 0 - for neighbor in self.model.grid.iter_neighbors(self.pos, True): - if neighbor.type == self.type: - similar += 1 - - # If unhappy, move: - if similar < self.model.homophily: - self.model.grid.move_to_empty(self) - else: - self.model.happy += 1 - - -class Schelling(mesa.Model): - """ - Model class for the Schelling segregation model. - """ - - def __init__(self, width=20, height=20, density=0.8, minority_pc=0.2, homophily=3): - """ """ - - self.width = width - self.height = height - self.density = density - self.minority_pc = minority_pc - self.homophily = homophily - - self.schedule = mesa.time.RandomActivation(self) - self.grid = mesa.space.SingleGrid(width, height, torus=True) - - self.happy = 0 - self.datacollector = mesa.DataCollector( - {"happy": "happy"}, # Model-level count of happy agents - # For testing purposes, agent's individual x and y - {"x": lambda a: a.pos[0], "y": lambda a: a.pos[1]}, - ) - - # Set up agents - # We use a grid iterator that returns - # the coordinates of a cell as well as - # its contents. (coord_iter) - for cell in self.grid.coord_iter(): - x = cell[1] - y = cell[2] - if self.random.random() < self.density: - agent_type = 1 if self.random.random() < self.minority_pc else 0 - - agent = SchellingAgent((x, y), self, agent_type) - self.grid.place_agent(agent, (x, y)) - self.schedule.add(agent) - - self.running = True - self.datacollector.collect(self) - - def step(self): - """ - Run one step of the model. If All agents are happy, halt the model. - """ - self.happy = 0 # Reset counter of happy agents - self.schedule.step() - # collect data - self.datacollector.collect(self) - - if self.happy == self.schedule.get_agent_count(): - self.running = False diff --git a/examples/basic/schelling/requirements.txt b/examples/basic/schelling/requirements.txt deleted file mode 100644 index 2d40f057b50..00000000000 --- a/examples/basic/schelling/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -jupyter -matplotlib -mesa~=1.1 -solara -git+https://github.com/projectmesa/mesa-examples diff --git a/examples/basic/schelling/run_ascii.py b/examples/basic/schelling/run_ascii.py deleted file mode 100644 index 460fabbb746..00000000000 --- a/examples/basic/schelling/run_ascii.py +++ /dev/null @@ -1,48 +0,0 @@ -import mesa -from model import Schelling - - -class SchellingTextVisualization(mesa.visualization.TextVisualization): - """ - ASCII visualization for schelling model - """ - - def __init__(self, model): - """ - Create new Schelling ASCII visualization. - """ - self.model = model - - grid_viz = mesa.visualization.TextGrid(self.model.grid, self.print_ascii_agent) - happy_viz = mesa.visualization.TextData(self.model, "happy") - self.elements = [grid_viz, happy_viz] - - @staticmethod - def print_ascii_agent(a): - """ - Minority agents are X, Majority are O. - """ - if a.type == 0: - return "O" - if a.type == 1: - return "X" - - -if __name__ == "__main__": - model_params = { - "height": 20, - "width": 20, - # Agent density, from 0.8 to 1.0 - "density": 0.8, - # Fraction minority, from 0.2 to 1.0 - "minority_pc": 0.2, - # Homophily, from 3 to 8 - "homophily": 3, - } - - model = Schelling(**model_params) - viz = SchellingTextVisualization(model) - for i in range(10): - print("Step:", i) - viz.step() - print("---") From 90e2caa19355904a9e08d6bee28a08b2a7f4e64a Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 29 Jun 2023 06:43:15 -0400 Subject: [PATCH 041/116] Add back original boltzmann and schelling --- .../basic/boltzmann_wealth_model/Readme.md | 51 ++ examples/basic/boltzmann_wealth_model/app.py | 113 +++++ .../boltzmann_wealth_model/__init__.py | 0 .../boltzmann_wealth_model/model.py | 76 +++ .../boltzmann_wealth_model/server.py | 40 ++ .../boltzmann_wealth_model/requirements.txt | 1 + examples/basic/boltzmann_wealth_model/run.py | 3 + examples/basic/schelling/README.md | 49 ++ examples/basic/schelling/__init__.py | 0 examples/basic/schelling/analysis.ipynb | 457 ++++++++++++++++++ examples/basic/schelling/model.py | 86 ++++ examples/basic/schelling/requirements.txt | 3 + examples/basic/schelling/run.py | 3 + examples/basic/schelling/run_ascii.py | 48 ++ examples/basic/schelling/server.py | 45 ++ 15 files changed, 975 insertions(+) create mode 100644 examples/basic/boltzmann_wealth_model/Readme.md create mode 100644 examples/basic/boltzmann_wealth_model/app.py create mode 100644 examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py create mode 100644 examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py create mode 100644 examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/server.py create mode 100644 examples/basic/boltzmann_wealth_model/requirements.txt create mode 100644 examples/basic/boltzmann_wealth_model/run.py create mode 100644 examples/basic/schelling/README.md create mode 100644 examples/basic/schelling/__init__.py create mode 100644 examples/basic/schelling/analysis.ipynb create mode 100644 examples/basic/schelling/model.py create mode 100644 examples/basic/schelling/requirements.txt create mode 100644 examples/basic/schelling/run.py create mode 100644 examples/basic/schelling/run_ascii.py create mode 100644 examples/basic/schelling/server.py diff --git a/examples/basic/boltzmann_wealth_model/Readme.md b/examples/basic/boltzmann_wealth_model/Readme.md new file mode 100644 index 00000000000..4a6e21f142a --- /dev/null +++ b/examples/basic/boltzmann_wealth_model/Readme.md @@ -0,0 +1,51 @@ +# Boltzmann Wealth Model (Tutorial) + +## Summary + +A simple model of agents exchanging wealth. All agents start with the same amount of money. Every step, each agent with one unit of money or more gives one unit of wealth to another random agent. This is the model described in the [Intro Tutorial](https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html), with the completed code. + +If you want to go over the step-by-step tutorial, please go and run the [Jupyter Notebook](https://github.com/projectmesa/mesa/blob/main/docs/tutorials/intro_tutorial.ipynb). The code here runs the finalized code in the last cells directly. + +As the model runs, the distribution of wealth among agents goes from being perfectly uniform (all agents have the same starting wealth), to highly skewed -- a small number have high wealth, more have none at all. + +## How to Run + +To follow the tutorial example, launch the Jupyter Notebook and run the code in ``Introduction to Mesa Tutorial Code.ipynb`` which you can find in the main mesa repo [here](https://github.com/projectmesa/mesa/blob/main/docs/tutorials/intro_tutorial.ipynb) + +To launch the interactive server, as described in the [last section of the tutorial](https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html#adding-visualization), run: + +``` + $ python server.py +``` + +Make sure to install the requirements first: + +``` + pip install -r requirements.txt +``` + +If your browser doesn't open automatically, point it to [http://127.0.0.1:8521/](http://127.0.0.1:8521/). When the visualization loads, press Reset, then Run. + + +## Files + +* ``boltzmann_wealth_model/model.py``: Final version of the model. +* ``boltzmann_wealth_model/server.py``: Code for the interactive visualization. +* ``run.py``: Launches the server. + +## Optional + +* ``boltzmann_wealth_model/app.py``: can be used to run the simulation via the streamlit interface. +* For this some additional packages like ``streamlit`` and ``altair`` needs to be installed. +* Once installed, the app can be opened in the browser using : ``streamlit run app.py`` + +## Further Reading + +The full tutorial describing how the model is built can be found at: +https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html + +This model is drawn from econophysics and presents a statistical mechanics approach to wealth distribution. Some examples of further reading on the topic can be found at: + +[Milakovic, M. A Statistical Equilibrium Model of Wealth Distribution. February, 2001.](https://editorialexpress.com/cgi-bin/conference/download.cgi?db_name=SCE2001&paper_id=214) + +[Dragulescu, A and Yakovenko, V. Statistical Mechanics of Money, Income, and Wealth: A Short Survey. November, 2002](http://arxiv.org/pdf/cond-mat/0211175v1.pdf) diff --git a/examples/basic/boltzmann_wealth_model/app.py b/examples/basic/boltzmann_wealth_model/app.py new file mode 100644 index 00000000000..f2dd6da91ab --- /dev/null +++ b/examples/basic/boltzmann_wealth_model/app.py @@ -0,0 +1,113 @@ +import time + +import altair as alt +import pandas as pd +import streamlit as st +from boltzmann_wealth_model.model import BoltzmannWealthModel + +model = st.title("Boltzman Wealth Model") +num_agents = st.slider( + "Choose how many agents to include in the model", + min_value=1, + max_value=100, + value=50, +) +num_ticks = st.slider( + "Select number of Simulation Runs", min_value=1, max_value=100, value=50 +) +height = st.slider("Select Grid Height", min_value=10, max_value=100, step=10, value=15) +width = st.slider("Select Grid Width", min_value=10, max_value=100, step=10, value=20) +model = BoltzmannWealthModel(num_agents, height, width) + + +status_text = st.empty() +run = st.button("Run Simulation") + + +if run: + tick = time.time() + step = 0 + # init grid + df_grid = pd.DataFrame() + df_gini = pd.DataFrame({"step": [0], "gini": [-1]}) + for x in range(width): + for y in range(height): + df_grid = pd.concat( + [df_grid, pd.DataFrame({"x": [x], "y": [y], "agent_count": 0})], + ignore_index=True, + ) + + heatmap = ( + alt.Chart(df_grid) + .mark_point(size=100) + .encode(x="x", y="y", color=alt.Color("agent_count")) + .interactive() + .properties(width=800, height=600) + ) + + line = ( + alt.Chart(df_gini) + .mark_line(point=True) + .encode(x="step", y="gini") + .properties(width=800, height=600) + ) + + # init progress bar + my_bar = st.progress(0, text="Simulation Progress") # progress + placeholder = st.empty() + st.subheader("Agent Grid") + chart = st.altair_chart(heatmap) + st.subheader("Gini Values") + line_chart = st.altair_chart(line) + + color_scale = alt.Scale( + domain=[0, 1, 2, 3, 4], range=["red", "cyan", "white", "white", "blue"] + ) + for i in range(num_ticks): + model.step() + my_bar.progress((i / num_ticks), text="Simulation progress") + placeholder.text("Step = %d" % i) + for cell in model.grid.coord_iter(): + cell_content, x, y = cell + agent_count = len(cell_content) + selected_row = df_grid[(df_grid["x"] == x) & (df_grid["y"] == y)] + df_grid.loc[ + selected_row.index, "agent_count" + ] = agent_count # random.choice([1,2]) + + df_gini = pd.concat( + [ + df_gini, + pd.DataFrame( + {"step": [i], "gini": [model.datacollector.model_vars["Gini"][i]]} + ), + ] + ) + # st.table(df_grid) + heatmap = ( + alt.Chart(df_grid) + .mark_circle(size=100) + .encode(x="x", y="y", color=alt.Color("agent_count", scale=color_scale)) + .interactive() + .properties(width=800, height=600) + ) + chart.altair_chart(heatmap) + + line = ( + alt.Chart(df_gini) + .mark_line(point=True) + .encode(x="step", y="gini") + .properties(width=800, height=600) + ) + line_chart.altair_chart(line) + + time.sleep(0.01) + + tock = time.time() + st.success(f"Simulation completed in {tock - tick:.2f} secs") + + # st.subheader('Agent Grid') + # fig = px.imshow(agent_counts,labels={'color':'Agent Count'}) + # st.plotly_chart(fig) + # st.subheader('Gini value over sim ticks (Plotly)') + # chart = st.line_chart(model.datacollector.model_vars['Gini']) diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py new file mode 100644 index 00000000000..0f61b8838d6 --- /dev/null +++ b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -0,0 +1,76 @@ +import mesa + + +def compute_gini(model): + agent_wealths = [agent.wealth for agent in model.schedule.agents] + x = sorted(agent_wealths) + N = model.num_agents + B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x)) + return 1 + (1 / N) - 2 * B + + +class BoltzmannWealthModel(mesa.Model): + """A simple model of an economy where agents exchange currency at random. + + All the agents begin with one unit of currency, and each time step can give + a unit of currency to another agent. Note how, over time, this produces a + highly skewed distribution of wealth. + """ + + def __init__(self, N=100, width=10, height=10): + self.num_agents = N + self.grid = mesa.space.MultiGrid(width, height, True) + self.schedule = mesa.time.RandomActivation(self) + self.datacollector = mesa.DataCollector( + model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} + ) + # Create agents + for i in range(self.num_agents): + a = MoneyAgent(i, self) + self.schedule.add(a) + # Add the agent to a random grid cell + x = self.random.randrange(self.grid.width) + y = self.random.randrange(self.grid.height) + self.grid.place_agent(a, (x, y)) + + self.running = True + self.datacollector.collect(self) + + def step(self): + self.schedule.step() + # collect data + self.datacollector.collect(self) + + def run_model(self, n): + for i in range(n): + self.step() + + +class MoneyAgent(mesa.Agent): + """An agent with fixed initial wealth.""" + + def __init__(self, unique_id, model): + super().__init__(unique_id, model) + self.wealth = 1 + + def move(self): + possible_steps = self.model.grid.get_neighborhood( + self.pos, moore=True, include_center=False + ) + new_position = self.random.choice(possible_steps) + self.model.grid.move_agent(self, new_position) + + def give_money(self): + cellmates = self.model.grid.get_cell_list_contents([self.pos]) + cellmates.pop( + cellmates.index(self) + ) # Ensure agent is not giving money to itself + if len(cellmates) > 0: + other = self.random.choice(cellmates) + other.wealth += 1 + self.wealth -= 1 + + def step(self): + self.move() + if self.wealth > 0: + self.give_money() diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/server.py b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/server.py new file mode 100644 index 00000000000..a49546ce741 --- /dev/null +++ b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/server.py @@ -0,0 +1,40 @@ +import mesa + +from .model import BoltzmannWealthModel + + +def agent_portrayal(agent): + portrayal = {"Shape": "circle", "Filled": "true", "r": 0.5} + + if agent.wealth > 0: + portrayal["Color"] = "red" + portrayal["Layer"] = 0 + else: + portrayal["Color"] = "grey" + portrayal["Layer"] = 1 + portrayal["r"] = 0.2 + return portrayal + + +grid = mesa.visualization.CanvasGrid(agent_portrayal, 10, 10, 500, 500) +chart = mesa.visualization.ChartModule( + [{"Label": "Gini", "Color": "#0000FF"}], data_collector_name="datacollector" +) + +model_params = { + "N": mesa.visualization.Slider( + "Number of agents", + 100, + 2, + 200, + 1, + description="Choose how many agents to include in the model", + ), + "width": 10, + "height": 10, +} + +server = mesa.visualization.ModularServer( + BoltzmannWealthModel, [grid, chart], "Money Model", model_params +) +server.port = 8521 diff --git a/examples/basic/boltzmann_wealth_model/requirements.txt b/examples/basic/boltzmann_wealth_model/requirements.txt new file mode 100644 index 00000000000..63b0d24e76d --- /dev/null +++ b/examples/basic/boltzmann_wealth_model/requirements.txt @@ -0,0 +1 @@ +mesa~=1.1 diff --git a/examples/basic/boltzmann_wealth_model/run.py b/examples/basic/boltzmann_wealth_model/run.py new file mode 100644 index 00000000000..f17675937cc --- /dev/null +++ b/examples/basic/boltzmann_wealth_model/run.py @@ -0,0 +1,3 @@ +from boltzmann_wealth_model.server import server + +server.launch(open_browser=True) diff --git a/examples/basic/schelling/README.md b/examples/basic/schelling/README.md new file mode 100644 index 00000000000..64cc9c83295 --- /dev/null +++ b/examples/basic/schelling/README.md @@ -0,0 +1,49 @@ +# Schelling Segregation Model + +## Summary + +The Schelling segregation model is a classic agent-based model, demonstrating how even a mild preference for similar neighbors can lead to a much higher degree of segregation than we would intuitively expect. The model consists of agents on a square grid, where each grid cell can contain at most one agent. Agents come in two colors: red and blue. They are happy if a certain number of their eight possible neighbors are of the same color, and unhappy otherwise. Unhappy agents will pick a random empty cell to move to each step, until they are happy. The model keeps running until there are no unhappy agents. + +By default, the number of similar neighbors the agents need to be happy is set to 3. That means the agents would be perfectly happy with a majority of their neighbors being of a different color (e.g. a Blue agent would be happy with five Red neighbors and three Blue ones). Despite this, the model consistently leads to a high degree of segregation, with most agents ending up with no neighbors of a different color. + +## 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 interactively, run ``mesa runserver`` in this directory. e.g. + +``` + $ mesa runserver +``` + +Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run. + +To view and run some example model analyses, launch the IPython Notebook and open ``analysis.ipynb``. Visualizing the analysis also requires [matplotlib](http://matplotlib.org/). + +## How to Run without the GUI + +To run the model with the grid displayed as an ASCII text, run `python run_ascii.py` in this directory. + +## Files + +* ``run.py``: Launches a model visualization server. +* ``run_ascii.py``: Run the model in text mode. +* ``schelling.py``: Contains the agent class, and the overall model class. +* ``server.py``: Defines classes for visualizing the model in the browser via Mesa's modular server, and instantiates a visualization server. +* ``analysis.ipynb``: Notebook demonstrating how to run experiments and parameter sweeps on the model. + +## Further Reading + +Schelling's original paper describing the model: + +[Schelling, Thomas C. Dynamic Models of Segregation. Journal of Mathematical Sociology. 1971, Vol. 1, pp 143-186.](https://www.stat.berkeley.edu/~aldous/157/Papers/Schelling_Seg_Models.pdf) + +An interactive, browser-based explanation and implementation: + +[Parable of the Polygons](http://ncase.me/polygons/), by Vi Hart and Nicky Case. diff --git a/examples/basic/schelling/__init__.py b/examples/basic/schelling/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/basic/schelling/analysis.ipynb b/examples/basic/schelling/analysis.ipynb new file mode 100644 index 00000000000..50f382c66a0 --- /dev/null +++ b/examples/basic/schelling/analysis.ipynb @@ -0,0 +1,457 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Schelling Segregation Model\n", + "\n", + "## Background\n", + "\n", + "The Schelling (1971) segregation model is a classic of agent-based modeling, demonstrating how agents following simple rules lead to the emergence of qualitatively different macro-level outcomes. Agents are randomly placed on a grid. There are two types of agents, one constituting the majority and the other the minority. All agents want a certain number (generally, 3) of their 8 surrounding neighbors to be of the same type in order for them to be happy. Unhappy agents will move to a random available grid space. While individual agents do not have a preference for a segregated outcome (e.g. they would be happy with 3 similar neighbors and 5 different ones), the aggregate outcome is nevertheless heavily segregated.\n", + "\n", + "## Implementation\n", + "\n", + "This is a demonstration of running a Mesa model in an IPython Notebook. The actual model and agent code are implemented in Schelling.py, in the same directory as this notebook. Below, we will import the model class, instantiate it, run it, and plot the time series of the number of happy agents." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "%matplotlib inline\n", + "\n", + "from model import Schelling" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we instantiate a model instance: a 10x10 grid, with an 80% change of an agent being placed in each cell, approximately 20% of agents set as minorities, and agents wanting at least 3 similar neighbors." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "model = Schelling(10, 10, 0.8, 0.2, 3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We want to run the model until all the agents are happy with where they are. However, there's no guarantee that a given model instantiation will *ever* settle down. So let's run it for either 100 steps or until it stops on its own, whichever comes first:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100\n" + ] + } + ], + "source": [ + "while model.running and model.schedule.steps < 100:\n", + " model.step()\n", + "print(model.schedule.steps) # Show how many steps have actually run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The model has a DataCollector object, which checks and stores how many agents are happy at the end of each step. It can also generate a pandas DataFrame of the data it has collected:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "model_out = model.datacollector.get_model_vars_dataframe()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
happy
00
173
267
372
472
\n", + "
" + ], + "text/plain": [ + " happy\n", + "0 0\n", + "1 73\n", + "2 72\n", + "3 73\n", + "4 72" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model_out.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we can plot the 'happy' series:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "model_out.happy.plot()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For testing purposes, here is a table giving each agent's x and y values at each step." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "x_positions = model.datacollector.get_agent_vars_dataframe()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
xy
StepAgentID
0(0, 0)01
(0, 1)89
(0, 2)52
(0, 3)00
(0, 4)17
\n", + "
" + ], + "text/plain": [ + " x y\n", + "Step AgentID \n", + "0 (0, 0) 0 1\n", + " (0, 1) 8 9\n", + " (0, 2) 5 2\n", + " (0, 3) 0 0\n", + " (0, 4) 1 7" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x_positions.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Effect of Homophily on segregation\n", + "\n", + "Now, we can do a parameter sweep to see how segregation changes with homophily.\n", + "\n", + "First, we create a function which takes a model instance and returns what fraction of agents are segregated -- that is, have no neighbors of the opposite type." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "from mesa.batchrunner import BatchRunner" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "def get_segregation(model):\n", + " \"\"\"\n", + " Find the % of agents that only have neighbors of their same type.\n", + " \"\"\"\n", + " segregated_agents = 0\n", + " for agent in model.schedule.agents:\n", + " segregated = True\n", + " for neighbor in model.grid.iter_neighbors(agent.pos, True):\n", + " if neighbor.type != agent.type:\n", + " segregated = False\n", + " break\n", + " if segregated:\n", + " segregated_agents += 1\n", + " return segregated_agents / model.schedule.get_agent_count()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we set up the batch run, with a dictionary of fixed and changing parameters. Let's hold everything fixed except for Homophily." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "fixed_params = {\"height\": 10, \"width\": 10, \"density\": 0.8, \"minority_pc\": 0.2}\n", + "variable_parms = {\"homophily\": range(1, 9)}" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "model_reporters = {\"Segregated_Agents\": get_segregation}" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "param_sweep = BatchRunner(\n", + " Schelling,\n", + " variable_parameters=variable_parms,\n", + " fixed_parameters=fixed_params,\n", + " iterations=10,\n", + " max_steps=200,\n", + " model_reporters=model_reporters,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "80it [00:15, 3.13it/s]\n" + ] + } + ], + "source": [ + "param_sweep.run_all()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "df = param_sweep.get_model_vars_dataframe()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.scatter(df.homophily, df.Segregated_Agents)\n", + "plt.grid(True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.9" + }, + "widgets": { + "state": {}, + "version": "1.1.2" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/examples/basic/schelling/model.py b/examples/basic/schelling/model.py new file mode 100644 index 00000000000..ccc5699e9c4 --- /dev/null +++ b/examples/basic/schelling/model.py @@ -0,0 +1,86 @@ +import mesa + + +class SchellingAgent(mesa.Agent): + """ + Schelling segregation agent + """ + + def __init__(self, pos, model, agent_type): + """ + Create a new Schelling agent. + + Args: + unique_id: Unique identifier for the agent. + x, y: Agent initial location. + agent_type: Indicator for the agent's type (minority=1, majority=0) + """ + super().__init__(pos, model) + self.pos = pos + self.type = agent_type + + def step(self): + similar = 0 + for neighbor in self.model.grid.iter_neighbors(self.pos, True): + if neighbor.type == self.type: + similar += 1 + + # If unhappy, move: + if similar < self.model.homophily: + self.model.grid.move_to_empty(self) + else: + self.model.happy += 1 + + +class Schelling(mesa.Model): + """ + Model class for the Schelling segregation model. + """ + + def __init__(self, width=20, height=20, density=0.8, minority_pc=0.2, homophily=3): + """ """ + + self.width = width + self.height = height + self.density = density + self.minority_pc = minority_pc + self.homophily = homophily + + self.schedule = mesa.time.RandomActivation(self) + self.grid = mesa.space.SingleGrid(width, height, torus=True) + + self.happy = 0 + self.datacollector = mesa.DataCollector( + {"happy": "happy"}, # Model-level count of happy agents + # For testing purposes, agent's individual x and y + {"x": lambda a: a.pos[0], "y": lambda a: a.pos[1]}, + ) + + # Set up agents + # We use a grid iterator that returns + # the coordinates of a cell as well as + # its contents. (coord_iter) + for cell in self.grid.coord_iter(): + x = cell[1] + y = cell[2] + if self.random.random() < self.density: + agent_type = 1 if self.random.random() < self.minority_pc else 0 + + agent = SchellingAgent((x, y), self, agent_type) + self.grid.place_agent(agent, (x, y)) + self.schedule.add(agent) + + self.running = True + self.datacollector.collect(self) + + def step(self): + """ + Run one step of the model. If All agents are happy, halt the model. + """ + self.happy = 0 # Reset counter of happy agents + self.schedule.step() + # collect data + self.datacollector.collect(self) + + if self.happy == self.schedule.get_agent_count(): + self.running = False diff --git a/examples/basic/schelling/requirements.txt b/examples/basic/schelling/requirements.txt new file mode 100644 index 00000000000..19b805acb1f --- /dev/null +++ b/examples/basic/schelling/requirements.txt @@ -0,0 +1,3 @@ +jupyter +matplotlib +mesa~=1.1 diff --git a/examples/basic/schelling/run.py b/examples/basic/schelling/run.py new file mode 100644 index 00000000000..f20cebcbd5f --- /dev/null +++ b/examples/basic/schelling/run.py @@ -0,0 +1,3 @@ +from server import server + +server.launch(open_browser=True) diff --git a/examples/basic/schelling/run_ascii.py b/examples/basic/schelling/run_ascii.py new file mode 100644 index 00000000000..460fabbb746 --- /dev/null +++ b/examples/basic/schelling/run_ascii.py @@ -0,0 +1,48 @@ +import mesa +from model import Schelling + + +class SchellingTextVisualization(mesa.visualization.TextVisualization): + """ + ASCII visualization for schelling model + """ + + def __init__(self, model): + """ + Create new Schelling ASCII visualization. + """ + self.model = model + + grid_viz = mesa.visualization.TextGrid(self.model.grid, self.print_ascii_agent) + happy_viz = mesa.visualization.TextData(self.model, "happy") + self.elements = [grid_viz, happy_viz] + + @staticmethod + def print_ascii_agent(a): + """ + Minority agents are X, Majority are O. + """ + if a.type == 0: + return "O" + if a.type == 1: + return "X" + + +if __name__ == "__main__": + model_params = { + "height": 20, + "width": 20, + # Agent density, from 0.8 to 1.0 + "density": 0.8, + # Fraction minority, from 0.2 to 1.0 + "minority_pc": 0.2, + # Homophily, from 3 to 8 + "homophily": 3, + } + + model = Schelling(**model_params) + viz = SchellingTextVisualization(model) + for i in range(10): + print("Step:", i) + viz.step() + print("---") diff --git a/examples/basic/schelling/server.py b/examples/basic/schelling/server.py new file mode 100644 index 00000000000..1396e9c7fed --- /dev/null +++ b/examples/basic/schelling/server.py @@ -0,0 +1,45 @@ +import mesa +from model import Schelling + + +def get_happy_agents(model): + """ + Display a text count of how many happy agents there are. + """ + return f"Happy agents: {model.happy}" + + +def schelling_draw(agent): + """ + Portrayal Method for canvas + """ + if agent is None: + return + portrayal = {"Shape": "circle", "r": 0.5, "Filled": "true", "Layer": 0} + + if agent.type == 0: + portrayal["Color"] = ["#FF0000", "#FF9999"] + portrayal["stroke_color"] = "#00FF00" + else: + portrayal["Color"] = ["#0000FF", "#9999FF"] + portrayal["stroke_color"] = "#000000" + return portrayal + + +canvas_element = mesa.visualization.CanvasGrid(schelling_draw, 20, 20, 500, 500) +happy_chart = mesa.visualization.ChartModule([{"Label": "happy", "Color": "Black"}]) + +model_params = { + "height": 20, + "width": 20, + "density": mesa.visualization.Slider("Agent density", 0.8, 0.1, 1.0, 0.1), + "minority_pc": mesa.visualization.Slider("Fraction minority", 0.2, 0.00, 1.0, 0.05), + "homophily": mesa.visualization.Slider("Homophily", 3, 0, 8, 1), +} + +server = mesa.visualization.ModularServer( + Schelling, + [canvas_element, get_happy_agents, happy_chart], + "Schelling", + model_params, +) From 2a68b116ca051b77f0c15f73216b06c0e72ec1da Mon Sep 17 00:00:00 2001 From: JoeHelbing <89875117+JoeHelbing@users.noreply.github.com> Date: Thu, 29 Jun 2023 23:39:19 -0500 Subject: [PATCH 042/116] Bug fixes and additions to Epstein Civil Violence (#42) * Bug fixes and additions to Epstein Civil Violence This update changes a small set of bugs that was causing the model to not exhibit the cyclical explosions of active agents seen in the original Epstein paper. It also adds some small functionality to the browser display, introducing sliders to allow the user to change model parameters on the fly, and watch a graph of agent state numbers. Bug Fixes - Fix agent state logic - Fix Moore vs Von Neuman neighborhood selection to use Epstein paper behavior - Fix agent vision bug (was 1 and not parameter `vision` when calculating neighborhood, which also fixes agent movement issue) - Fix arrestee state issue (arrestee was not being reset to "Quiescent", and so was counting as "Active" for all nearby agents) Additions and Changes - Change display of agents where jailed agents are represented by grey square box - Added sliders for model attributes Co-authored-by: rht --- .../epstein_civil_violence/agent.py | 14 ++--- .../epstein_civil_violence/model.py | 6 ++- .../epstein_civil_violence/server.py | 54 ++++++++++++++----- 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py index 358b4484d44..ea108faa698 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py @@ -78,14 +78,9 @@ def step(self): self.update_neighbors() self.update_estimated_arrest_probability() net_risk = self.risk_aversion * self.arrest_probability - if ( - self.condition == "Quiescent" - and (self.grievance - net_risk) > self.threshold - ): + if self.grievance - net_risk > self.threshold: self.condition = "Active" - elif ( - self.condition == "Active" and (self.grievance - net_risk) <= self.threshold - ): + else: self.condition = "Quiescent" if self.model.movement and self.empty_neighbors: new_pos = self.random.choice(self.empty_neighbors) @@ -96,7 +91,7 @@ def update_neighbors(self): Look around and see who my neighbors are """ self.neighborhood = self.model.grid.get_neighborhood( - self.pos, moore=False, radius=1 + self.pos, moore=True, radius=self.vision ) self.neighbors = self.model.grid.get_cell_list_contents(self.neighborhood) self.empty_neighbors = [ @@ -167,6 +162,7 @@ def step(self): arrestee = self.random.choice(active_neighbors) sentence = self.random.randint(0, self.model.max_jail_term) arrestee.jail_sentence = sentence + arrestee.condition = "Quiescent" if self.model.movement and self.empty_neighbors: new_pos = self.random.choice(self.empty_neighbors) self.model.grid.move_agent(self, new_pos) @@ -176,7 +172,7 @@ def update_neighbors(self): Look around and see who my neighbors are. """ self.neighborhood = self.model.grid.get_neighborhood( - self.pos, moore=False, radius=1 + self.pos, moore=True, radius=self.vision ) self.neighbors = self.model.grid.get_cell_list_contents(self.neighborhood) self.empty_neighbors = [ diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py index 4ba70ecba17..1c7e45f490f 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py @@ -60,10 +60,12 @@ def __init__( self.iteration = 0 self.schedule = mesa.time.RandomActivation(self) self.grid = mesa.space.SingleGrid(width, height, torus=True) + model_reporters = { "Quiescent": lambda m: self.count_type_citizens(m, "Quiescent"), "Active": lambda m: self.count_type_citizens(m, "Active"), "Jailed": self.count_jailed, + "Cops": self.count_cops, } agent_reporters = { "x": lambda a: a.pos[0], @@ -123,7 +125,7 @@ def count_type_citizens(model, condition, exclude_jailed=True): for agent in model.schedule.agents: if agent.breed == "cop": continue - if exclude_jailed and agent.jail_sentence: + if exclude_jailed and agent.jail_sentence > 0: continue if agent.condition == condition: count += 1 @@ -136,6 +138,6 @@ def count_jailed(model): """ count = 0 for agent in model.schedule.agents: - if agent.breed == "citizen" and agent.jail_sentence: + if agent.breed == "citizen" and agent.jail_sentence > 0: count += 1 return count diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py index 04412835b90..560b94e5468 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py @@ -4,9 +4,10 @@ from .model import EpsteinCivilViolence COP_COLOR = "#000000" -AGENT_QUIET_COLOR = "#0066CC" -AGENT_REBEL_COLOR = "#CC0000" -JAIL_COLOR = "#757575" +AGENT_QUIET_COLOR = "#648FFF" +AGENT_REBEL_COLOR = "#FE6100" +JAIL_COLOR = "#808080" +JAIL_SHAPE = "rect" def citizen_cop_portrayal(agent): @@ -25,29 +26,56 @@ def citizen_cop_portrayal(agent): AGENT_QUIET_COLOR if agent.condition == "Quiescent" else AGENT_REBEL_COLOR ) color = JAIL_COLOR if agent.jail_sentence else color + shape = JAIL_SHAPE if agent.jail_sentence else "circle" portrayal["Color"] = color - portrayal["r"] = 0.8 + portrayal["Shape"] = shape + if shape == "rect": + portrayal["w"] = 0.9 + portrayal["h"] = 0.9 + else: + portrayal["r"] = 0.5 + portrayal["Filled"] = "false" portrayal["Layer"] = 0 elif type(agent) is Cop: portrayal["Color"] = COP_COLOR - portrayal["r"] = 0.5 + portrayal["r"] = 0.9 portrayal["Layer"] = 1 + return portrayal model_params = { "height": 40, "width": 40, - "citizen_density": 0.7, - "cop_density": 0.074, - "citizen_vision": 7, - "cop_vision": 7, - "legitimacy": 0.8, - "max_jail_term": 1000, + "citizen_density": mesa.visualization.Slider( + "Initial Agent Density", 0.7, 0.0, 0.9, 0.1 + ), + "cop_density": mesa.visualization.Slider( + "Initial Cop Density", 0.04, 0.0, 0.1, 0.01 + ), + "citizen_vision": mesa.visualization.Slider("Citizen Vision", 7, 1, 10, 1), + "cop_vision": mesa.visualization.Slider("Cop Vision", 7, 1, 10, 1), + "legitimacy": mesa.visualization.Slider( + "Government Legitimacy", 0.82, 0.0, 1, 0.01 + ), + "max_jail_term": mesa.visualization.Slider("Max Jail Term", 30, 0, 50, 1), } - canvas_element = mesa.visualization.CanvasGrid(citizen_cop_portrayal, 40, 40, 480, 480) +chart = mesa.visualization.ChartModule( + [ + {"Label": "Quiescent", "Color": "#648FFF"}, + {"Label": "Active", "Color": "#FE6100"}, + {"Label": "Jailed", "Color": "#808080"}, + ], + data_collector_name="datacollector", +) server = mesa.visualization.ModularServer( - EpsteinCivilViolence, [canvas_element], "Epstein Civil Violence", model_params + EpsteinCivilViolence, + [ + canvas_element, + chart, + ], + "Epstein Civil Violence", + model_params, ) From 1ea028a0541c4039cac5b7f75dd4b1a1405fd446 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 6 Jul 2023 11:11:26 -0400 Subject: [PATCH 043/116] trading Sugarscape: Add solara visualization --- examples/advanced/sugarscape_g1mt/Readme.md | 1 + examples/advanced/sugarscape_g1mt/app.py | 63 +++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 examples/advanced/sugarscape_g1mt/app.py diff --git a/examples/advanced/sugarscape_g1mt/Readme.md b/examples/advanced/sugarscape_g1mt/Readme.md index 23013b52d5f..67ddfd9b189 100644 --- a/examples/advanced/sugarscape_g1mt/Readme.md +++ b/examples/advanced/sugarscape_g1mt/Readme.md @@ -82,6 +82,7 @@ Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and p * ``sugarscape_g1mt/sugar_map.txt``: Provides sugar and spice landscape in raster type format. * ``server.py``: Sets up and launches and interactive visualization server. * ``run.py``: Runs Server, Single Run or Batch Run with data collection and basic analysis. +* `app.py`: Runs a visualization server via Solara (`solara run app.py`). ## Additional Resources diff --git a/examples/advanced/sugarscape_g1mt/app.py b/examples/advanced/sugarscape_g1mt/app.py new file mode 100644 index 00000000000..2fc52207c2b --- /dev/null +++ b/examples/advanced/sugarscape_g1mt/app.py @@ -0,0 +1,63 @@ +import numpy as np +import solara +from matplotlib.figure import Figure +from mesa_models.experimental import JupyterViz +from sugarscape_g1mt.model import SugarscapeG1mt +from sugarscape_g1mt.resource_agents import Sugar +from sugarscape_g1mt.trader_agents import Trader + + +def space_drawer(viz): + 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 i in range(g.width): + for j in range(g.height): + content = g._grid[i][j] + 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. + value = agent.amount if agent.amount > 1 else np.nan + if isinstance(agent, Sugar): + layers["sugar"][i][j] = value + else: + layers["spice"][i][j] = value + return layers + + fig = Figure() + ax = fig.subplots() + out = portray(viz.model.grid) + # 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") + fig.colorbar(im, orientation="vertical") + # Spice + ax.imshow(out["spice"], cmap="winter", origin="lower") + # Trader + ax.scatter(**out["trader"]) + ax.set_axis_off() + solara.FigureMatplotlib(fig, dependencies=[viz.model, viz.df]) + + +model_params = { + "width": 50, + "height": 50, +} + +page = JupyterViz( + SugarscapeG1mt, + model_params, + measures=["Trader", "Price"], + name="Sugarscape {G1, M, T}", + space_drawer=space_drawer, + play_interval=1500, +) +page # noqa From 63c3a6998b49dee26767c1217eab01e935a2f491 Mon Sep 17 00:00:00 2001 From: rht Date: Fri, 7 Jul 2023 10:01:09 -0400 Subject: [PATCH 044/116] solara trading sugarscape: Use coord_iter --- examples/advanced/sugarscape_g1mt/app.py | 25 ++++++++++++------------ 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/app.py b/examples/advanced/sugarscape_g1mt/app.py index 2fc52207c2b..1866db2227f 100644 --- a/examples/advanced/sugarscape_g1mt/app.py +++ b/examples/advanced/sugarscape_g1mt/app.py @@ -15,20 +15,19 @@ def portray(g): "trader": {"x": [], "y": [], "c": "tab:red", "marker": "o", "s": 10}, } - for i in range(g.width): - for j in range(g.height): - content = g._grid[i][j] - for agent in content: - if isinstance(agent, Trader): - layers["trader"]["x"].append(i) - layers["trader"]["y"].append(j) + # TODO update to Mesa 2.0 API + 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. + value = agent.amount if agent.amount > 1 else np.nan + if isinstance(agent, Sugar): + layers["sugar"][i][j] = value else: - # Don't visualize resource with value <= 1. - value = agent.amount if agent.amount > 1 else np.nan - if isinstance(agent, Sugar): - layers["sugar"][i][j] = value - else: - layers["spice"][i][j] = value + layers["spice"][i][j] = value return layers fig = Figure() From 7adbb0b5fc2b3d76e873bcc2fb08c32bf29c74d7 Mon Sep 17 00:00:00 2001 From: rht Date: Sun, 16 Jul 2023 00:04:28 -0400 Subject: [PATCH 045/116] Bump to Mesa 2.0 (#40) * Bump to Mesa 2.0 * Update to Mesa 2.0 API * Update coord_iter to Mesa 2.0 API --------- Co-authored-by: Tom Pike --- .../epstein_civil_violence/epstein_civil_violence/model.py | 2 +- examples/advanced/pd_grid/analysis.ipynb | 2 +- examples/advanced/pd_grid/requirements.txt | 2 +- examples/advanced/sugarscape_g1mt/app.py | 3 +-- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py | 2 +- examples/advanced/wolf_sheep/requirements.txt | 2 +- examples/advanced/wolf_sheep/wolf_sheep/model.py | 2 +- 7 files changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py index 1c7e45f490f..f660ecfa21e 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py @@ -81,7 +81,7 @@ def __init__( unique_id = 0 if self.cop_density + self.citizen_density > 1: raise ValueError("Cop density + citizen density must be less than 1") - for contents, x, y in self.grid.coord_iter(): + for contents, (x, y) in self.grid.coord_iter(): if self.random.random() < self.cop_density: cop = Cop(unique_id, self, (x, y), vision=self.cop_vision) unique_id += 1 diff --git a/examples/advanced/pd_grid/analysis.ipynb b/examples/advanced/pd_grid/analysis.ipynb index 53a63345884..7f848318aac 100644 --- a/examples/advanced/pd_grid/analysis.ipynb +++ b/examples/advanced/pd_grid/analysis.ipynb @@ -68,7 +68,7 @@ " if not ax:\n", " fig, ax = plt.subplots(figsize=(6, 6))\n", " grid = np.zeros((model.grid.width, model.grid.height))\n", - " for agent, x, y in model.grid.coord_iter():\n", + " for agent, (x, y) in model.grid.coord_iter():\n", " if agent.move == \"D\":\n", " grid[y][x] = 1\n", " else:\n", diff --git a/examples/advanced/pd_grid/requirements.txt b/examples/advanced/pd_grid/requirements.txt index 19b805acb1f..da2b9972efd 100644 --- a/examples/advanced/pd_grid/requirements.txt +++ b/examples/advanced/pd_grid/requirements.txt @@ -1,3 +1,3 @@ jupyter matplotlib -mesa~=1.1 +mesa~=2.0 diff --git a/examples/advanced/sugarscape_g1mt/app.py b/examples/advanced/sugarscape_g1mt/app.py index 1866db2227f..ed54be19940 100644 --- a/examples/advanced/sugarscape_g1mt/app.py +++ b/examples/advanced/sugarscape_g1mt/app.py @@ -15,8 +15,7 @@ def portray(g): "trader": {"x": [], "y": [], "c": "tab:red", "marker": "o", "s": 10}, } - # TODO update to Mesa 2.0 API - for content, i, j in g.coord_iter(): + for content, (i, j) in g.coord_iter(): for agent in content: if isinstance(agent, Trader): layers["trader"]["x"].append(i) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index a7ed520016a..83eaaafa19d 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -89,7 +89,7 @@ def __init__( spice_distribution = np.flip(sugar_distribution, 1) agent_id = 0 - for _, x, y in self.grid.coord_iter(): + for _, (x, y) in self.grid.coord_iter(): max_sugar = sugar_distribution[x, y] if max_sugar > 0: sugar = Sugar(agent_id, self, (x, y), max_sugar) diff --git a/examples/advanced/wolf_sheep/requirements.txt b/examples/advanced/wolf_sheep/requirements.txt index 63b0d24e76d..25d263f4e84 100644 --- a/examples/advanced/wolf_sheep/requirements.txt +++ b/examples/advanced/wolf_sheep/requirements.txt @@ -1 +1 @@ -mesa~=1.1 +mesa~=2.0 diff --git a/examples/advanced/wolf_sheep/wolf_sheep/model.py b/examples/advanced/wolf_sheep/wolf_sheep/model.py index ac44beff42b..2626f9581c5 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/model.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/model.py @@ -113,7 +113,7 @@ def __init__( # Create grass patches if self.grass: - for agent, x, y in self.grid.coord_iter(): + for agent, (x, y) in self.grid.coord_iter(): fully_grown = self.random.choice([True, False]) if fully_grown: From 013afa9763306d72f3c98aa73e3ee4d7918ffc1d Mon Sep 17 00:00:00 2001 From: rht Date: Sun, 16 Jul 2023 00:04:28 -0400 Subject: [PATCH 046/116] Bump to Mesa 2.0 (#40) * Bump to Mesa 2.0 * Update to Mesa 2.0 API * Update coord_iter to Mesa 2.0 API --------- Co-authored-by: Tom Pike --- examples/basic/boid_flockers/requirements.txt | 2 +- examples/basic/boltzmann_wealth_model/app.py | 2 +- examples/basic/boltzmann_wealth_model/requirements.txt | 2 +- examples/basic/conways_game_of_life/app.py | 2 +- .../basic/conways_game_of_life/conways_game_of_life/model.py | 2 +- examples/basic/schelling/model.py | 3 +-- examples/basic/schelling/requirements.txt | 2 +- examples/basic/virus_on_network/requirements.txt | 2 +- examples/basic/virus_on_network/virus_on_network/model.py | 4 +++- 9 files changed, 11 insertions(+), 10 deletions(-) diff --git a/examples/basic/boid_flockers/requirements.txt b/examples/basic/boid_flockers/requirements.txt index 19b805acb1f..da2b9972efd 100644 --- a/examples/basic/boid_flockers/requirements.txt +++ b/examples/basic/boid_flockers/requirements.txt @@ -1,3 +1,3 @@ jupyter matplotlib -mesa~=1.1 +mesa~=2.0 diff --git a/examples/basic/boltzmann_wealth_model/app.py b/examples/basic/boltzmann_wealth_model/app.py index f2dd6da91ab..c4c92ea837e 100644 --- a/examples/basic/boltzmann_wealth_model/app.py +++ b/examples/basic/boltzmann_wealth_model/app.py @@ -68,7 +68,7 @@ my_bar.progress((i / num_ticks), text="Simulation progress") placeholder.text("Step = %d" % i) for cell in model.grid.coord_iter(): - cell_content, x, y = cell + cell_content, (x, y) = cell agent_count = len(cell_content) selected_row = df_grid[(df_grid["x"] == x) & (df_grid["y"] == y)] df_grid.loc[ diff --git a/examples/basic/boltzmann_wealth_model/requirements.txt b/examples/basic/boltzmann_wealth_model/requirements.txt index 63b0d24e76d..25d263f4e84 100644 --- a/examples/basic/boltzmann_wealth_model/requirements.txt +++ b/examples/basic/boltzmann_wealth_model/requirements.txt @@ -1 +1 @@ -mesa~=1.1 +mesa~=2.0 diff --git a/examples/basic/conways_game_of_life/app.py b/examples/basic/conways_game_of_life/app.py index 0977b6f3b43..884ec523921 100644 --- a/examples/basic/conways_game_of_life/app.py +++ b/examples/basic/conways_game_of_life/app.py @@ -49,7 +49,7 @@ model.step() my_bar.progress((i / num_ticks), text="Simulation progress") placeholder.text("Step = %d" % i) - for contents, x, y in model.grid.coord_iter(): + for contents, (x, y) in model.grid.coord_iter(): # print('x:',x,'y:',y, 'state:',contents) selected_row = df_grid[(df_grid["x"] == x) & (df_grid["y"] == y)] df_grid.loc[ diff --git a/examples/basic/conways_game_of_life/conways_game_of_life/model.py b/examples/basic/conways_game_of_life/conways_game_of_life/model.py index 581541c11d3..bf2204e0251 100644 --- a/examples/basic/conways_game_of_life/conways_game_of_life/model.py +++ b/examples/basic/conways_game_of_life/conways_game_of_life/model.py @@ -27,7 +27,7 @@ def __init__(self, width=50, height=50): # Place a cell at each location, with some initialized to # ALIVE and some to DEAD. - for contents, x, y in self.grid.coord_iter(): + for contents, (x, y) in self.grid.coord_iter(): cell = Cell((x, y), self) if self.random.random() < 0.1: cell.state = cell.ALIVE diff --git a/examples/basic/schelling/model.py b/examples/basic/schelling/model.py index ccc5699e9c4..c7fe766ac14 100644 --- a/examples/basic/schelling/model.py +++ b/examples/basic/schelling/model.py @@ -61,8 +61,7 @@ def __init__(self, width=20, height=20, density=0.8, minority_pc=0.2, homophily= # the coordinates of a cell as well as # its contents. (coord_iter) for cell in self.grid.coord_iter(): - x = cell[1] - y = cell[2] + x, y = cell[1] if self.random.random() < self.density: agent_type = 1 if self.random.random() < self.minority_pc else 0 diff --git a/examples/basic/schelling/requirements.txt b/examples/basic/schelling/requirements.txt index 19b805acb1f..da2b9972efd 100644 --- a/examples/basic/schelling/requirements.txt +++ b/examples/basic/schelling/requirements.txt @@ -1,3 +1,3 @@ jupyter matplotlib -mesa~=1.1 +mesa~=2.0 diff --git a/examples/basic/virus_on_network/requirements.txt b/examples/basic/virus_on_network/requirements.txt index 9d01589a901..03e3c237233 100644 --- a/examples/basic/virus_on_network/requirements.txt +++ b/examples/basic/virus_on_network/requirements.txt @@ -1,2 +1,2 @@ networkx>=2.0 -mesa~=1.1 \ No newline at end of file +mesa~=2.0 \ No newline at end of file diff --git a/examples/basic/virus_on_network/virus_on_network/model.py b/examples/basic/virus_on_network/virus_on_network/model.py index 6f9524900f6..961c56bcf9f 100644 --- a/examples/basic/virus_on_network/virus_on_network/model.py +++ b/examples/basic/virus_on_network/virus_on_network/model.py @@ -123,7 +123,9 @@ def __init__( self.gain_resistance_chance = gain_resistance_chance def try_to_infect_neighbors(self): - neighbors_nodes = self.model.grid.get_neighbors(self.pos, include_center=False) + neighbors_nodes = self.model.grid.get_neighborhood( + self.pos, include_center=False + ) susceptible_neighbors = [ agent for agent in self.model.grid.get_cell_list_contents(neighbors_nodes) From 7709bd9794cd4733c612e36ef3b5d76ca194e2d9 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 20 Jul 2023 00:37:01 -0400 Subject: [PATCH 047/116] Move jupyter_viz to core Mesa repo --- examples/advanced/sugarscape_g1mt/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/advanced/sugarscape_g1mt/app.py b/examples/advanced/sugarscape_g1mt/app.py index ed54be19940..af991e12f71 100644 --- a/examples/advanced/sugarscape_g1mt/app.py +++ b/examples/advanced/sugarscape_g1mt/app.py @@ -1,7 +1,7 @@ import numpy as np import solara from matplotlib.figure import Figure -from mesa_models.experimental import JupyterViz +from mesa.experimental import JupyterViz from sugarscape_g1mt.model import SugarscapeG1mt from sugarscape_g1mt.resource_agents import Sugar from sugarscape_g1mt.trader_agents import Trader From e692eedf9a445c1c4fb9b3b3eb9bcdacede246be Mon Sep 17 00:00:00 2001 From: jurreaserna <105222220+jurreaserna@users.noreply.github.com> Date: Thu, 14 Sep 2023 20:23:12 -0500 Subject: [PATCH 048/116] fix: Add count_cops method to the model (#60) --- .../epstein_civil_violence/model.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py index f660ecfa21e..6bce24ebe23 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py @@ -141,3 +141,14 @@ def count_jailed(model): if agent.breed == "citizen" and agent.jail_sentence > 0: count += 1 return count + + @staticmethod + def count_cops(model): + """ + Helper method to count jailed agents. + """ + count = 0 + for agent in model.schedule.agents: + if agent.breed == "cop": + count += 1 + return count From 394e566125e5aa4c32ae56e7bf90ef8ebf30979b Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 17 Aug 2023 06:47:46 -0400 Subject: [PATCH 049/116] solara: Add virus_on_network network example --- examples/basic/virus_on_network/app.py | 135 +++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 examples/basic/virus_on_network/app.py diff --git a/examples/basic/virus_on_network/app.py b/examples/basic/virus_on_network/app.py new file mode 100644 index 00000000000..da3e5dda9be --- /dev/null +++ b/examples/basic/virus_on_network/app.py @@ -0,0 +1,135 @@ +import math + +import solara +from matplotlib.figure import Figure +from matplotlib.ticker import MaxNLocator +from mesa.experimental import JupyterViz, make_text +from virus_on_network.model import State, VirusOnNetwork, number_infected + + +def agent_portrayal(graph): + def get_agent(node): + return graph.nodes[node]["agent"][0] + + edge_width = [] + edge_color = [] + for u, v in graph.edges(): + agent1 = get_agent(u) + agent2 = get_agent(v) + w = 2 + ec = "#e8e8e8" + if State.RESISTANT in (agent1.state, agent2.state): + w = 3 + ec = "black" + edge_width.append(w) + edge_color.append(ec) + node_color_dict = { + State.INFECTED: "tab:red", + State.SUSCEPTIBLE: "tab:green", + State.RESISTANT: "tab:gray", + } + node_color = [node_color_dict[get_agent(node).state] for node in graph.nodes()] + return { + "width": edge_width, + "edge_color": edge_color, + "node_color": node_color, + } + + +def get_resistant_susceptible_ratio(model): + ratio = model.resistant_susceptible_ratio() + ratio_text = r"$\infty$" if ratio is math.inf else f"{ratio:.2f}" + infected_text = str(number_infected(model)) + + return "Resistant/Susceptible Ratio: {}
Infected Remaining: {}".format( + ratio_text, infected_text + ) + + +def make_plot(model): + # This is for the case when we want to plot multiple measures in 1 figure. + # We could incorporate this into core Mesa. + fig = Figure() + ax = fig.subplots() + measures = ["Infected", "Susceptible", "Resistant"] + colors = ["tab:red", "tab:green", "tab:gray"] + for i, m in enumerate(measures): + color = colors[i] + df = model.datacollector.get_model_vars_dataframe() + ax.plot(df.loc[:, m], label=m, color=color) + fig.legend() + # Set integer x axis + ax.xaxis.set_major_locator(MaxNLocator(integer=True)) + solara.FigureMatplotlib(fig) + + +model_params = { + "num_nodes": { + "type": "SliderInt", + "value": 10, + "label": "Number of agents", + "min": 10, + "max": 100, + "step": 1, + }, + "avg_node_degree": { + "type": "SliderInt", + "value": 3, + "label": "Avg Node Degree", + "min": 3, + "max": 8, + "step": 1, + }, + "initial_outbreak_size": { + "type": "SliderInt", + "value": 1, + "label": "Initial Outbreak Size", + "min": 1, + "max": 10, + "step": 1, + }, + "virus_spread_chance": { + "type": "SliderFloat", + "value": 0.4, + "label": "Virus Spread Chance", + "min": 0.0, + "max": 1.0, + "step": 0.1, + }, + "virus_check_frequency": { + "type": "SliderFloat", + "value": 0.4, + "label": "Virus Check Frequency", + "min": 0.0, + "max": 1.0, + "step": 0.1, + }, + "recovery_chance": { + "type": "SliderFloat", + "value": 0.3, + "label": "Recovery Chance", + "min": 0.0, + "max": 1.0, + "step": 0.1, + }, + "gain_resistance_chance": { + "type": "SliderFloat", + "value": 0.5, + "label": "Gain Resistance Chance", + "min": 0.0, + "max": 1.0, + "step": 0.1, + }, +} + +page = JupyterViz( + VirusOnNetwork, + model_params, + measures=[ + make_plot, + make_text(get_resistant_susceptible_ratio), + ], + name="Virus Model", + agent_portrayal=agent_portrayal, +) +page # noqa From 34aa1c9f59408bc2d96f3b0d577204547993cf2b Mon Sep 17 00:00:00 2001 From: rht Date: Tue, 26 Sep 2023 05:28:25 -0400 Subject: [PATCH 050/116] Sugarscape G1MT: Update to Mesa 2.1.2 JupyterViz API --- examples/advanced/sugarscape_g1mt/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/app.py b/examples/advanced/sugarscape_g1mt/app.py index af991e12f71..9759c032481 100644 --- a/examples/advanced/sugarscape_g1mt/app.py +++ b/examples/advanced/sugarscape_g1mt/app.py @@ -7,7 +7,7 @@ from sugarscape_g1mt.trader_agents import Trader -def space_drawer(viz): +def space_drawer(model, agent_portrayal): def portray(g): layers = { "sugar": [[np.nan for j in range(g.height)] for i in range(g.width)], @@ -31,7 +31,7 @@ def portray(g): fig = Figure() ax = fig.subplots() - out = portray(viz.model.grid) + out = portray(model.grid) # Sugar # Important note: imshow by default draws from upper left. You have to # always explicitly specify origin="lower". @@ -42,7 +42,7 @@ def portray(g): # Trader ax.scatter(**out["trader"]) ax.set_axis_off() - solara.FigureMatplotlib(fig, dependencies=[viz.model, viz.df]) + solara.FigureMatplotlib(fig) model_params = { From aa6377d219b6378288e135a7855b5da97d79f7ec Mon Sep 17 00:00:00 2001 From: rht Date: Sun, 26 Nov 2023 18:38:32 -0500 Subject: [PATCH 051/116] Sugarscape {G1,{M,T}}: Add tests --- examples/advanced/sugarscape_g1mt/Readme.md | 3 +- .../sugarscape_g1mt/sugarscape_g1mt/model.py | 7 ++ examples/advanced/sugarscape_g1mt/tests.py | 72 +++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 examples/advanced/sugarscape_g1mt/tests.py diff --git a/examples/advanced/sugarscape_g1mt/Readme.md b/examples/advanced/sugarscape_g1mt/Readme.md index 67ddfd9b189..7fbced07ddc 100644 --- a/examples/advanced/sugarscape_g1mt/Readme.md +++ b/examples/advanced/sugarscape_g1mt/Readme.md @@ -3,7 +3,7 @@ ## Summary This is Epstein & Axtell's Sugarscape model with Traders, a detailed description is in Chapter four of -*Growing Artificial Societies: Social Science from the Bottom Up.* (1996) +*Growing Artificial Societies: Social Science from the Bottom Up.* (1996) The model shows an emergent price equilibrium can happen via a decentralized dynamics. This code generally matches the code in the Complexity Explorer Tutorial, but in `.py` instead of `.ipynb` format. @@ -83,6 +83,7 @@ Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and p * ``server.py``: Sets up and launches and interactive visualization server. * ``run.py``: Runs Server, Single Run or Batch Run with data collection and basic analysis. * `app.py`: Runs a visualization server via Solara (`solara run app.py`). +* `tests.py`: Has tests to ensure that the model reproduces the results in shown in Growing Artificial Societies. ## Additional Resources diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index 83eaaafa19d..dd27da23ba9 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -49,6 +49,7 @@ def __init__( metabolism_max=5, vision_min=1, vision_max=5, + enable_trade=True, ): # Initiate width and heigh of sugarscape self.width = width @@ -61,6 +62,7 @@ def __init__( self.metabolism_max = metabolism_max self.vision_min = vision_min self.vision_max = vision_max + self.enable_trade = enable_trade self.running = True # initiate activation schedule @@ -175,6 +177,11 @@ def step(self): agent.eat() agent.maybe_die() + if not self.enable_trade: + # If trade is not enabled, return early + self.datacollector.collect(self) + return + trader_shuffle = self.randomize_traders() for agent in trader_shuffle: diff --git a/examples/advanced/sugarscape_g1mt/tests.py b/examples/advanced/sugarscape_g1mt/tests.py new file mode 100644 index 00000000000..bcfcf73931d --- /dev/null +++ b/examples/advanced/sugarscape_g1mt/tests.py @@ -0,0 +1,72 @@ +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) + + +def check_slope(y, increasing): + x = range(len(y)) + slope, intercept, _, p_value, _ = stats.linregress(x, y) + result = (slope > 0) if increasing else (slope < 0) + # p_value for significance. + assert result and p_value < 0.05, (slope, p_value) + + +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.datacollector._new_model_reporter( + "price_variance", + lambda m: np.var( + flatten([a.prices for a in m.schedule.agents_by_type[Trader].values()]) + ), + ) + model.run_model(step_count=50) + + df_model = model.datacollector.get_model_vars_dataframe() + + check_slope(df_model.price_variance, increasing=False) + + +def test_carrying_capacity(): + def calculate_carrying_capacities(enable_trade): + carrying_capacities = [] + visions = range(1, 10) + for vision_max in visions: + model = SugarscapeG1mt(vision_max=vision_max, enable_trade=enable_trade) + model.run_model(step_count=50) + carrying_capacities.append(len(model.schedule.agents_by_type[Trader])) + return carrying_capacities + + # Carrying capacity should increase over mean vision (figure IV-6). + # See Growing Artificial Societies p. 112. + carrying_capacities_with_trade = calculate_carrying_capacities(True) + check_slope( + carrying_capacities_with_trade, + increasing=True, + ) + # Carrying capacity should be higher when trade is enabled (figure IV-6). + carrying_capacities_no_trade = calculate_carrying_capacities(False) + check_slope( + carrying_capacities_no_trade, + increasing=True, + ) + + t_statistic, p_value = stats.ttest_rel( + carrying_capacities_with_trade, carrying_capacities_no_trade + ) + # t_statistic > 0 means carrying_capacities_with_trade has larger values + # than carrying_capacities_no_trade. + # p_value for significance. + assert t_statistic > 0 and p_value < 0.05 + + +# TODO: +# 1. Reproduce figure IV-12 that the log of average price should decrease over average agent age +# 2. Reproduce figure IV-13 that the gini coefficient on trade should decrease over mean vision, and should be higher with trade +# 3. a stricter test would be to ensure the amount of variance of the trade price matches figure IV-3 From 98e7ede27ad687bbfc036229bfddc7a7a9abbedd Mon Sep 17 00:00:00 2001 From: rht Date: Mon, 27 Nov 2023 08:53:41 -0500 Subject: [PATCH 052/116] refactor: Simplify Sugarscape resource agents This combines the Sugar and Spice resources into 1 resource class. --- .../sugarscape_g1mt/sugarscape_g1mt/model.py | 27 +++----- .../sugarscape_g1mt/resource_agents.py | 37 +++-------- .../sugarscape_g1mt/trader_agents.py | 66 ++++--------------- 3 files changed, 31 insertions(+), 99 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index dd27da23ba9..e9da521ac33 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -1,7 +1,7 @@ import mesa import numpy as np -from .resource_agents import Spice, Sugar +from .resource_agents import Resource from .trader_agents import Trader @@ -93,18 +93,11 @@ def __init__( agent_id = 0 for _, (x, y) in self.grid.coord_iter(): max_sugar = sugar_distribution[x, y] - if max_sugar > 0: - sugar = Sugar(agent_id, self, (x, y), max_sugar) - self.schedule.add(sugar) - self.grid.place_agent(sugar, (x, y)) - agent_id += 1 - max_spice = spice_distribution[x, y] - if max_spice > 0: - spice = Spice(agent_id, self, (x, y), max_spice) - self.schedule.add(spice) - self.grid.place_agent(spice, (x, y)) - agent_id += 1 + resource = Resource(agent_id, self, (x, y), max_sugar, max_spice) + self.schedule.add(resource) + self.grid.place_agent(resource, (x, y)) + agent_id += 1 for i in range(self.initial_population): # get agent position @@ -157,13 +150,9 @@ def step(self): Unique step function that does staged activation of sugar and spice and then randomly activates traders """ - # step Sugar agents - for sugar in self.schedule.agents_by_type[Sugar].values(): - sugar.step() - - # step Spice agents - for spice in self.schedule.agents_by_type[Spice].values(): - spice.step() + # step Resource agents + for resource in self.schedule.agents_by_type[Resource].values(): + resource.step() # step trader agents # to account for agent death and removal we need a seperate data strcuture to diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py index 6d78a93ec71..18d11cd6e51 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py @@ -1,43 +1,26 @@ import mesa -class Sugar(mesa.Agent): +class Resource(mesa.Agent): """ - Sugar: - - contains an amount of sugar + Resource: + - contains an amount of sugar and spice - grows 1 amount of sugar at each turn - """ - - def __init__(self, unique_id, model, pos, max_sugar): - super().__init__(unique_id, model) - self.pos = pos - self.amount = max_sugar - self.max_sugar = max_sugar - - def step(self): - """ - Sugar growth function, adds one unit of sugar each step until - max amount - """ - self.amount = min([self.max_sugar, self.amount + 1]) - - -class Spice(mesa.Agent): - """ - Spice: - - contains an amount of spice - grows 1 amount of spice at each turn """ - def __init__(self, unique_id, model, pos, max_spice): + def __init__(self, unique_id, model, pos, max_sugar, max_spice): super().__init__(unique_id, model) self.pos = pos - self.amount = max_spice + self.sugar_amount = max_sugar + self.max_sugar = max_sugar + self.spice_amount = max_spice self.max_spice = max_spice def step(self): """ - Spice growth function, adds one unit of spice each step until + Growth function, adds one unit of sugar and spice each step up to max amount """ - self.amount = min([self.max_spice, self.amount + 1]) + 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/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py index e396f72ff1d..96bc8c5b391 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py @@ -2,7 +2,7 @@ import mesa -from .resource_agents import Spice, Sugar +from .resource_agents import Resource # Helper function @@ -50,47 +50,12 @@ def __init__( self.prices = [] self.trade_partners = [] - def get_sugar(self, pos): - """ - used in self.get_sugar_amount() - """ - - this_cell = self.model.grid.get_cell_list_contents(pos) - for agent in this_cell: - if type(agent) is Sugar: - return agent - return None - - def get_sugar_amount(self, pos): - """ - used in self.move() as part of self.calculate_welfare() - """ - - sugar_patch = self.get_sugar(pos) - if sugar_patch: - return sugar_patch.amount - return 0 - - def get_spice(self, pos): - """ - used in self.get_spice_amount() - """ - + def get_resource(self, pos): this_cell = self.model.grid.get_cell_list_contents(pos) for agent in this_cell: - if type(agent) is Spice: + if type(agent) is Resource: return agent - return None - - def get_spice_amount(self, pos): - """ - used in self.move() as part of self.calculate_welfare() - """ - - spice_patch = self.get_spice(pos) - if spice_patch: - return spice_patch.amount - return 0 + raise Exception(f"Resource agent not found in the position {pos}") def get_trader(self, pos): """ @@ -292,8 +257,8 @@ def move(self): welfares = [ self.calculate_welfare( - self.sugar + self.get_sugar_amount(pos), - self.spice + self.get_spice_amount(pos), + self.sugar + self.get_resource(pos).sugar_amount, + self.spice + self.get_resource(pos).spice_amount, ) for pos in neighbors ] @@ -323,20 +288,15 @@ def move(self): self.model.grid.move_agent(self, final_candidate) def eat(self): - # get sugar - sugar_patch = self.get_sugar(self.pos) - - if sugar_patch: - self.sugar += sugar_patch.amount - sugar_patch.amount = 0 + patch = self.get_resource(self.pos) + if patch.sugar_amount > 0: + self.sugar += patch.sugar_amount + patch.sugar_amount = 0 self.sugar -= self.metabolism_sugar - # get_spice - spice_patch = self.get_spice(self.pos) - - if spice_patch: - self.spice += spice_patch.amount - spice_patch.amount = 0 + if patch.spice_amount > 0: + self.spice += patch.spice_amount + patch.spice_amount = 0 self.spice -= self.metabolism_spice def maybe_die(self): From 2cb5a0667bfca78725bb3a80cf07bf6ee71c1a8f Mon Sep 17 00:00:00 2001 From: Jackie Kazil Date: Tue, 26 Dec 2023 22:15:15 -0600 Subject: [PATCH 053/116] Fix epstein_civil_voilence reqs txt. --- examples/advanced/epstein_civil_violence/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/advanced/epstein_civil_violence/requirements.txt b/examples/advanced/epstein_civil_violence/requirements.txt index 42445310f44..da2b9972efd 100644 --- a/examples/advanced/epstein_civil_violence/requirements.txt +++ b/examples/advanced/epstein_civil_violence/requirements.txt @@ -1,3 +1,3 @@ jupyter matplotlib -mesa~=1.2 +mesa~=2.0 From 86fcb989a690b66be62b9a8fbd602bbea70749df Mon Sep 17 00:00:00 2001 From: Jackie Kazil Date: Tue, 26 Dec 2023 21:46:29 -0600 Subject: [PATCH 054/116] Fix conways_game reqs txt. --- examples/basic/conways_game_of_life/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/basic/conways_game_of_life/requirements.txt b/examples/basic/conways_game_of_life/requirements.txt index 1a7fa12e2a4..ecd07eafe6f 100644 --- a/examples/basic/conways_game_of_life/requirements.txt +++ b/examples/basic/conways_game_of_life/requirements.txt @@ -1 +1 @@ -mesa~=1.2 \ No newline at end of file +mesa~=2.0 \ No newline at end of file From 27a74a18f7f04a4f1aa59b06313edba46b27bcd8 Mon Sep 17 00:00:00 2001 From: Corvince Date: Wed, 1 Nov 2023 16:05:04 +0100 Subject: [PATCH 055/116] Always use relative imports and fix examples --- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index e9da521ac33..aad58c769e6 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -1,3 +1,4 @@ +from pathlib import Path import mesa import numpy as np @@ -87,7 +88,7 @@ def __init__( ) # read in landscape file from supplmentary material - sugar_distribution = np.genfromtxt("sugarscape_g1mt/sugar-map.txt") + sugar_distribution = np.genfromtxt(Path(__file__).parent / "sugar-map.txt") spice_distribution = np.flip(sugar_distribution, 1) agent_id = 0 From 54d44e017d0780da417abcc53da5eaae5b691f40 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 11 Jan 2024 02:12:32 -0500 Subject: [PATCH 056/116] Apply ruff --fix . --- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py | 1 + examples/advanced/wolf_sheep/wolf_sheep/server.py | 1 - examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index aad58c769e6..ae93ac4df86 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -1,4 +1,5 @@ from pathlib import Path + import mesa import numpy as np diff --git a/examples/advanced/wolf_sheep/wolf_sheep/server.py b/examples/advanced/wolf_sheep/wolf_sheep/server.py index 7b1b831a574..112c1a2dfda 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/server.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/server.py @@ -1,5 +1,4 @@ import mesa - from wolf_sheep.agents import GrassPatch, Sheep, Wolf from wolf_sheep.model import WolfSheep diff --git a/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py b/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py index 0ba480ccdae..d2340fedba3 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py @@ -7,7 +7,6 @@ from mesa.space import MultiGrid from mesa.time import RandomActivation from mesa.visualization.TextVisualization import TextGrid, TextVisualization - from wolf_sheep.random_walk import RandomWalker From 1672af7e9566e8c47199fe5daca908563b48319c Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 11 Jan 2024 02:23:17 -0500 Subject: [PATCH 057/116] Apply lint fixes --- .../epstein_civil_violence/Epstein Civil Violence.ipynb | 3 --- examples/advanced/pd_grid/analysis.ipynb | 9 +++------ examples/advanced/wolf_sheep/wolf_sheep/agents.py | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb b/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb index 2fe5ed25879..e5d4d9a2af3 100644 --- a/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +++ b/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb @@ -17,11 +17,8 @@ "metadata": {}, "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n", - "\n", "%matplotlib inline\n", "\n", - "from epstein_civil_violence.agent import Citizen, Cop\n", "from epstein_civil_violence.model import EpsteinCivilViolence" ] }, diff --git a/examples/advanced/pd_grid/analysis.ipynb b/examples/advanced/pd_grid/analysis.ipynb index 7f848318aac..1fe69759743 100644 --- a/examples/advanced/pd_grid/analysis.ipynb +++ b/examples/advanced/pd_grid/analysis.ipynb @@ -34,12 +34,9 @@ "metadata": {}, "outputs": [], "source": [ - "from pd_grid.model import PdGrid\n", - "\n", - "import numpy as np\n", - "\n", "import matplotlib.pyplot as plt\n", - "import matplotlib.gridspec\n", + "import numpy as np\n", + "from pd_grid.model import PdGrid\n", "\n", "%matplotlib inline" ] @@ -75,7 +72,7 @@ " grid[y][x] = 0\n", " ax.pcolormesh(grid, cmap=bwr, vmin=0, vmax=1)\n", " ax.axis(\"off\")\n", - " ax.set_title(\"Steps: {}\".format(model.schedule.steps))" + " ax.set_title(f\"Steps: {model.schedule.steps}\")" ] }, { diff --git a/examples/advanced/wolf_sheep/wolf_sheep/agents.py b/examples/advanced/wolf_sheep/wolf_sheep/agents.py index eef30d5475f..460c4abb131 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/agents.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/agents.py @@ -29,7 +29,7 @@ def step(self): # If there is grass available, eat it this_cell = self.model.grid.get_cell_list_contents([self.pos]) - grass_patch = [obj for obj in this_cell if isinstance(obj, GrassPatch)][0] + grass_patch = next(obj for obj in this_cell if isinstance(obj, GrassPatch)) if grass_patch.fully_grown: self.energy += self.model.sheep_gain_from_food grass_patch.fully_grown = False From b6f43075fd42dbf8dead6657e57a5fe61c6f4b4b Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 11 Jan 2024 02:23:17 -0500 Subject: [PATCH 058/116] Apply lint fixes --- examples/basic/boid_flockers/Flocker Test.ipynb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/basic/boid_flockers/Flocker Test.ipynb b/examples/basic/boid_flockers/Flocker Test.ipynb index 664019e51fc..82ecc47b99f 100644 --- a/examples/basic/boid_flockers/Flocker Test.ipynb +++ b/examples/basic/boid_flockers/Flocker Test.ipynb @@ -8,9 +8,8 @@ }, "outputs": [], "source": [ - "from boid_flockers.model import BoidFlockers\n", - "import numpy as np\n", "import matplotlib.pyplot as plt\n", + "from boid_flockers.model import BoidFlockers\n", "\n", "%matplotlib inline" ] From 2fe3bef5cb5759260858f0ef91360af9475c8ecf Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 11 Jan 2024 17:49:58 -0500 Subject: [PATCH 059/116] fix: Use new API to loop over agents_by_type --- .../advanced/sugarscape_g1mt/sugarscape_g1mt/model.py | 8 ++++---- examples/advanced/wolf_sheep/wolf_sheep/scheduler.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index ae93ac4df86..fbe9c8a6803 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -77,11 +77,11 @@ def __init__( "Trader": lambda m: m.schedule.get_type_count(Trader), "Trade Volume": lambda m: sum( len(a.trade_partners) - for a in m.schedule.agents_by_type[Trader].values() + for a in m.schedule.agents_by_type[Trader] ), "Price": lambda m: geometric_mean( flatten( - [a.prices for a in m.schedule.agents_by_type[Trader].values()] + [a.prices for a in m.schedule.agents_by_type[Trader]] ) ), }, @@ -142,7 +142,7 @@ def randomize_traders(self): puts traders in randomized list for step function """ - traders_shuffle = list(self.schedule.agents_by_type[Trader].values()) + traders_shuffle = list(self.schedule.agents_by_type[Trader]) self.random.shuffle(traders_shuffle) return traders_shuffle @@ -153,7 +153,7 @@ def step(self): and then randomly activates traders """ # step Resource agents - for resource in self.schedule.agents_by_type[Resource].values(): + for resource in self.schedule.agents_by_type[Resource]: resource.step() # step trader agents diff --git a/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py b/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py index 4279de716d0..9a7c7d7b27d 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py @@ -23,7 +23,7 @@ def get_type_count( that satisfy the filter function. """ count = 0 - for agent in self.agents_by_type[type_class].values(): + for agent in self.agents_by_type[type_class]: if filter_func is None or filter_func(agent): count += 1 return count From 9b8630db96263c9f67ccd01bb89427eda4197f01 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 11 Jan 2024 18:00:43 -0500 Subject: [PATCH 060/116] Add super().__init__() in model.__init__() --- examples/advanced/pd_grid/pd_grid/model.py | 1 + examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py | 1 + 2 files changed, 2 insertions(+) diff --git a/examples/advanced/pd_grid/pd_grid/model.py b/examples/advanced/pd_grid/pd_grid/model.py index d2445c88d61..b970c0f4c52 100644 --- a/examples/advanced/pd_grid/pd_grid/model.py +++ b/examples/advanced/pd_grid/pd_grid/model.py @@ -29,6 +29,7 @@ def __init__( Determines the agent activation regime. payoffs: (optional) Dictionary of (move, neighbor_move) payoffs. """ + super().__init__() self.grid = mesa.space.SingleGrid(width, height, torus=True) self.schedule_type = schedule_type self.schedule = self.schedule_types[self.schedule_type](self) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index fbe9c8a6803..eae4036ed3e 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -53,6 +53,7 @@ def __init__( vision_max=5, enable_trade=True, ): + super().__init__() # Initiate width and heigh of sugarscape self.width = width self.height = height From 5e2cd7d514fe02614e7511f623be3066fc024b3c Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 11 Jan 2024 18:29:44 -0500 Subject: [PATCH 061/116] Apply ruff format --- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index eae4036ed3e..3f7c08d7731 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -77,13 +77,10 @@ def __init__( model_reporters={ "Trader": lambda m: m.schedule.get_type_count(Trader), "Trade Volume": lambda m: sum( - len(a.trade_partners) - for a in m.schedule.agents_by_type[Trader] + len(a.trade_partners) for a in m.schedule.agents_by_type[Trader] ), "Price": lambda m: geometric_mean( - flatten( - [a.prices for a in m.schedule.agents_by_type[Trader]] - ) + flatten([a.prices for a in m.schedule.agents_by_type[Trader]]) ), }, agent_reporters={"Trade Network": lambda a: get_trade(a)}, From fe8907728bfb1477db7a034409d4ac65367f3004 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 11 Jan 2024 18:00:43 -0500 Subject: [PATCH 062/116] Add super().__init__() in model.__init__() --- examples/basic/boid_flockers/boid_flockers/model.py | 1 + .../boltzmann_wealth_model/boltzmann_wealth_model/model.py | 1 + .../basic/conways_game_of_life/conways_game_of_life/model.py | 1 + examples/basic/schelling/model.py | 3 +-- examples/basic/virus_on_network/virus_on_network/model.py | 1 + 5 files changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/basic/boid_flockers/boid_flockers/model.py b/examples/basic/boid_flockers/boid_flockers/model.py index 3f7428f0924..22e9dce6711 100644 --- a/examples/basic/boid_flockers/boid_flockers/model.py +++ b/examples/basic/boid_flockers/boid_flockers/model.py @@ -40,6 +40,7 @@ def __init__( keep from any other cohere, separate, match: factors for the relative importance of the three drives.""" + super().__init__() self.population = population self.vision = vision self.speed = speed diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py index 0f61b8838d6..11a3e95878a 100644 --- a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -18,6 +18,7 @@ class BoltzmannWealthModel(mesa.Model): """ def __init__(self, N=100, width=10, height=10): + super().__init__() self.num_agents = N self.grid = mesa.space.MultiGrid(width, height, True) self.schedule = mesa.time.RandomActivation(self) diff --git a/examples/basic/conways_game_of_life/conways_game_of_life/model.py b/examples/basic/conways_game_of_life/conways_game_of_life/model.py index bf2204e0251..f6c9637a67e 100644 --- a/examples/basic/conways_game_of_life/conways_game_of_life/model.py +++ b/examples/basic/conways_game_of_life/conways_game_of_life/model.py @@ -13,6 +13,7 @@ def __init__(self, width=50, height=50): """ Create a new playing area of (width, height) cells. """ + super().__init__() # Set up the grid and schedule. diff --git a/examples/basic/schelling/model.py b/examples/basic/schelling/model.py index c7fe766ac14..2eb148df6fe 100644 --- a/examples/basic/schelling/model.py +++ b/examples/basic/schelling/model.py @@ -38,8 +38,7 @@ class Schelling(mesa.Model): """ def __init__(self, width=20, height=20, density=0.8, minority_pc=0.2, homophily=3): - """ """ - + super().__init__() self.width = width self.height = height self.density = density diff --git a/examples/basic/virus_on_network/virus_on_network/model.py b/examples/basic/virus_on_network/virus_on_network/model.py index 961c56bcf9f..2cee39ccc17 100644 --- a/examples/basic/virus_on_network/virus_on_network/model.py +++ b/examples/basic/virus_on_network/virus_on_network/model.py @@ -40,6 +40,7 @@ def __init__( recovery_chance=0.3, gain_resistance_chance=0.5, ): + super().__init__() self.num_nodes = num_nodes prob = avg_node_degree / self.num_nodes self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=prob) From 76d3c0aa5cf9712c651ef07f0ec3b1d3721670b9 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 11 Jan 2024 18:54:52 -0500 Subject: [PATCH 063/116] wolf_sheep: Handle the case when type_class not in agents_by_type --- examples/advanced/wolf_sheep/wolf_sheep/scheduler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py b/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py index 9a7c7d7b27d..a29a5a770eb 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py @@ -22,6 +22,8 @@ def get_type_count( Returns the current number of agents of certain type in the queue that satisfy the filter function. """ + if type_class not in self.agents_by_type: + return 0 count = 0 for agent in self.agents_by_type[type_class]: if filter_func is None or filter_func(agent): From d08a26cf67d11b717adcb8c3c715d9cc637e12c6 Mon Sep 17 00:00:00 2001 From: rht Date: Mon, 8 Jan 2024 19:03:01 -0500 Subject: [PATCH 064/116] Boid flockers: Add Solara viz file --- examples/basic/boid_flockers/app.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 examples/basic/boid_flockers/app.py diff --git a/examples/basic/boid_flockers/app.py b/examples/basic/boid_flockers/app.py new file mode 100644 index 00000000000..30b9fa28ff6 --- /dev/null +++ b/examples/basic/boid_flockers/app.py @@ -0,0 +1,25 @@ +from boid_flockers.model import BoidFlockers +from mesa.experimental import JupyterViz + + +def boid_draw(agent): + return {"color": "tab:red"} + + +model_params = { + "population": 100, + "width": 100, + "height": 100, + "speed": 5, + "vision": 10, + "separation": 2, +} + +page = JupyterViz( + BoidFlockers, + model_params, + measures=[], + name="BoidFlockers", + agent_portrayal=boid_draw, +) +page # noqa From 985e889e7d9880c1762f06a23a72aa1364b1bf1b Mon Sep 17 00:00:00 2001 From: rht Date: Wed, 24 Jan 2024 04:20:44 -0500 Subject: [PATCH 065/116] Revert "fix: Use new API to loop over agents_by_type" This reverts commit 2fe3bef5cb5759260858f0ef91360af9475c8ecf. --- .../advanced/sugarscape_g1mt/sugarscape_g1mt/model.py | 11 +++++++---- examples/advanced/wolf_sheep/wolf_sheep/scheduler.py | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index 3f7c08d7731..6e9629db672 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -77,10 +77,13 @@ def __init__( model_reporters={ "Trader": lambda m: m.schedule.get_type_count(Trader), "Trade Volume": lambda m: sum( - len(a.trade_partners) for a in m.schedule.agents_by_type[Trader] + len(a.trade_partners) + for a in m.schedule.agents_by_type[Trader].values() ), "Price": lambda m: geometric_mean( - flatten([a.prices for a in m.schedule.agents_by_type[Trader]]) + flatten( + [a.prices for a in m.schedule.agents_by_type[Trader].values()] + ) ), }, agent_reporters={"Trade Network": lambda a: get_trade(a)}, @@ -140,7 +143,7 @@ def randomize_traders(self): puts traders in randomized list for step function """ - traders_shuffle = list(self.schedule.agents_by_type[Trader]) + traders_shuffle = list(self.schedule.agents_by_type[Trader].values()) self.random.shuffle(traders_shuffle) return traders_shuffle @@ -151,7 +154,7 @@ def step(self): and then randomly activates traders """ # step Resource agents - for resource in self.schedule.agents_by_type[Resource]: + for resource in self.schedule.agents_by_type[Resource].values(): resource.step() # step trader agents diff --git a/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py b/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py index a29a5a770eb..97424a553a8 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py @@ -25,7 +25,7 @@ def get_type_count( if type_class not in self.agents_by_type: return 0 count = 0 - for agent in self.agents_by_type[type_class]: + for agent in self.agents_by_type[type_class].values(): if filter_func is None or filter_func(agent): count += 1 return count From d80f17ec24554613a616f809c2518f44ea1332a4 Mon Sep 17 00:00:00 2001 From: rht Date: Wed, 24 Jan 2024 08:12:32 -0500 Subject: [PATCH 066/116] fix: Ensure schedule.steps is always incremented before data collection --- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index 6e9629db672..f40a3c62a21 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -171,6 +171,9 @@ def step(self): if not self.enable_trade: # If trade is not enabled, return early + self.schedule.steps += ( + 1 # important for data collector to track number of steps + ) self.datacollector.collect(self) return From 999dccecac918d8a7b455e06be09ad1333b3215a Mon Sep 17 00:00:00 2001 From: rht Date: Wed, 24 Jan 2024 14:11:51 -0500 Subject: [PATCH 067/116] sugarscape_g1mt: Remove dependence on model.schedule.steps --- .../sugarscape_g1mt/sugarscape_g1mt/model.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index f40a3c62a21..f88f1c2c6a3 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -171,9 +171,7 @@ def step(self): if not self.enable_trade: # If trade is not enabled, return early - self.schedule.steps += ( - 1 # important for data collector to track number of steps - ) + self._steps += 1 self.datacollector.collect(self) return @@ -182,10 +180,7 @@ def step(self): for agent in trader_shuffle: agent.trade_with_neighbors() - self.schedule.steps += ( - 1 # important for data collector to track number of steps - ) - + self._steps += 1 # collect model level data self.datacollector.collect(self) """ @@ -201,11 +196,11 @@ def step(self): """ # Need to remove excess data # Create local variable to store trade data - agent_trades = self.datacollector._agent_records[self.schedule.steps] + agent_trades = self.datacollector._agent_records[self._steps] # Get rid of all None to reduce data storage needs agent_trades = [agent for agent in agent_trades if agent[2] is not None] # Reassign the dictionary value with lean trade data - self.datacollector._agent_records[self.schedule.steps] = agent_trades + self.datacollector._agent_records[self._steps] = agent_trades def run_model(self, step_count=1000): for i in range(step_count): From b51c09e5c1d88e1ae11e63fc232641e7af9a00a8 Mon Sep 17 00:00:00 2001 From: rht Date: Wed, 24 Jan 2024 13:56:09 -0500 Subject: [PATCH 068/116] fix: Use resource agent for Sugarscape G1MT server.py --- .../sugarscape_g1mt/sugarscape_g1mt/server.py | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py index fbc10ce6c54..3ef0066883f 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py @@ -1,7 +1,7 @@ import mesa from .model import SugarscapeG1mt -from .resource_agents import Spice, Sugar +from .resource_agents import Resource from .trader_agents import Trader sugar_dic = {4: "#005C00", 3: "#008300", 2: "#00AA00", 1: "#00F800"} @@ -21,9 +21,18 @@ def Agent_portrayal(agent): "Color": "#FF0A01", } - elif isinstance(agent, Sugar): - color = sugar_dic[agent.amount] if agent.amount != 0 else "#D6F5D6" - layer = 1 if agent.amount > 2 else 0 + 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", @@ -33,18 +42,6 @@ def Agent_portrayal(agent): "h": 1, } - elif isinstance(agent, Spice): - color = spice_dic[agent.amount] if agent.amount != 0 else "#D6F5D6" - layer = 1 if agent.amount > 2 else 0 - return { - "Color": color, - "Shape": "rect", - "Filled": "true", - "Layer": 0, - "w": 1, - "h": 1, - } - return {} From c8ba9e93460591dd5bcd4ed8f071ce12f058e6c6 Mon Sep 17 00:00:00 2001 From: Achal Jain Date: Thu, 22 Feb 2024 22:02:35 +0530 Subject: [PATCH 069/116] Improve Virus on Network documentation (#100) Change 1: Make the ReadMe more descriptive - Added a more detailed explanation of the model - Added an extra method of running the model Change 2: Doc Strings and Cleaning Made small changes to make the code more readable and easy to understand at first glance NOTE: In the model, after running, some of the nodes in the graph are isolated. In my opinion it should be connected (if it was intentional then fine but in case not it needs to be updated with a different graph creation technique) --- examples/basic/virus_on_network/README.md | 17 +++- examples/basic/virus_on_network/app.py | 1 - .../virus_on_network/__init__.py | 0 .../virus_on_network/model.py | 8 +- .../virus_on_network/server.py | 81 ++++++++++--------- 5 files changed, 68 insertions(+), 39 deletions(-) delete mode 100644 examples/basic/virus_on_network/virus_on_network/__init__.py diff --git a/examples/basic/virus_on_network/README.md b/examples/basic/virus_on_network/README.md index b9fd1e94ecb..f6a51fd580b 100644 --- a/examples/basic/virus_on_network/README.md +++ b/examples/basic/virus_on_network/README.md @@ -2,7 +2,13 @@ ## Summary -This model is based on the NetLogo model "Virus on Network". +This model is based on the NetLogo model "Virus on Network". It demonstrates the spread of a virus through a network and follows the SIR model, commonly seen in epidemiology. + +The SIR model is one of the simplest compartmental models, and many models are derivatives of this basic form. The model consists of three compartments: + +S: The number of susceptible individuals. When a susceptible and an infectious individual come into "infectious contact", the susceptible individual contracts the disease and transitions to the infectious compartment. +I: The number of infectious individuals. These are individuals who have been infected and are capable of infecting susceptible individuals. +R for the number of removed (and immune) or deceased individuals. These are individuals who have been infected and have either recovered from the disease and entered the removed compartment, or died. It is assumed that the number of deaths is negligible with respect to the total population. This compartment may also be called "recovered" or "resistant". For more information about this model, read the NetLogo's web page: http://ccl.northwestern.edu/netlogo/models/VirusonaNetwork. @@ -26,6 +32,15 @@ To run the model interactively, run ``mesa runserver`` in this directory. e.g. Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run. +or + +Directly run the file ``run.py`` in the terminal. e.g. + +``` + $ python run.py +``` + + ## Files * ``run.py``: Launches a model visualization server. diff --git a/examples/basic/virus_on_network/app.py b/examples/basic/virus_on_network/app.py index da3e5dda9be..cefb0059cb9 100644 --- a/examples/basic/virus_on_network/app.py +++ b/examples/basic/virus_on_network/app.py @@ -48,7 +48,6 @@ def get_resistant_susceptible_ratio(model): def make_plot(model): # This is for the case when we want to plot multiple measures in 1 figure. - # We could incorporate this into core Mesa. fig = Figure() ax = fig.subplots() measures = ["Infected", "Susceptible", "Resistant"] diff --git a/examples/basic/virus_on_network/virus_on_network/__init__.py b/examples/basic/virus_on_network/virus_on_network/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/examples/basic/virus_on_network/virus_on_network/model.py b/examples/basic/virus_on_network/virus_on_network/model.py index 2cee39ccc17..a33e7545861 100644 --- a/examples/basic/virus_on_network/virus_on_network/model.py +++ b/examples/basic/virus_on_network/virus_on_network/model.py @@ -28,7 +28,9 @@ def number_resistant(model): class VirusOnNetwork(mesa.Model): - """A virus model with some number of agents""" + """ + A virus model with some number of agents + """ def __init__( self, @@ -104,6 +106,10 @@ def run_model(self, n): class VirusAgent(mesa.Agent): + """ + Individual Agent definition and its properties/interaction methods + """ + def __init__( self, unique_id, diff --git a/examples/basic/virus_on_network/virus_on_network/server.py b/examples/basic/virus_on_network/virus_on_network/server.py index c49306a9867..afb5c1954d2 100644 --- a/examples/basic/virus_on_network/virus_on_network/server.py +++ b/examples/basic/virus_on_network/virus_on_network/server.py @@ -49,7 +49,11 @@ def get_agents(source, target): return portrayal -network = mesa.visualization.NetworkModule(network_portrayal, 500, 500) +network = mesa.visualization.NetworkModule( + portrayal_method=network_portrayal, + canvas_height=500, + canvas_width=500, +) chart = mesa.visualization.ChartModule( [ {"Label": "Infected", "Color": "#FF0000"}, @@ -71,63 +75,68 @@ def get_resistant_susceptible_ratio(model): model_params = { "num_nodes": mesa.visualization.Slider( - "Number of agents", - 10, - 10, - 100, - 1, + name="Number of agents", + value=10, + min_value=10, + max_value=100, + step=1, description="Choose how many agents to include in the model", ), "avg_node_degree": mesa.visualization.Slider( - "Avg Node Degree", 3, 3, 8, 1, description="Avg Node Degree" + name="Avg Node Degree", + value=3, + min_value=3, + max_value=8, + step=1, + description="Avg Node Degree", ), "initial_outbreak_size": mesa.visualization.Slider( - "Initial Outbreak Size", - 1, - 1, - 10, - 1, + name="Initial Outbreak Size", + value=1, + min_value=1, + max_value=10, + step=1, description="Initial Outbreak Size", ), "virus_spread_chance": mesa.visualization.Slider( - "Virus Spread Chance", - 0.4, - 0.0, - 1.0, - 0.1, + name="Virus Spread Chance", + value=0.4, + min_value=0.0, + max_value=1.0, + step=0.1, description="Probability that susceptible neighbor will be infected", ), "virus_check_frequency": mesa.visualization.Slider( - "Virus Check Frequency", - 0.4, - 0.0, - 1.0, - 0.1, + name="Virus Check Frequency", + value=0.4, + min_value=0.0, + max_value=1.0, + step=0.1, description="Frequency the nodes check whether they are infected by a virus", ), "recovery_chance": mesa.visualization.Slider( - "Recovery Chance", - 0.3, - 0.0, - 1.0, - 0.1, + name="Recovery Chance", + value=0.3, + min_value=0.0, + max_value=1.0, + step=0.1, description="Probability that the virus will be removed", ), "gain_resistance_chance": mesa.visualization.Slider( - "Gain Resistance Chance", - 0.5, - 0.0, - 1.0, - 0.1, + name="Gain Resistance Chance", + value=0.5, + min_value=0.0, + max_value=1.0, + step=0.1, description="Probability that a recovered agent will become " "resistant to this virus in the future", ), } server = mesa.visualization.ModularServer( - VirusOnNetwork, - [network, get_resistant_susceptible_ratio, chart], - "Virus Model", - model_params, + model_cls=VirusOnNetwork, + visualization_elements=[network, get_resistant_susceptible_ratio, chart], + name="Virus on Network Model", + model_params=model_params, ) server.port = 8521 From af856c66ebc147e7b8d26ef0fc369e4e3d762782 Mon Sep 17 00:00:00 2001 From: Achal Jain Date: Sat, 24 Feb 2024 22:50:45 +0530 Subject: [PATCH 070/116] Improve boid flocker model and documentation (#101) * Improve boid flocker model * Update Readme.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update model.py * Update server.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- examples/basic/boid_flockers/Readme.md | 47 ++++--- examples/basic/boid_flockers/app.py | 4 +- .../boid_flockers/SimpleContinuousModule.py | 5 +- .../basic/boid_flockers/boid_flockers/boid.py | 104 --------------- .../boid_flockers/boid_flockers/model.py | 123 ++++++++++++++++-- .../boid_flockers/boid_flockers/server.py | 55 +++++++- .../boid_flockers/simple_continuous_canvas.js | 1 - 7 files changed, 193 insertions(+), 146 deletions(-) delete mode 100644 examples/basic/boid_flockers/boid_flockers/boid.py diff --git a/examples/basic/boid_flockers/Readme.md b/examples/basic/boid_flockers/Readme.md index cb3292b4f68..d1f4a987399 100644 --- a/examples/basic/boid_flockers/Readme.md +++ b/examples/basic/boid_flockers/Readme.md @@ -1,34 +1,47 @@ -# Flockers +# Boids Flockers + +## Summary An implementation of Craig Reynolds's Boids flocker model. Agents (simulated birds) try to fly towards the average position of their neighbors and in the same direction as them, while maintaining a minimum distance. This produces flocking behavior. This model tests Mesa's continuous space feature, and uses numpy arrays to represent vectors. It also demonstrates how to create custom visualization components. +## Installation + +To install the dependencies use pip and the requirements.txt in this directory. e.g. + +``` + $ pip install -r requirements.txt +``` + ## How to Run -Launch the model: +* To launch the visualization interactively, run ``mesa runserver`` in this directory. e.g. + +``` +$ mesa runserver +``` + +or + +Directly run the file ``run.py`` in the terminal. e.g. + ``` - $ python Flocker_Server.py + $ python run.py ``` -Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run. +* Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run. ## Files -* [flockers/model.py](flockers/model.py): Core model file; contains the BoidModel class. -* [flockers/boid.py](flockers/boid.py): The Boid agent class. -* [flockers/SimpleContinuousModule.py](flockers/SimpleContinuousModule.py): Defines ``SimpleCanvas``, the Python side of a custom visualization module for drawing agents with continuous positions. -* [flockers/simple_continuous_canvas.js](flockers/simple_continuous_canvas.js): JavaScript side of the ``SimpleCanvas`` visualization module; takes the output generated by the Python ``SimpleCanvas`` element and draws it in the browser window via HTML5 canvas. -* [flockers/server.py](flockers/server.py): Sets up the visualization; uses the SimpleCanvas element defined above +* [boid_flockers/model.py](boid_flockers/model.py): Core model file; contains the Boid Model and Boid Agent class. +* [boid_flockers/SimpleContinuousModule.py](boid_flockers/SimpleContinuousModule.py): Defines ``SimpleCanvas``, the Python side of a custom visualization module for drawing agents with continuous positions. +* [boid_flockers/simple_continuous_canvas.js](boid_flockers/simple_continuous_canvas.js): JavaScript side of the ``SimpleCanvas`` visualization module; takes the output generated by the Python ``SimpleCanvas`` element and draws it in the browser window via HTML5 canvas. +* [boid_flockers/server.py](boid_flockers/server.py): Sets up the visualization; uses the SimpleCanvas element defined above * [run.py](run.py) Launches the visualization. -* [Flocker Test.ipynb](Flocker Test.ipynb): Tests the model in a Jupyter notebook. +* [Flocker_Test.ipynb](Flocker_Test.ipynb): Tests the model in a Jupyter notebook. ## Further Reading -======= -* Launch the visualization -``` -$ mesa runserver -``` -* Visit your browser: http://127.0.0.1:8521/ -* In your browser hit *run* +The following link can be visited for more information on the boid flockers model: +https://cs.stanford.edu/people/eroberts/courses/soco/projects/2008-09/modeling-natural-systems/boids.html diff --git a/examples/basic/boid_flockers/app.py b/examples/basic/boid_flockers/app.py index 30b9fa28ff6..5de317feff1 100644 --- a/examples/basic/boid_flockers/app.py +++ b/examples/basic/boid_flockers/app.py @@ -16,8 +16,8 @@ def boid_draw(agent): } page = JupyterViz( - BoidFlockers, - model_params, + model_class=BoidFlockers, + model_params=model_params, measures=[], name="BoidFlockers", agent_portrayal=boid_draw, diff --git a/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py b/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py index 3f3da5dd01e..eabf077c8f6 100644 --- a/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py +++ b/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py @@ -3,11 +3,8 @@ class SimpleCanvas(mesa.visualization.VisualizationElement): local_includes = ["boid_flockers/simple_continuous_canvas.js"] - portrayal_method = None - canvas_height = 500 - canvas_width = 500 - def __init__(self, portrayal_method, canvas_height=500, canvas_width=500): + def __init__(self, portrayal_method=None, canvas_height=500, canvas_width=500): """ Instantiate a new SimpleCanvas """ diff --git a/examples/basic/boid_flockers/boid_flockers/boid.py b/examples/basic/boid_flockers/boid_flockers/boid.py deleted file mode 100644 index f427f9ddbbc..00000000000 --- a/examples/basic/boid_flockers/boid_flockers/boid.py +++ /dev/null @@ -1,104 +0,0 @@ -import mesa -import numpy as np - - -class Boid(mesa.Agent): - """ - A Boid-style flocker agent. - - The agent follows three behaviors to flock: - - Cohesion: steering towards neighboring agents. - - Separation: avoiding getting too close to any other agent. - - Alignment: try to fly in the same direction as the neighbors. - - Boids have a vision that defines the radius in which they look for their - neighbors to flock with. Their speed (a scalar) and velocity (a vector) - define their movement. Separation is their desired minimum distance from - any other Boid. - """ - - def __init__( - self, - unique_id, - model, - pos, - speed, - velocity, - vision, - separation, - cohere=0.025, - separate=0.25, - match=0.04, - ): - """ - Create a new Boid flocker agent. - - Args: - unique_id: Unique agent identifyer. - pos: Starting position - speed: Distance to move per step. - heading: numpy vector for the Boid's direction of movement. - vision: Radius to look around for nearby Boids. - separation: Minimum distance to maintain from other Boids. - cohere: the relative importance of matching neighbors' positions - separate: the relative importance of avoiding close neighbors - match: the relative importance of matching neighbors' headings - """ - super().__init__(unique_id, model) - self.pos = np.array(pos) - self.speed = speed - self.velocity = velocity - self.vision = vision - self.separation = separation - self.cohere_factor = cohere - self.separate_factor = separate - self.match_factor = match - - def cohere(self, neighbors): - """ - Return the vector toward the center of mass of the local neighbors. - """ - cohere = np.zeros(2) - if neighbors: - for neighbor in neighbors: - cohere += self.model.space.get_heading(self.pos, neighbor.pos) - cohere /= len(neighbors) - return cohere - - def separate(self, neighbors): - """ - Return a vector away from any neighbors closer than separation dist. - """ - me = self.pos - them = (n.pos for n in neighbors) - separation_vector = np.zeros(2) - for other in them: - if self.model.space.get_distance(me, other) < self.separation: - separation_vector -= self.model.space.get_heading(me, other) - return separation_vector - - def match_heading(self, neighbors): - """ - Return a vector of the neighbors' average heading. - """ - match_vector = np.zeros(2) - if neighbors: - for neighbor in neighbors: - match_vector += neighbor.velocity - match_vector /= len(neighbors) - return match_vector - - def step(self): - """ - Get the Boid's neighbors, compute the new vector, and move accordingly. - """ - - neighbors = self.model.space.get_neighbors(self.pos, self.vision, False) - self.velocity += ( - self.cohere(neighbors) * self.cohere_factor - + self.separate(neighbors) * self.separate_factor - + self.match_heading(neighbors) * self.match_factor - ) / 2 - self.velocity /= np.linalg.norm(self.velocity) - new_pos = self.pos + self.velocity * self.speed - self.model.space.move_agent(self, new_pos) diff --git a/examples/basic/boid_flockers/boid_flockers/model.py b/examples/basic/boid_flockers/boid_flockers/model.py index 22e9dce6711..ff443b52c7b 100644 --- a/examples/basic/boid_flockers/boid_flockers/model.py +++ b/examples/basic/boid_flockers/boid_flockers/model.py @@ -8,7 +8,108 @@ import mesa import numpy as np -from .boid import Boid + +class Boid(mesa.Agent): + """ + A Boid-style flocker agent. + + The agent follows three behaviors to flock: + - Cohesion: steering towards neighboring agents. + - Separation: avoiding getting too close to any other agent. + - Alignment: try to fly in the same direction as the neighbors. + + Boids have a vision that defines the radius in which they look for their + neighbors to flock with. Their speed (a scalar) and direction (a vector) + define their movement. Separation is their desired minimum distance from + any other Boid. + """ + + def __init__( + self, + unique_id, + model, + pos, + speed, + direction, + vision, + separation, + cohere=0.025, + separate=0.25, + match=0.04, + ): + """ + Create a new Boid flocker agent. + + Args: + unique_id: Unique agent identifyer. + pos: Starting position + speed: Distance to move per step. + direction: numpy vector for the Boid's direction of movement. + vision: Radius to look around for nearby Boids. + separation: Minimum distance to maintain from other Boids. + cohere: the relative importance of matching neighbors' positions + separate: the relative importance of avoiding close neighbors + match: the relative importance of matching neighbors' headings + """ + super().__init__(unique_id, model) + self.pos = np.array(pos) + self.speed = speed + self.direction = direction + self.vision = vision + self.separation = separation + self.cohere_factor = cohere + self.separate_factor = separate + self.match_factor = match + self.neighbors = None + + def cohere(self): + """ + Return the vector toward the center of mass of the local neighbors. + """ + cohere = np.zeros(2) + if self.neighbors: + for neighbor in self.neighbors: + cohere += self.model.space.get_heading(self.pos, neighbor.pos) + cohere /= len(self.neighbors) + return cohere + + def separate(self): + """ + Return a vector away from any neighbors closer than separation dist. + """ + me = self.pos + them = (n.pos for n in self.neighbors) + separation_vector = np.zeros(2) + for other in them: + if self.model.space.get_distance(me, other) < self.separation: + separation_vector -= self.model.space.get_heading(me, other) + return separation_vector + + def match_heading(self): + """ + Return a vector of the neighbors' average heading. + """ + match_vector = np.zeros(2) + if self.neighbors: + for neighbor in self.neighbors: + match_vector += neighbor.direction + match_vector /= len(self.neighbors) + return match_vector + + def step(self): + """ + Get the Boid's neighbors, compute the new vector, and move accordingly. + """ + + self.neighbors = self.model.space.get_neighbors(self.pos, self.vision, False) + self.direction += ( + self.cohere() * self.cohere_factor + + self.separate() * self.separate_factor + + self.match_heading() * self.match_factor + ) / 2 + self.direction /= np.linalg.norm(self.direction) + new_pos = self.pos + self.direction * self.speed + self.model.space.move_agent(self, new_pos) class BoidFlockers(mesa.Model): @@ -39,7 +140,8 @@ def __init__( separation: What's the minimum distance each Boid will attempt to keep from any other cohere, separate, match: factors for the relative importance of - the three drives.""" + the three drives. + """ super().__init__() self.population = population self.vision = vision @@ -49,7 +151,6 @@ def __init__( self.space = mesa.space.ContinuousSpace(width, height, True) self.factors = {"cohere": cohere, "separate": separate, "match": match} self.make_agents() - self.running = True def make_agents(self): """ @@ -59,15 +160,15 @@ def make_agents(self): x = self.random.random() * self.space.x_max y = self.random.random() * self.space.y_max pos = np.array((x, y)) - velocity = np.random.random(2) * 2 - 1 + direction = np.random.random(2) * 2 - 1 boid = Boid( - i, - self, - pos, - self.speed, - velocity, - self.vision, - self.separation, + unique_id=i, + model=self, + pos=pos, + speed=self.speed, + direction=direction, + vision=self.vision, + separation=self.separation, **self.factors, ) self.space.place_agent(boid, pos) diff --git a/examples/basic/boid_flockers/boid_flockers/server.py b/examples/basic/boid_flockers/boid_flockers/server.py index 4906df699c7..190c6533abb 100644 --- a/examples/basic/boid_flockers/boid_flockers/server.py +++ b/examples/basic/boid_flockers/boid_flockers/server.py @@ -5,19 +5,60 @@ def boid_draw(agent): - return {"Shape": "circle", "r": 2, "Filled": "true", "Color": "Red"} + if not agent.neighbors: # Only for the first Frame + neighbors = len(agent.model.space.get_neighbors(agent.pos, agent.vision, False)) + else: + neighbors = len(agent.neighbors) + if neighbors <= 1: + return {"Shape": "circle", "r": 2, "Filled": "true", "Color": "Red"} + elif neighbors >= 2: + return {"Shape": "circle", "r": 2, "Filled": "true", "Color": "Green"} -boid_canvas = SimpleCanvas(boid_draw, 500, 500) + +boid_canvas = SimpleCanvas( + portrayal_method=boid_draw, canvas_height=500, canvas_width=500 +) model_params = { - "population": 100, + "population": mesa.visualization.Slider( + name="Number of boids", + value=100, + min_value=10, + max_value=200, + step=10, + description="Choose how many agents to include in the model", + ), "width": 100, "height": 100, - "speed": 5, - "vision": 10, - "separation": 2, + "speed": mesa.visualization.Slider( + name="Speed of Boids", + value=5, + min_value=1, + max_value=20, + step=1, + description="How fast should the Boids move", + ), + "vision": mesa.visualization.Slider( + name="Vision of Bird (radius)", + value=10, + min_value=1, + max_value=50, + step=1, + description="How far around should each Boid look for its neighbors", + ), + "separation": mesa.visualization.Slider( + name="Minimum Separation", + value=2, + min_value=1, + max_value=20, + step=1, + description="What is the minimum distance each Boid will attempt to keep from any other", + ), } server = mesa.visualization.ModularServer( - BoidFlockers, [boid_canvas], "Boids", model_params + model_cls=BoidFlockers, + visualization_elements=[boid_canvas], + name="Boid Flocking Model", + model_params=model_params, ) diff --git a/examples/basic/boid_flockers/boid_flockers/simple_continuous_canvas.js b/examples/basic/boid_flockers/boid_flockers/simple_continuous_canvas.js index 20c0ded8732..812cadced8b 100644 --- a/examples/basic/boid_flockers/boid_flockers/simple_continuous_canvas.js +++ b/examples/basic/boid_flockers/boid_flockers/simple_continuous_canvas.js @@ -6,7 +6,6 @@ const ContinuousVisualization = function(width, height, context) { if (p.Shape == "circle") this.drawCircle(p.x, p.y, p.r, p.Color, p.Filled); }; - }; this.drawCircle = function(x, y, radius, color, fill) { From f855671122720818b6357041f9f717084e17d8de Mon Sep 17 00:00:00 2001 From: Achal Jain Date: Mon, 26 Feb 2024 03:39:28 +0530 Subject: [PATCH 071/116] Improve model to benchmark (#104) --- .../boid_flockers/boid_flockers/model.py | 74 +++++++------------ 1 file changed, 25 insertions(+), 49 deletions(-) diff --git a/examples/basic/boid_flockers/boid_flockers/model.py b/examples/basic/boid_flockers/boid_flockers/model.py index ff443b52c7b..6ebbf8aa8fa 100644 --- a/examples/basic/boid_flockers/boid_flockers/model.py +++ b/examples/basic/boid_flockers/boid_flockers/model.py @@ -33,15 +33,15 @@ def __init__( direction, vision, separation, - cohere=0.025, - separate=0.25, - match=0.04, + cohere=0.03, + separate=0.015, + match=0.05, ): """ Create a new Boid flocker agent. Args: - unique_id: Unique agent identifyer. + unique_id: Unique agent identifier. pos: Starting position speed: Distance to move per step. direction: numpy vector for the Boid's direction of movement. @@ -62,51 +62,26 @@ def __init__( self.match_factor = match self.neighbors = None - def cohere(self): - """ - Return the vector toward the center of mass of the local neighbors. - """ - cohere = np.zeros(2) - if self.neighbors: - for neighbor in self.neighbors: - cohere += self.model.space.get_heading(self.pos, neighbor.pos) - cohere /= len(self.neighbors) - return cohere - - def separate(self): - """ - Return a vector away from any neighbors closer than separation dist. - """ - me = self.pos - them = (n.pos for n in self.neighbors) - separation_vector = np.zeros(2) - for other in them: - if self.model.space.get_distance(me, other) < self.separation: - separation_vector -= self.model.space.get_heading(me, other) - return separation_vector - - def match_heading(self): - """ - Return a vector of the neighbors' average heading. - """ - match_vector = np.zeros(2) - if self.neighbors: - for neighbor in self.neighbors: - match_vector += neighbor.direction - match_vector /= len(self.neighbors) - return match_vector - def step(self): """ Get the Boid's neighbors, compute the new vector, and move accordingly. """ self.neighbors = self.model.space.get_neighbors(self.pos, self.vision, False) - self.direction += ( - self.cohere() * self.cohere_factor - + self.separate() * self.separate_factor - + self.match_heading() * self.match_factor - ) / 2 + n = 0 + match_vector, separation_vector, cohere = np.zeros((3, 2)) + for neighbor in self.neighbors: + n += 1 + heading = self.model.space.get_heading(self.pos, neighbor.pos) + cohere += heading + if self.model.space.get_distance(self.pos, neighbor.pos) < self.separation: + separation_vector -= heading + match_vector += neighbor.direction + n = max(n, 1) + cohere = cohere * self.cohere_factor + separation_vector = separation_vector * self.separate_factor + match_vector = match_vector * self.match_factor + self.direction += (cohere + separation_vector + match_vector) / n self.direction /= np.linalg.norm(self.direction) new_pos = self.pos + self.direction * self.speed self.model.space.move_agent(self, new_pos) @@ -119,15 +94,16 @@ class BoidFlockers(mesa.Model): def __init__( self, + seed=None, population=100, width=100, height=100, - speed=1, vision=10, - separation=2, - cohere=0.025, - separate=0.25, - match=0.04, + speed=1, + separation=1, + cohere=0.03, + separate=0.015, + match=0.05, ): """ Create a new Flockers model. @@ -142,7 +118,7 @@ def __init__( cohere, separate, match: factors for the relative importance of the three drives. """ - super().__init__() + super().__init__(seed=seed) self.population = population self.vision = vision self.speed = speed From b045e66b729537db6c7529e3bb8c38ac758e173b Mon Sep 17 00:00:00 2001 From: Achal Jain Date: Mon, 26 Feb 2024 18:10:22 +0530 Subject: [PATCH 072/116] Improve schelling model documentation (#103) * Improve schelling model * Update server.py * Update model.py * Update model.py * Update server.py * Update model.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update model.py --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- examples/basic/schelling/README.md | 8 +++++ examples/basic/schelling/model.py | 54 ++++++++++++++++++++---------- examples/basic/schelling/server.py | 31 ++++++++++++----- 3 files changed, 67 insertions(+), 26 deletions(-) diff --git a/examples/basic/schelling/README.md b/examples/basic/schelling/README.md index 64cc9c83295..fe8971f6fd1 100644 --- a/examples/basic/schelling/README.md +++ b/examples/basic/schelling/README.md @@ -22,6 +22,14 @@ To run the model interactively, run ``mesa runserver`` in this directory. e.g. $ mesa runserver ``` +or + +Directly run the file ``run.py`` in the terminal. e.g. + +``` + $ python run.py +``` + Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run. To view and run some example model analyses, launch the IPython Notebook and open ``analysis.ipynb``. Visualizing the analysis also requires [matplotlib](http://matplotlib.org/). diff --git a/examples/basic/schelling/model.py b/examples/basic/schelling/model.py index 2eb148df6fe..dfba4efb2b1 100644 --- a/examples/basic/schelling/model.py +++ b/examples/basic/schelling/model.py @@ -6,7 +6,7 @@ class SchellingAgent(mesa.Agent): Schelling segregation agent """ - def __init__(self, pos, model, agent_type): + def __init__(self, unique_id, model, agent_type): """ Create a new Schelling agent. @@ -15,13 +15,14 @@ def __init__(self, pos, model, agent_type): x, y: Agent initial location. agent_type: Indicator for the agent's type (minority=1, majority=0) """ - super().__init__(pos, model) - self.pos = pos + super().__init__(unique_id, model) self.type = agent_type def step(self): similar = 0 - for neighbor in self.model.grid.iter_neighbors(self.pos, True): + for neighbor in self.model.grid.iter_neighbors( + self.pos, moore=True, radius=self.model.radius + ): if neighbor.type == self.type: similar += 1 @@ -37,47 +38,64 @@ class Schelling(mesa.Model): Model class for the Schelling segregation model. """ - def __init__(self, width=20, height=20, density=0.8, minority_pc=0.2, homophily=3): - super().__init__() - self.width = width + def __init__( + self, + height=20, + width=20, + homophily=3, + radius=1, + density=0.8, + minority_pc=0.2, + seed=None, + ): + """ + Create a new Schelling model. + + Args: + width, height: Size of the space. + density: Initial Chance for a cell to populated + minority_pc: Chances for an agent to be in minority class + homophily: Minimum number of agents of same class needed to be happy + radius: Search radius for checking similarity + seed: Seed for Reproducibility + """ + + super().__init__(seed=seed) self.height = height + self.width = width self.density = density self.minority_pc = minority_pc self.homophily = homophily + self.radius = radius self.schedule = mesa.time.RandomActivation(self) self.grid = mesa.space.SingleGrid(width, height, torus=True) self.happy = 0 self.datacollector = mesa.DataCollector( - {"happy": "happy"}, # Model-level count of happy agents - # For testing purposes, agent's individual x and y - {"x": lambda a: a.pos[0], "y": lambda a: a.pos[1]}, + model_reporters={"happy": "happy"}, # Model-level count of happy agents ) # Set up agents # We use a grid iterator that returns # the coordinates of a cell as well as # its contents. (coord_iter) - for cell in self.grid.coord_iter(): - x, y = cell[1] + for _, pos in self.grid.coord_iter(): if self.random.random() < self.density: agent_type = 1 if self.random.random() < self.minority_pc else 0 - - agent = SchellingAgent((x, y), self, agent_type) - self.grid.place_agent(agent, (x, y)) + agent = SchellingAgent(self.next_id(), self, agent_type) + self.grid.place_agent(agent, pos) self.schedule.add(agent) - self.running = True self.datacollector.collect(self) def step(self): """ - Run one step of the model. If All agents are happy, halt the model. + Run one step of the model. """ self.happy = 0 # Reset counter of happy agents self.schedule.step() - # collect data + self.datacollector.collect(self) if self.happy == self.schedule.get_agent_count(): diff --git a/examples/basic/schelling/server.py b/examples/basic/schelling/server.py index 1396e9c7fed..1f0d5f92408 100644 --- a/examples/basic/schelling/server.py +++ b/examples/basic/schelling/server.py @@ -26,20 +26,35 @@ def schelling_draw(agent): return portrayal -canvas_element = mesa.visualization.CanvasGrid(schelling_draw, 20, 20, 500, 500) +canvas_element = mesa.visualization.CanvasGrid( + portrayal_method=schelling_draw, + grid_width=20, + grid_height=20, + canvas_width=500, + canvas_height=500, +) happy_chart = mesa.visualization.ChartModule([{"Label": "happy", "Color": "Black"}]) model_params = { "height": 20, "width": 20, - "density": mesa.visualization.Slider("Agent density", 0.8, 0.1, 1.0, 0.1), - "minority_pc": mesa.visualization.Slider("Fraction minority", 0.2, 0.00, 1.0, 0.05), - "homophily": mesa.visualization.Slider("Homophily", 3, 0, 8, 1), + "density": mesa.visualization.Slider( + name="Agent density", value=0.8, min_value=0.1, max_value=1.0, step=0.1 + ), + "minority_pc": mesa.visualization.Slider( + name="Fraction minority", value=0.2, min_value=0.00, max_value=1.0, step=0.05 + ), + "homophily": mesa.visualization.Slider( + name="Homophily", value=3, min_value=0, max_value=8, step=1 + ), + "radius": mesa.visualization.Slider( + name="Search Radius", value=1, min_value=1, max_value=5, step=1 + ), } server = mesa.visualization.ModularServer( - Schelling, - [canvas_element, get_happy_agents, happy_chart], - "Schelling", - model_params, + model_cls=Schelling, + visualization_elements=[canvas_element, get_happy_agents, happy_chart], + name="Schelling Segregation Model", + model_params=model_params, ) From 5f4b49c3d713ffe2adec28b29b8d25db410c72dd Mon Sep 17 00:00:00 2001 From: federico <74419371+FoFFolo@users.noreply.github.com> Date: Sun, 3 Mar 2024 00:31:01 +0100 Subject: [PATCH 073/116] fix sugarscape_g1mt running options, requirements and changes to README (#109) * fix sugarscape_g1mt running options, requirements and changes to README * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fixes to README and run.py check * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Federico Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- examples/advanced/sugarscape_g1mt/Readme.md | 28 ++++++++----------- .../advanced/sugarscape_g1mt/requirements.txt | 3 +- examples/advanced/sugarscape_g1mt/run.py | 10 ++++--- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/Readme.md b/examples/advanced/sugarscape_g1mt/Readme.md index 7fbced07ddc..5a658cecc5d 100644 --- a/examples/advanced/sugarscape_g1mt/Readme.md +++ b/examples/advanced/sugarscape_g1mt/Readme.md @@ -3,21 +3,17 @@ ## Summary This is Epstein & Axtell's Sugarscape model with Traders, a detailed description is in Chapter four of -*Growing Artificial Societies: Social Science from the Bottom Up.* (1996) The model shows an emergent price equilibrium can happen via a decentralized dynamics. +*Growing Artificial Societies: Social Science from the Bottom Up (1996)*. The model shows an emergent price equilibrium can happen via a decentralized dynamics. This code generally matches the code in the Complexity Explorer Tutorial, but in `.py` instead of `.ipynb` format. ### Agents: -- **Sugar**: Sugar agents grow back at one unit per time step and can be harvested and traded by the trader agents. Sugar -is unequally distributed across the landscape with sugar hills in the upper left and lower right of the space. - (green if you do the interactive run) -- **Spice**: Spice agents grow back at one unit per time step and can be harvested and traded by the trader agents. Spice -is unequally distributed across the landscape with spice hills in the upper right and lower left of the space. -(yellow if you do the interactive run) +- **Resource**: Resource agents grow back at one unit of sugar and spice per time step up to a specified max amount and can be harvested and traded by the trader agents. + (if you do the interactive run, the color will be green if the resource agent has a bigger amount of sugar, or yellow if it has a bigger amount of spice) - **Traders**: Trader agents have the following attributes: (1) metabolism for sugar, (2) metabolism for spice, (3) vision, (4) initial sugar endowment and (5) initial spice endowment. The traverse the landscape harvesting sugar and spice and -trading with other agents. If they run out of sugar or spice then they are removed from the model. +trading with other agents. If they run out of sugar or spice then they are removed from the model. (red circle if you do the interactive run) The trader agents traverse the landscape according to rule **M**: - Look out as far as vision permits in the four principal lattice directions and identify the unoccupied site(s). @@ -49,7 +45,7 @@ The model demonstrates several Mesa concepts and features: To install the dependencies use pip and the requirements.txt in this directory. e.g. ``` - $ pip install -r requirements.txt + $ pip install -r requirements.txt ``` ## How to Run @@ -69,19 +65,19 @@ To run the model with BatchRunner: To run the model interactively: ``` - $ mesa runserver + $ mesa runserver ``` 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 Sugar and Spice agent classes. -* ``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 and launches and interactive visualization server. -* ``run.py``: Runs Server, Single Run or Batch Run with data collection and basic analysis. +* `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. * `app.py`: Runs a visualization server via Solara (`solara run app.py`). * `tests.py`: Has tests to ensure that the model reproduces the results in shown in Growing Artificial Societies. diff --git a/examples/advanced/sugarscape_g1mt/requirements.txt b/examples/advanced/sugarscape_g1mt/requirements.txt index cc578b40b7c..14c03478da9 100644 --- a/examples/advanced/sugarscape_g1mt/requirements.txt +++ b/examples/advanced/sugarscape_g1mt/requirements.txt @@ -1,5 +1,6 @@ jupyter -mesa +mesa~=2.0 numpy matplotlib networkx +pandas diff --git a/examples/advanced/sugarscape_g1mt/run.py b/examples/advanced/sugarscape_g1mt/run.py index 7f2eb274eaf..1522adb34a5 100644 --- a/examples/advanced/sugarscape_g1mt/run.py +++ b/examples/advanced/sugarscape_g1mt/run.py @@ -64,11 +64,10 @@ def assess_results(results, single_agent): args = sys.argv[1:] - -if args[0] == "runserver": +if len(args) == 0: server.launch() -elif "s" in args[0] or "Single" in args[0]: +elif args[0] == "-s": print("Running Single Model") # instantiate the model model = SugarscapeG1mt() @@ -83,7 +82,7 @@ def assess_results(results, single_agent): # assess the results assess_results(model_results, agent_results) -else: +elif args[0] == "-b": print("Conducting a Batch Run") # Batch Run params = { @@ -103,3 +102,6 @@ def assess_results(results, single_agent): ) assess_results(results_batch, None) + +else: + raise Exception("Option not found") From 3610bcc5b474dc0758852eee3836988095dd90ce Mon Sep 17 00:00:00 2001 From: rht Date: Tue, 19 Mar 2024 04:58:29 -0400 Subject: [PATCH 074/116] Fix Sugarscape G1MT app.py to use Resource instead --- examples/advanced/sugarscape_g1mt/app.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/app.py b/examples/advanced/sugarscape_g1mt/app.py index 9759c032481..2196995dfd8 100644 --- a/examples/advanced/sugarscape_g1mt/app.py +++ b/examples/advanced/sugarscape_g1mt/app.py @@ -3,7 +3,6 @@ from matplotlib.figure import Figure from mesa.experimental import JupyterViz from sugarscape_g1mt.model import SugarscapeG1mt -from sugarscape_g1mt.resource_agents import Sugar from sugarscape_g1mt.trader_agents import Trader @@ -22,11 +21,12 @@ def portray(g): layers["trader"]["y"].append(j) else: # Don't visualize resource with value <= 1. - value = agent.amount if agent.amount > 1 else np.nan - if isinstance(agent, Sugar): - layers["sugar"][i][j] = value - else: - layers["spice"][i][j] = value + 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() From 2ced3b065c3930d99005b26149934b2b9501dce3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 23:16:33 -0400 Subject: [PATCH 075/116] [pre-commit.ci] pre-commit autoupdate (#116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.2.0 → v0.4.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.0...v0.4.3) - [github.com/asottile/pyupgrade: v3.15.0 → v3.15.2](https://github.com/asottile/pyupgrade/compare/v3.15.0...v3.15.2) - [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- examples/basic/boltzmann_wealth_model/app.py | 6 +++--- examples/basic/conways_game_of_life/app.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/basic/boltzmann_wealth_model/app.py b/examples/basic/boltzmann_wealth_model/app.py index c4c92ea837e..97f0f20c0fd 100644 --- a/examples/basic/boltzmann_wealth_model/app.py +++ b/examples/basic/boltzmann_wealth_model/app.py @@ -71,9 +71,9 @@ cell_content, (x, y) = cell agent_count = len(cell_content) selected_row = df_grid[(df_grid["x"] == x) & (df_grid["y"] == y)] - df_grid.loc[ - selected_row.index, "agent_count" - ] = agent_count # random.choice([1,2]) + df_grid.loc[selected_row.index, "agent_count"] = ( + agent_count # random.choice([1,2]) + ) df_gini = pd.concat( [ diff --git a/examples/basic/conways_game_of_life/app.py b/examples/basic/conways_game_of_life/app.py index 884ec523921..5be8327a35b 100644 --- a/examples/basic/conways_game_of_life/app.py +++ b/examples/basic/conways_game_of_life/app.py @@ -52,9 +52,9 @@ for contents, (x, y) in model.grid.coord_iter(): # print('x:',x,'y:',y, 'state:',contents) selected_row = df_grid[(df_grid["x"] == x) & (df_grid["y"] == y)] - df_grid.loc[ - selected_row.index, "state" - ] = contents.state # random.choice([1,2]) + df_grid.loc[selected_row.index, "state"] = ( + contents.state + ) # random.choice([1,2]) heatmap = ( alt.Chart(df_grid) From c120d978a7f38332cfa2a131bbef0e1e511dd58b Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Thu, 4 Jul 2024 16:15:46 +0200 Subject: [PATCH 076/116] Fix pre-commit Make pre-commit fully green again --- .../boid_flockers/boid_flockers/SimpleContinuousModule.py | 4 ++-- examples/basic/virus_on_network/app.py | 4 +--- examples/basic/virus_on_network/virus_on_network/server.py | 4 +--- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py b/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py index eabf077c8f6..42b3e9dd76f 100644 --- a/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py +++ b/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py @@ -11,8 +11,8 @@ def __init__(self, portrayal_method=None, canvas_height=500, canvas_width=500): self.portrayal_method = portrayal_method self.canvas_height = canvas_height self.canvas_width = canvas_width - new_element = "new Simple_Continuous_Module({}, {})".format( - self.canvas_width, self.canvas_height + new_element = ( + f"new Simple_Continuous_Module({self.canvas_width}, {self.canvas_height})" ) self.js_code = "elements.push(" + new_element + ");" diff --git a/examples/basic/virus_on_network/app.py b/examples/basic/virus_on_network/app.py index cefb0059cb9..a9661a4de4a 100644 --- a/examples/basic/virus_on_network/app.py +++ b/examples/basic/virus_on_network/app.py @@ -41,9 +41,7 @@ def get_resistant_susceptible_ratio(model): ratio_text = r"$\infty$" if ratio is math.inf else f"{ratio:.2f}" infected_text = str(number_infected(model)) - return "Resistant/Susceptible Ratio: {}
Infected Remaining: {}".format( - ratio_text, infected_text - ) + return f"Resistant/Susceptible Ratio: {ratio_text}
Infected Remaining: {infected_text}" def make_plot(model): diff --git a/examples/basic/virus_on_network/virus_on_network/server.py b/examples/basic/virus_on_network/virus_on_network/server.py index afb5c1954d2..dcc7643f080 100644 --- a/examples/basic/virus_on_network/virus_on_network/server.py +++ b/examples/basic/virus_on_network/virus_on_network/server.py @@ -68,9 +68,7 @@ def get_resistant_susceptible_ratio(model): ratio_text = "∞" if ratio is math.inf else f"{ratio:.2f}" infected_text = str(number_infected(model)) - return "Resistant/Susceptible Ratio: {}
Infected Remaining: {}".format( - ratio_text, infected_text - ) + return f"Resistant/Susceptible Ratio: {ratio_text}
Infected Remaining: {infected_text}" model_params = { From bd63101adb044d34761d0848a434994502cd1399 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Mon, 22 Jul 2024 09:58:29 +0200 Subject: [PATCH 077/116] Make batch_run pytestable by adding main() functions (#143) Added a main() function to bank_reserves `batch_run.py` and sugarscape_g1mt `run.py` scripts to facilitate testing and script execution. The `main()` function encapsulates the primary script logic, allowing for easier modular testing and execution. By defining script operations within `main()`, we can directly invoke this function in testing environments without relying on command-line execution. This practice enhances code readability, maintainability, and testability, providing a clear entry point for the script's functionality. --- examples/advanced/sugarscape_g1mt/run.py | 84 ++++++++++++------------ 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/run.py b/examples/advanced/sugarscape_g1mt/run.py index 1522adb34a5..f1056fa4b8f 100644 --- a/examples/advanced/sugarscape_g1mt/run.py +++ b/examples/advanced/sugarscape_g1mt/run.py @@ -61,47 +61,45 @@ def assess_results(results, single_agent): # 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) -args = sys.argv[1:] - -if len(args) == 0: - server.launch() - -elif args[0] == "-s": - print("Running Single Model") - # instantiate the model - model = SugarscapeG1mt() - # run the model - model.run_model() - # Get results - model_results = model.datacollector.get_model_vars_dataframe() - # Convert to make similar to batch_run_results - model_results["Step"] = model_results.index - agent_results = model.datacollector.get_agent_vars_dataframe() - agent_results = agent_results.reset_index() - # assess the results - assess_results(model_results, agent_results) - -elif args[0] == "-b": - print("Conducting a Batch Run") - # 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") + else: + raise Exception("Option not found") + + +if __name__ == "__main__": + main() From 56a0c046aa36af3742c9fd470b556499b5a33e2f Mon Sep 17 00:00:00 2001 From: rht Date: Sat, 10 Aug 2024 07:34:02 -0400 Subject: [PATCH 078/116] Address duplicate position warning (#122) --- examples/advanced/pd_grid/pd_grid/agent.py | 7 +++---- examples/advanced/pd_grid/pd_grid/model.py | 2 +- .../sugarscape_g1mt/sugarscape_g1mt/model.py | 3 +-- .../sugarscape_g1mt/resource_agents.py | 3 +-- .../sugarscape_g1mt/trader_agents.py | 2 -- .../advanced/wolf_sheep/wolf_sheep/agents.py | 21 +++++++------------ .../advanced/wolf_sheep/wolf_sheep/model.py | 6 +++--- .../wolf_sheep/wolf_sheep/random_walk.py | 3 +-- 8 files changed, 18 insertions(+), 29 deletions(-) diff --git a/examples/advanced/pd_grid/pd_grid/agent.py b/examples/advanced/pd_grid/pd_grid/agent.py index e289169f482..d658ddc846e 100644 --- a/examples/advanced/pd_grid/pd_grid/agent.py +++ b/examples/advanced/pd_grid/pd_grid/agent.py @@ -4,18 +4,17 @@ class PDAgent(mesa.Agent): """Agent member of the iterated, spatial prisoner's dilemma model.""" - def __init__(self, pos, model, starting_move=None): + def __init__(self, unique_id, model, starting_move=None): """ Create a new Prisoner's Dilemma agent. Args: - pos: (x, y) tuple of the agent's position. + unique_id: Unique identifier for the agent. model: model instance starting_move: If provided, determines the agent's initial state: C(ooperating) or D(efecting). Otherwise, random. """ - super().__init__(pos, model) - self.pos = pos + super().__init__(unique_id, model) self.score = 0 if starting_move: self.move = starting_move diff --git a/examples/advanced/pd_grid/pd_grid/model.py b/examples/advanced/pd_grid/pd_grid/model.py index b970c0f4c52..448e47450bf 100644 --- a/examples/advanced/pd_grid/pd_grid/model.py +++ b/examples/advanced/pd_grid/pd_grid/model.py @@ -37,7 +37,7 @@ def __init__( # Create agents for x in range(width): for y in range(height): - agent = PDAgent((x, y), self) + agent = PDAgent(self.next_id(), self) self.grid.place_agent(agent, (x, y)) self.schedule.add(agent) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index f88f1c2c6a3..3500b0c2cfd 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -97,7 +97,7 @@ def __init__( for _, (x, y) in self.grid.coord_iter(): max_sugar = sugar_distribution[x, y] max_spice = spice_distribution[x, y] - resource = Resource(agent_id, self, (x, y), max_sugar, max_spice) + resource = Resource(agent_id, self, max_sugar, max_spice) self.schedule.add(resource) self.grid.place_agent(resource, (x, y)) agent_id += 1 @@ -123,7 +123,6 @@ def __init__( trader = Trader( agent_id, self, - (x, y), moore=False, sugar=sugar, spice=spice, diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py index 18d11cd6e51..2be75de7af5 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py @@ -9,9 +9,8 @@ class Resource(mesa.Agent): - grows 1 amount of spice at each turn """ - def __init__(self, unique_id, model, pos, max_sugar, max_spice): + def __init__(self, unique_id, model, max_sugar, max_spice): super().__init__(unique_id, model) - self.pos = pos self.sugar_amount = max_sugar self.max_sugar = max_sugar self.spice_amount = max_spice diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py index 96bc8c5b391..4ddb4d3841f 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py @@ -31,7 +31,6 @@ def __init__( self, unique_id, model, - pos, moore=False, sugar=0, spice=0, @@ -40,7 +39,6 @@ def __init__( vision=0, ): super().__init__(unique_id, model) - self.pos = pos self.moore = moore self.sugar = sugar self.spice = spice diff --git a/examples/advanced/wolf_sheep/wolf_sheep/agents.py b/examples/advanced/wolf_sheep/wolf_sheep/agents.py index 460c4abb131..6d73e113a9f 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/agents.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/agents.py @@ -12,8 +12,8 @@ class Sheep(RandomWalker): energy = None - def __init__(self, unique_id, pos, model, moore, energy=None): - super().__init__(unique_id, pos, model, moore=moore) + def __init__(self, unique_id, model, moore, energy=None): + super().__init__(unique_id, model, moore=moore) self.energy = energy def step(self): @@ -44,9 +44,7 @@ def step(self): # Create a new sheep: if self.model.grass: self.energy /= 2 - lamb = Sheep( - self.model.next_id(), self.pos, self.model, self.moore, self.energy - ) + lamb = Sheep(self.model.next_id(), self.model, self.moore, self.energy) self.model.grid.place_agent(lamb, self.pos) self.model.schedule.add(lamb) @@ -58,8 +56,8 @@ class Wolf(RandomWalker): energy = None - def __init__(self, unique_id, pos, model, moore, energy=None): - super().__init__(unique_id, pos, model, moore=moore) + def __init__(self, unique_id, model, moore, energy=None): + super().__init__(unique_id, model, moore=moore) self.energy = energy def step(self): @@ -86,10 +84,8 @@ def step(self): if self.random.random() < self.model.wolf_reproduce: # Create a new wolf cub self.energy /= 2 - cub = Wolf( - self.model.next_id(), self.pos, self.model, self.moore, self.energy - ) - self.model.grid.place_agent(cub, cub.pos) + cub = Wolf(self.model.next_id(), self.model, self.moore, self.energy) + self.model.grid.place_agent(cub, self.pos) self.model.schedule.add(cub) @@ -98,7 +94,7 @@ class GrassPatch(mesa.Agent): A patch of grass that grows at a fixed rate and it is eaten by sheep """ - def __init__(self, unique_id, pos, model, fully_grown, countdown): + def __init__(self, unique_id, model, fully_grown, countdown): """ Creates a new patch of grass @@ -109,7 +105,6 @@ def __init__(self, unique_id, pos, model, fully_grown, countdown): super().__init__(unique_id, model) self.fully_grown = fully_grown self.countdown = countdown - self.pos = pos def step(self): if not self.fully_grown: diff --git a/examples/advanced/wolf_sheep/wolf_sheep/model.py b/examples/advanced/wolf_sheep/wolf_sheep/model.py index 2626f9581c5..48128d00a9f 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/model.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/model.py @@ -98,7 +98,7 @@ def __init__( x = self.random.randrange(self.width) y = self.random.randrange(self.height) energy = self.random.randrange(2 * self.sheep_gain_from_food) - sheep = Sheep(self.next_id(), (x, y), self, True, energy) + sheep = Sheep(self.next_id(), self, True, energy) self.grid.place_agent(sheep, (x, y)) self.schedule.add(sheep) @@ -107,7 +107,7 @@ def __init__( x = self.random.randrange(self.width) y = self.random.randrange(self.height) energy = self.random.randrange(2 * self.wolf_gain_from_food) - wolf = Wolf(self.next_id(), (x, y), self, True, energy) + wolf = Wolf(self.next_id(), self, True, energy) self.grid.place_agent(wolf, (x, y)) self.schedule.add(wolf) @@ -121,7 +121,7 @@ def __init__( else: countdown = self.random.randrange(self.grass_regrowth_time) - patch = GrassPatch(self.next_id(), (x, y), self, fully_grown, countdown) + patch = GrassPatch(self.next_id(), self, fully_grown, countdown) self.grid.place_agent(patch, (x, y)) self.schedule.add(patch) diff --git a/examples/advanced/wolf_sheep/wolf_sheep/random_walk.py b/examples/advanced/wolf_sheep/wolf_sheep/random_walk.py index 49219fa7fff..920ae08ca1d 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/random_walk.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/random_walk.py @@ -18,7 +18,7 @@ class RandomWalker(mesa.Agent): y = None moore = True - def __init__(self, unique_id, pos, model, moore=True): + def __init__(self, unique_id, model, moore=True): """ grid: The MultiGrid object in which the agent lives. x: The agent's current x coordinate @@ -27,7 +27,6 @@ def __init__(self, unique_id, pos, model, moore=True): Otherwise, only up, down, left, right. """ super().__init__(unique_id, model) - self.pos = pos self.moore = moore def random_move(self): From 6cc60c3df07306896847bba1d7a8595c79bfdd33 Mon Sep 17 00:00:00 2001 From: rht Date: Sat, 10 Aug 2024 07:34:02 -0400 Subject: [PATCH 079/116] Address duplicate position warning (#122) --- examples/basic/boid_flockers/boid_flockers/model.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/examples/basic/boid_flockers/boid_flockers/model.py b/examples/basic/boid_flockers/boid_flockers/model.py index 6ebbf8aa8fa..8ddfc11a2f4 100644 --- a/examples/basic/boid_flockers/boid_flockers/model.py +++ b/examples/basic/boid_flockers/boid_flockers/model.py @@ -28,7 +28,6 @@ def __init__( self, unique_id, model, - pos, speed, direction, vision, @@ -42,7 +41,6 @@ def __init__( Args: unique_id: Unique agent identifier. - pos: Starting position speed: Distance to move per step. direction: numpy vector for the Boid's direction of movement. vision: Radius to look around for nearby Boids. @@ -52,7 +50,6 @@ def __init__( match: the relative importance of matching neighbors' headings """ super().__init__(unique_id, model) - self.pos = np.array(pos) self.speed = speed self.direction = direction self.vision = vision @@ -140,7 +137,6 @@ def make_agents(self): boid = Boid( unique_id=i, model=self, - pos=pos, speed=self.speed, direction=direction, vision=self.vision, From e946873bba17d5839e1209ae3c79fda7cb504cf9 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sun, 11 Aug 2024 12:22:01 +0200 Subject: [PATCH 080/116] Use SolaraViz instead of JupyterViz in examples This updates all the examples to use the renamed and stabilized SolaraViz. --- examples/advanced/sugarscape_g1mt/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/app.py b/examples/advanced/sugarscape_g1mt/app.py index 2196995dfd8..9f01d8aaaf5 100644 --- a/examples/advanced/sugarscape_g1mt/app.py +++ b/examples/advanced/sugarscape_g1mt/app.py @@ -1,7 +1,7 @@ import numpy as np import solara from matplotlib.figure import Figure -from mesa.experimental import JupyterViz +from mesa.visualization import SolaraViz from sugarscape_g1mt.model import SugarscapeG1mt from sugarscape_g1mt.trader_agents import Trader @@ -50,7 +50,7 @@ def portray(g): "height": 50, } -page = JupyterViz( +page = SolaraViz( SugarscapeG1mt, model_params, measures=["Trader", "Price"], From fcc415b372054b3d64c3c713951e9db0fa5d75d4 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sun, 11 Aug 2024 12:22:01 +0200 Subject: [PATCH 081/116] Use SolaraViz instead of JupyterViz in examples This updates all the examples to use the renamed and stabilized SolaraViz. --- examples/basic/boid_flockers/app.py | 4 ++-- examples/basic/virus_on_network/app.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/basic/boid_flockers/app.py b/examples/basic/boid_flockers/app.py index 5de317feff1..c8dd76bcd2c 100644 --- a/examples/basic/boid_flockers/app.py +++ b/examples/basic/boid_flockers/app.py @@ -1,5 +1,5 @@ from boid_flockers.model import BoidFlockers -from mesa.experimental import JupyterViz +from mesa.visualization import SolaraViz def boid_draw(agent): @@ -15,7 +15,7 @@ def boid_draw(agent): "separation": 2, } -page = JupyterViz( +page = SolaraViz( model_class=BoidFlockers, model_params=model_params, measures=[], diff --git a/examples/basic/virus_on_network/app.py b/examples/basic/virus_on_network/app.py index a9661a4de4a..47ab294fd4e 100644 --- a/examples/basic/virus_on_network/app.py +++ b/examples/basic/virus_on_network/app.py @@ -3,7 +3,7 @@ import solara from matplotlib.figure import Figure from matplotlib.ticker import MaxNLocator -from mesa.experimental import JupyterViz, make_text +from mesa.visualization import SolaraViz, make_text from virus_on_network.model import State, VirusOnNetwork, number_infected @@ -119,7 +119,7 @@ def make_plot(model): }, } -page = JupyterViz( +page = SolaraViz( VirusOnNetwork, model_params, measures=[ From eade76dc21ec26ab3d4e61cce1b40905360dcde1 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 10 Aug 2024 20:15:29 +0200 Subject: [PATCH 082/116] sugerscape_g1mt: Refactor using AgentSet functionality Resolves 4 warnings by using the AgentSet select(), shuffle() and do() functionality. Also removes the now unused scheduler. --- .../sugarscape_g1mt/sugarscape_g1mt/model.py | 32 ++++--------------- .../sugarscape_g1mt/trader_agents.py | 2 +- 2 files changed, 7 insertions(+), 27 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index 3500b0c2cfd..19e43644c92 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -68,22 +68,17 @@ def __init__( self.enable_trade = enable_trade self.running = True - # initiate activation schedule - self.schedule = mesa.time.RandomActivationByType(self) # initiate mesa grid class self.grid = mesa.space.MultiGrid(self.width, self.height, torus=False) # initiate datacollector self.datacollector = mesa.DataCollector( model_reporters={ - "Trader": lambda m: m.schedule.get_type_count(Trader), + "Trader": lambda m: len(m.agents.select(agent_type=Trader)), "Trade Volume": lambda m: sum( - len(a.trade_partners) - for a in m.schedule.agents_by_type[Trader].values() + len(a.trade_partners) for a in self.agents.select(agent_type=Trader) ), "Price": lambda m: geometric_mean( - flatten( - [a.prices for a in m.schedule.agents_by_type[Trader].values()] - ) + flatten([a.prices for a in self.agents.select(agent_type=Trader)]) ), }, agent_reporters={"Trade Network": lambda a: get_trade(a)}, @@ -98,7 +93,6 @@ def __init__( max_sugar = sugar_distribution[x, y] max_spice = spice_distribution[x, y] resource = Resource(agent_id, self, max_sugar, max_spice) - self.schedule.add(resource) self.grid.place_agent(resource, (x, y)) agent_id += 1 @@ -132,34 +126,20 @@ def __init__( ) # place agent self.grid.place_agent(trader, (x, y)) - self.schedule.add(trader) agent_id += 1 - def randomize_traders(self): - """ - helper function for self.step() - - puts traders in randomized list for step function - """ - - traders_shuffle = list(self.schedule.agents_by_type[Trader].values()) - self.random.shuffle(traders_shuffle) - - return traders_shuffle - def step(self): """ Unique step function that does staged activation of sugar and spice and then randomly activates traders """ # step Resource agents - for resource in self.schedule.agents_by_type[Resource].values(): - resource.step() + self.agents.select(agent_type=Resource).do("step") # step trader agents # to account for agent death and removal we need a seperate data strcuture to # iterate - trader_shuffle = self.randomize_traders() + trader_shuffle = self.agents.select(agent_type=Trader).shuffle() for agent in trader_shuffle: agent.prices = [] @@ -174,7 +154,7 @@ def step(self): self.datacollector.collect(self) return - trader_shuffle = self.randomize_traders() + trader_shuffle = self.agents.select(agent_type=Trader).shuffle() for agent in trader_shuffle: agent.trade_with_neighbors() diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py index 4ddb4d3841f..18c8c6269de 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py @@ -304,7 +304,7 @@ def maybe_die(self): if self.is_starved(): self.model.grid.remove_agent(self) - self.model.schedule.remove(self) + self.remove() def trade_with_neighbors(self): """ From 267499ed9c72f44d9b0d5ff192a107dbe3fb303d Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 10 Aug 2024 22:10:09 +0200 Subject: [PATCH 083/116] wolf_sheep: Replace custom scheduler with AgentSet functionality This resolves all warnings outputted by this model. For the model step, the behavior of the old RandomActivationByType scheduler when using step(shuffle_types=True, shuffle_agents=True) is replicated. Conceptually, it can be argued that this should be modelled differently. The verbose prints are also removed. --- .../advanced/wolf_sheep/wolf_sheep/agents.py | 8 ++- .../advanced/wolf_sheep/wolf_sheep/model.py | 49 +++++-------------- .../wolf_sheep/wolf_sheep/scheduler.py | 31 ------------ 3 files changed, 14 insertions(+), 74 deletions(-) delete mode 100644 examples/advanced/wolf_sheep/wolf_sheep/scheduler.py diff --git a/examples/advanced/wolf_sheep/wolf_sheep/agents.py b/examples/advanced/wolf_sheep/wolf_sheep/agents.py index 6d73e113a9f..688f25096f4 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/agents.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/agents.py @@ -37,7 +37,7 @@ def step(self): # Death if self.energy < 0: self.model.grid.remove_agent(self) - self.model.schedule.remove(self) + self.remove() living = False if living and self.random.random() < self.model.sheep_reproduce: @@ -46,7 +46,6 @@ def step(self): self.energy /= 2 lamb = Sheep(self.model.next_id(), self.model, self.moore, self.energy) self.model.grid.place_agent(lamb, self.pos) - self.model.schedule.add(lamb) class Wolf(RandomWalker): @@ -74,19 +73,18 @@ def step(self): # Kill the sheep self.model.grid.remove_agent(sheep_to_eat) - self.model.schedule.remove(sheep_to_eat) + sheep_to_eat.remove() # Death or reproduction if self.energy < 0: self.model.grid.remove_agent(self) - self.model.schedule.remove(self) + self.remove() else: if self.random.random() < self.model.wolf_reproduce: # Create a new wolf cub self.energy /= 2 cub = Wolf(self.model.next_id(), self.model, self.moore, self.energy) self.model.grid.place_agent(cub, self.pos) - self.model.schedule.add(cub) class GrassPatch(mesa.Agent): diff --git a/examples/advanced/wolf_sheep/wolf_sheep/model.py b/examples/advanced/wolf_sheep/wolf_sheep/model.py index 48128d00a9f..59f1835c0be 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/model.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/model.py @@ -12,7 +12,6 @@ import mesa from .agents import GrassPatch, Sheep, Wolf -from .scheduler import RandomActivationByTypeFiltered class WolfSheep(mesa.Model): @@ -35,8 +34,6 @@ class WolfSheep(mesa.Model): grass_regrowth_time = 30 sheep_gain_from_food = 4 - verbose = False # Print-monitoring - description = ( "A model for simulating wolf and sheep (predator-prey) ecosystem modelling." ) @@ -81,14 +78,13 @@ def __init__( self.grass_regrowth_time = grass_regrowth_time self.sheep_gain_from_food = sheep_gain_from_food - self.schedule = RandomActivationByTypeFiltered(self) self.grid = mesa.space.MultiGrid(self.width, self.height, torus=True) self.datacollector = mesa.DataCollector( { - "Wolves": lambda m: m.schedule.get_type_count(Wolf), - "Sheep": lambda m: m.schedule.get_type_count(Sheep), - "Grass": lambda m: m.schedule.get_type_count( - GrassPatch, lambda x: x.fully_grown + "Wolves": lambda m: len(m.get_agents_of_type(Wolf)), + "Sheep": lambda m: len(m.get_agents_of_type(Sheep)), + "Grass": lambda m: len( + m.get_agents_of_type(GrassPatch).select(lambda a: a.fully_grown) ), } ) @@ -100,7 +96,6 @@ def __init__( energy = self.random.randrange(2 * self.sheep_gain_from_food) sheep = Sheep(self.next_id(), self, True, energy) self.grid.place_agent(sheep, (x, y)) - self.schedule.add(sheep) # Create wolves for i in range(self.initial_wolves): @@ -109,7 +104,6 @@ def __init__( energy = self.random.randrange(2 * self.wolf_gain_from_food) wolf = Wolf(self.next_id(), self, True, energy) self.grid.place_agent(wolf, (x, y)) - self.schedule.add(wolf) # Create grass patches if self.grass: @@ -123,42 +117,21 @@ def __init__( patch = GrassPatch(self.next_id(), self, fully_grown, countdown) self.grid.place_agent(patch, (x, y)) - self.schedule.add(patch) self.running = True self.datacollector.collect(self) def step(self): - self.schedule.step() + # 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.get_agents_of_type(agent_type).do("step") + # collect data self.datacollector.collect(self) - if self.verbose: - print( - [ - self.schedule.time, - self.schedule.get_type_count(Wolf), - self.schedule.get_type_count(Sheep), - self.schedule.get_type_count(GrassPatch, lambda x: x.fully_grown), - ] - ) def run_model(self, step_count=200): - if self.verbose: - print("Initial number wolves: ", self.schedule.get_type_count(Wolf)) - print("Initial number sheep: ", self.schedule.get_type_count(Sheep)) - print( - "Initial number grass: ", - self.schedule.get_type_count(GrassPatch, lambda x: x.fully_grown), - ) - for i in range(step_count): self.step() - - if self.verbose: - print("") - print("Final number wolves: ", self.schedule.get_type_count(Wolf)) - print("Final number sheep: ", self.schedule.get_type_count(Sheep)) - print( - "Final number grass: ", - self.schedule.get_type_count(GrassPatch, lambda x: x.fully_grown), - ) diff --git a/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py b/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py deleted file mode 100644 index 97424a553a8..00000000000 --- a/examples/advanced/wolf_sheep/wolf_sheep/scheduler.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Callable, Optional, Type - -import mesa - - -class RandomActivationByTypeFiltered(mesa.time.RandomActivationByType): - """ - A scheduler that overrides the get_type_count method to allow for filtering - of agents by a function before counting. - - Example: - >>> scheduler = RandomActivationByTypeFiltered(model) - >>> scheduler.get_type_count(AgentA, lambda agent: agent.some_attribute > 10) - """ - - def get_type_count( - self, - type_class: Type[mesa.Agent], - filter_func: Optional[Callable[[mesa.Agent], bool]] = None, - ) -> int: - """ - Returns the current number of agents of certain type in the queue - that satisfy the filter function. - """ - if type_class not in self.agents_by_type: - return 0 - count = 0 - for agent in self.agents_by_type[type_class].values(): - if filter_func is None or filter_func(agent): - count += 1 - return count From c8cb14e61119bfa41af3e932e9a9c5f836fc522a Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sun, 11 Aug 2024 11:08:39 +0200 Subject: [PATCH 084/116] sugerscape_g1mt: Use model.get_agents_of_type instead of .select() Use model.get_agents_of_type instead of model.agents.select() to select all agents of a certain type. It should be faster, since it's a direct dictionary call. --- .../sugarscape_g1mt/sugarscape_g1mt/model.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index 19e43644c92..015c745c061 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -73,12 +73,12 @@ def __init__( # initiate datacollector self.datacollector = mesa.DataCollector( model_reporters={ - "Trader": lambda m: len(m.agents.select(agent_type=Trader)), + "Trader": lambda m: len(m.get_agents_of_type(Trader)), "Trade Volume": lambda m: sum( - len(a.trade_partners) for a in self.agents.select(agent_type=Trader) + len(a.trade_partners) for a in m.get_agents_of_type(Trader) ), "Price": lambda m: geometric_mean( - flatten([a.prices for a in self.agents.select(agent_type=Trader)]) + flatten([a.prices for a in m.get_agents_of_type(Trader)]) ), }, agent_reporters={"Trade Network": lambda a: get_trade(a)}, @@ -134,12 +134,12 @@ def step(self): and then randomly activates traders """ # step Resource agents - self.agents.select(agent_type=Resource).do("step") + self.get_agents_of_type(Resource).do("step") # step trader agents # to account for agent death and removal we need a seperate data strcuture to # iterate - trader_shuffle = self.agents.select(agent_type=Trader).shuffle() + trader_shuffle = self.get_agents_of_type(Trader).shuffle() for agent in trader_shuffle: agent.prices = [] @@ -154,7 +154,7 @@ def step(self): self.datacollector.collect(self) return - trader_shuffle = self.agents.select(agent_type=Trader).shuffle() + trader_shuffle = self.get_agents_of_type(Trader).shuffle() for agent in trader_shuffle: agent.trade_with_neighbors() From 977e7ae06613c985a8d04046fad49a9988fcffac Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 17 Aug 2024 21:33:56 +0200 Subject: [PATCH 085/116] Replace model.schedule.agents with model.agents AgentSet Replace all usages of model.schedule.agents with the use of the model.agents AgentSet in all examples. --- examples/basic/boid_flockers/Flocker Test.ipynb | 2 +- .../basic/boid_flockers/boid_flockers/SimpleContinuousModule.py | 2 +- .../boltzmann_wealth_model/boltzmann_wealth_model/model.py | 2 +- examples/basic/schelling/analysis.ipynb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/basic/boid_flockers/Flocker Test.ipynb b/examples/basic/boid_flockers/Flocker Test.ipynb index 82ecc47b99f..c757f3a88ed 100644 --- a/examples/basic/boid_flockers/Flocker Test.ipynb +++ b/examples/basic/boid_flockers/Flocker Test.ipynb @@ -25,7 +25,7 @@ "def draw_boids(model):\n", " x_vals = []\n", " y_vals = []\n", - " for boid in model.schedule.agents:\n", + " for boid in model.agents:\n", " x, y = boid.pos\n", " x_vals.append(x)\n", " y_vals.append(y)\n", diff --git a/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py b/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py index 42b3e9dd76f..ec670d7af1c 100644 --- a/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py +++ b/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py @@ -18,7 +18,7 @@ def __init__(self, portrayal_method=None, canvas_height=500, canvas_width=500): def render(self, model): space_state = [] - for obj in model.schedule.agents: + for obj in model.agents: portrayal = self.portrayal_method(obj) x, y = obj.pos x = (x - model.space.x_min) / (model.space.x_max - model.space.x_min) diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py index 11a3e95878a..1e1f3973d04 100644 --- a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -2,7 +2,7 @@ def compute_gini(model): - agent_wealths = [agent.wealth for agent in model.schedule.agents] + agent_wealths = [agent.wealth for agent in model.agents] x = sorted(agent_wealths) N = model.num_agents B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x)) diff --git a/examples/basic/schelling/analysis.ipynb b/examples/basic/schelling/analysis.ipynb index 50f382c66a0..c3c93651f23 100644 --- a/examples/basic/schelling/analysis.ipynb +++ b/examples/basic/schelling/analysis.ipynb @@ -328,7 +328,7 @@ " Find the % of agents that only have neighbors of their same type.\n", " \"\"\"\n", " segregated_agents = 0\n", - " for agent in model.schedule.agents:\n", + " for agent in model.agents:\n", " segregated = True\n", " for neighbor in model.grid.iter_neighbors(agent.pos, True):\n", " if neighbor.type != agent.type:\n", From e17e1c5dd19a007882012bdc5434061ac7fea143 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 17 Aug 2024 21:33:56 +0200 Subject: [PATCH 086/116] Replace model.schedule.agents with model.agents AgentSet Replace all usages of model.schedule.agents with the use of the model.agents AgentSet in all examples. --- .../epstein_civil_violence/epstein_civil_violence/model.py | 6 +++--- examples/advanced/sugarscape_g1mt/tests.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py index 6bce24ebe23..542c419e33a 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py @@ -122,7 +122,7 @@ def count_type_citizens(model, condition, exclude_jailed=True): Helper method to count agents by Quiescent/Active. """ count = 0 - for agent in model.schedule.agents: + for agent in model.agents: if agent.breed == "cop": continue if exclude_jailed and agent.jail_sentence > 0: @@ -137,7 +137,7 @@ def count_jailed(model): Helper method to count jailed agents. """ count = 0 - for agent in model.schedule.agents: + for agent in model.agents: if agent.breed == "citizen" and agent.jail_sentence > 0: count += 1 return count @@ -148,7 +148,7 @@ def count_cops(model): Helper method to count jailed agents. """ count = 0 - for agent in model.schedule.agents: + for agent in model.agents: if agent.breed == "cop": count += 1 return count diff --git a/examples/advanced/sugarscape_g1mt/tests.py b/examples/advanced/sugarscape_g1mt/tests.py index bcfcf73931d..70deaecb817 100644 --- a/examples/advanced/sugarscape_g1mt/tests.py +++ b/examples/advanced/sugarscape_g1mt/tests.py @@ -40,7 +40,7 @@ def calculate_carrying_capacities(enable_trade): for vision_max in visions: model = SugarscapeG1mt(vision_max=vision_max, enable_trade=enable_trade) model.run_model(step_count=50) - carrying_capacities.append(len(model.schedule.agents_by_type[Trader])) + carrying_capacities.append(len(model.agents_by_type[Trader])) return carrying_capacities # Carrying capacity should increase over mean vision (figure IV-6). From 0b3bbb9ea0e89e7647f53bb59158cbb8026f4950 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 17 Aug 2024 21:47:16 +0200 Subject: [PATCH 087/116] Replace RandomActivation scheduler with AgentSet Replace the old RandomActivation scheduler with AgentSet functionality. self.agents.shuffle.do("step") is equivalent and self.schedule.step() with an RandomActivation scheduler, and model behavior should not change. --- examples/basic/boid_flockers/boid_flockers/model.py | 4 ++-- .../boltzmann_wealth_model/boltzmann_wealth_model/model.py | 4 ++-- examples/basic/schelling/model.py | 3 +-- examples/basic/virus_on_network/virus_on_network/model.py | 4 ++-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/basic/boid_flockers/boid_flockers/model.py b/examples/basic/boid_flockers/boid_flockers/model.py index 8ddfc11a2f4..f9cd39344fe 100644 --- a/examples/basic/boid_flockers/boid_flockers/model.py +++ b/examples/basic/boid_flockers/boid_flockers/model.py @@ -120,7 +120,7 @@ def __init__( self.vision = vision self.speed = speed self.separation = separation - self.schedule = mesa.time.RandomActivation(self) + self.space = mesa.space.ContinuousSpace(width, height, True) self.factors = {"cohere": cohere, "separate": separate, "match": match} self.make_agents() @@ -147,4 +147,4 @@ def make_agents(self): self.schedule.add(boid) def step(self): - self.schedule.step() + self.agents.shuffle().do("step") diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py index 1e1f3973d04..9a16a69e90b 100644 --- a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -21,7 +21,7 @@ def __init__(self, N=100, width=10, height=10): super().__init__() self.num_agents = N self.grid = mesa.space.MultiGrid(width, height, True) - self.schedule = mesa.time.RandomActivation(self) + self.datacollector = mesa.DataCollector( model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} ) @@ -38,7 +38,7 @@ def __init__(self, N=100, width=10, height=10): self.datacollector.collect(self) def step(self): - self.schedule.step() + self.agents.shuffle().do("step") # collect data self.datacollector.collect(self) diff --git a/examples/basic/schelling/model.py b/examples/basic/schelling/model.py index dfba4efb2b1..5c3c15df4e2 100644 --- a/examples/basic/schelling/model.py +++ b/examples/basic/schelling/model.py @@ -68,7 +68,6 @@ def __init__( self.homophily = homophily self.radius = radius - self.schedule = mesa.time.RandomActivation(self) self.grid = mesa.space.SingleGrid(width, height, torus=True) self.happy = 0 @@ -94,7 +93,7 @@ def step(self): Run one step of the model. """ self.happy = 0 # Reset counter of happy agents - self.schedule.step() + self.agents.shuffle().do("step") self.datacollector.collect(self) diff --git a/examples/basic/virus_on_network/virus_on_network/model.py b/examples/basic/virus_on_network/virus_on_network/model.py index a33e7545861..65d2356093b 100644 --- a/examples/basic/virus_on_network/virus_on_network/model.py +++ b/examples/basic/virus_on_network/virus_on_network/model.py @@ -47,7 +47,7 @@ def __init__( prob = avg_node_degree / self.num_nodes self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=prob) self.grid = mesa.space.NetworkGrid(self.G) - self.schedule = mesa.time.RandomActivation(self) + self.initial_outbreak_size = ( initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes ) @@ -96,7 +96,7 @@ def resistant_susceptible_ratio(self): return math.inf def step(self): - self.schedule.step() + self.agents.shuffle().do("step") # collect data self.datacollector.collect(self) From 7e9017999f16deb2b614ae44d8d43c60916937f2 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 17 Aug 2024 21:47:16 +0200 Subject: [PATCH 088/116] Replace RandomActivation scheduler with AgentSet Replace the old RandomActivation scheduler with AgentSet functionality. self.agents.shuffle.do("step") is equivalent and self.schedule.step() with an RandomActivation scheduler, and model behavior should not change. --- .../epstein_civil_violence/epstein_civil_violence/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py index 542c419e33a..90ae1315872 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py @@ -58,7 +58,7 @@ def __init__( self.movement = movement self.max_iters = max_iters self.iteration = 0 - self.schedule = mesa.time.RandomActivation(self) + self.grid = mesa.space.SingleGrid(width, height, torus=True) model_reporters = { @@ -109,7 +109,7 @@ def step(self): """ Advance the model by one step and collect data. """ - self.schedule.step() + self.agents.shuffle().do("step") # collect data self.datacollector.collect(self) self.iteration += 1 From 9211360a35ae1722096f8f3e4bfc485d63051318 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 17 Aug 2024 22:04:10 +0200 Subject: [PATCH 089/116] Replace SimultaneousActivation scheduler by AgentSet The SimultaneousActivation scheduler used a "step" and "advanced" function in the Agent. These have been renamed for each model to be more descriptive (like "determine_state" and "assume_state"), and then the model step was replace by something like: self.agents.do("determine_state") self.agents.do("assume_state") Docstring was also updated accordingly. --- .../conways_game_of_life/cell.py | 4 ++-- .../conways_game_of_life/model.py | 16 +++++----------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/examples/basic/conways_game_of_life/conways_game_of_life/cell.py b/examples/basic/conways_game_of_life/conways_game_of_life/cell.py index 8639288d4ca..d9e0e7ba076 100644 --- a/examples/basic/conways_game_of_life/conways_game_of_life/cell.py +++ b/examples/basic/conways_game_of_life/conways_game_of_life/cell.py @@ -24,7 +24,7 @@ def isAlive(self): def neighbors(self): return self.model.grid.iter_neighbors((self.x, self.y), True) - def step(self): + def determine_state(self): """ Compute if the cell will be dead or alive at the next tick. This is based on the number of alive or dead neighbors. The state is not @@ -46,7 +46,7 @@ def step(self): if live_neighbors == 3: self._nextState = self.ALIVE - def advance(self): + def assume_state(self): """ Set the state to the new computed state -- computed in step(). """ diff --git a/examples/basic/conways_game_of_life/conways_game_of_life/model.py b/examples/basic/conways_game_of_life/conways_game_of_life/model.py index f6c9637a67e..387de9ec9b5 100644 --- a/examples/basic/conways_game_of_life/conways_game_of_life/model.py +++ b/examples/basic/conways_game_of_life/conways_game_of_life/model.py @@ -14,15 +14,6 @@ def __init__(self, width=50, height=50): Create a new playing area of (width, height) cells. """ super().__init__() - - # Set up the grid and schedule. - - # Use SimultaneousActivation which simulates all the cells - # computing their next state simultaneously. This needs to - # be done because each cell's next state depends on the current - # state of all its neighbors -- before they've changed. - self.schedule = mesa.time.SimultaneousActivation(self) - # Use a simple grid, where edges wrap around. self.grid = mesa.space.SingleGrid(width, height, torus=True) @@ -39,6 +30,9 @@ def __init__(self, width=50, height=50): def step(self): """ - Have the scheduler advance each cell by one step + Perform the model step in two stages: + - First, all cells assume their next state (whether they will be dead or alive) + - Then, all cells change state to their next state """ - self.schedule.step() + self.agents.do("determine_state") + self.agents.do("assume_state") From 145b6286babf01982640e1c8b544a93297507b43 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 17 Aug 2024 22:10:19 +0200 Subject: [PATCH 090/116] Remove adding agents to schedulers Schedulers don't exist anymore, so we don't need to add agents anymore. Agents are automatically added to model.agents on Agent initialisation. --- .../epstein_civil_violence/epstein_civil_violence/model.py | 3 +-- examples/advanced/pd_grid/pd_grid/model.py | 1 - examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py index 90ae1315872..70b3a841a91 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py @@ -86,7 +86,7 @@ def __init__( cop = Cop(unique_id, self, (x, y), vision=self.cop_vision) unique_id += 1 self.grid[x][y] = cop - self.schedule.add(cop) + elif self.random.random() < (self.cop_density + self.citizen_density): citizen = Citizen( unique_id, @@ -100,7 +100,6 @@ def __init__( ) unique_id += 1 self.grid[x][y] = citizen - self.schedule.add(citizen) self.running = True self.datacollector.collect(self) diff --git a/examples/advanced/pd_grid/pd_grid/model.py b/examples/advanced/pd_grid/pd_grid/model.py index 448e47450bf..388379ad37a 100644 --- a/examples/advanced/pd_grid/pd_grid/model.py +++ b/examples/advanced/pd_grid/pd_grid/model.py @@ -39,7 +39,6 @@ def __init__( for y in range(height): agent = PDAgent(self.next_id(), self) self.grid.place_agent(agent, (x, y)) - self.schedule.add(agent) self.datacollector = mesa.DataCollector( { diff --git a/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py b/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py index d2340fedba3..92bfdcd38f5 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py @@ -46,7 +46,7 @@ def __init__(self, width, height, agent_count): x = self.random.randrange(self.width) y = self.random.randrange(self.height) a = WalkerAgent(i, (x, y), self, True) - self.schedule.add(a) + self.grid.place_agent(a, (x, y)) def step(self): From 1be2dec532e33a3e9f026494fae2f8be157e1eae Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 17 Aug 2024 22:10:19 +0200 Subject: [PATCH 091/116] Remove adding agents to schedulers Schedulers don't exist anymore, so we don't need to add agents anymore. Agents are automatically added to model.agents on Agent initialisation. --- examples/basic/boid_flockers/boid_flockers/model.py | 1 - .../boltzmann_wealth_model/boltzmann_wealth_model/model.py | 2 +- .../basic/conways_game_of_life/conways_game_of_life/model.py | 1 - examples/basic/schelling/model.py | 1 - examples/basic/virus_on_network/virus_on_network/model.py | 2 +- 5 files changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/basic/boid_flockers/boid_flockers/model.py b/examples/basic/boid_flockers/boid_flockers/model.py index f9cd39344fe..6b032c33592 100644 --- a/examples/basic/boid_flockers/boid_flockers/model.py +++ b/examples/basic/boid_flockers/boid_flockers/model.py @@ -144,7 +144,6 @@ def make_agents(self): **self.factors, ) self.space.place_agent(boid, pos) - self.schedule.add(boid) def step(self): self.agents.shuffle().do("step") diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py index 9a16a69e90b..c34d0937dd3 100644 --- a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -28,7 +28,7 @@ def __init__(self, N=100, width=10, height=10): # Create agents for i in range(self.num_agents): a = MoneyAgent(i, self) - self.schedule.add(a) + # Add the agent to a random grid cell x = self.random.randrange(self.grid.width) y = self.random.randrange(self.grid.height) diff --git a/examples/basic/conways_game_of_life/conways_game_of_life/model.py b/examples/basic/conways_game_of_life/conways_game_of_life/model.py index 387de9ec9b5..76d9ca9fef4 100644 --- a/examples/basic/conways_game_of_life/conways_game_of_life/model.py +++ b/examples/basic/conways_game_of_life/conways_game_of_life/model.py @@ -24,7 +24,6 @@ def __init__(self, width=50, height=50): if self.random.random() < 0.1: cell.state = cell.ALIVE self.grid.place_agent(cell, (x, y)) - self.schedule.add(cell) self.running = True diff --git a/examples/basic/schelling/model.py b/examples/basic/schelling/model.py index 5c3c15df4e2..e271bcb2b2a 100644 --- a/examples/basic/schelling/model.py +++ b/examples/basic/schelling/model.py @@ -84,7 +84,6 @@ def __init__( agent_type = 1 if self.random.random() < self.minority_pc else 0 agent = SchellingAgent(self.next_id(), self, agent_type) self.grid.place_agent(agent, pos) - self.schedule.add(agent) self.datacollector.collect(self) diff --git a/examples/basic/virus_on_network/virus_on_network/model.py b/examples/basic/virus_on_network/virus_on_network/model.py index 65d2356093b..7c69bdcd94e 100644 --- a/examples/basic/virus_on_network/virus_on_network/model.py +++ b/examples/basic/virus_on_network/virus_on_network/model.py @@ -75,7 +75,7 @@ def __init__( self.recovery_chance, self.gain_resistance_chance, ) - self.schedule.add(a) + # Add the agent to the node self.grid.place_agent(a, node) From 12aa3edf85372b7032dad91101b8a1cdfb068526 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 17 Aug 2024 22:25:58 +0200 Subject: [PATCH 092/116] Restore Sugerscape and Wolf-sheep schedulers These models use complex schedulers which warrant an separate PR. --- examples/advanced/sugarscape_g1mt/tests.py | 2 +- examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/tests.py b/examples/advanced/sugarscape_g1mt/tests.py index 70deaecb817..bcfcf73931d 100644 --- a/examples/advanced/sugarscape_g1mt/tests.py +++ b/examples/advanced/sugarscape_g1mt/tests.py @@ -40,7 +40,7 @@ def calculate_carrying_capacities(enable_trade): for vision_max in visions: model = SugarscapeG1mt(vision_max=vision_max, enable_trade=enable_trade) model.run_model(step_count=50) - carrying_capacities.append(len(model.agents_by_type[Trader])) + carrying_capacities.append(len(model.schedule.agents_by_type[Trader])) return carrying_capacities # Carrying capacity should increase over mean vision (figure IV-6). diff --git a/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py b/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py index 92bfdcd38f5..d2340fedba3 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py @@ -46,7 +46,7 @@ def __init__(self, width, height, agent_count): x = self.random.randrange(self.width) y = self.random.randrange(self.height) a = WalkerAgent(i, (x, y), self, True) - + self.schedule.add(a) self.grid.place_agent(a, (x, y)) def step(self): From 25fadfb45a29f82cb24c8691cb51afbc69a1fb57 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 17 Aug 2024 22:20:51 +0200 Subject: [PATCH 093/116] Custom schedule replacements --- examples/basic/schelling/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/basic/schelling/model.py b/examples/basic/schelling/model.py index e271bcb2b2a..5aa71415446 100644 --- a/examples/basic/schelling/model.py +++ b/examples/basic/schelling/model.py @@ -96,5 +96,5 @@ def step(self): self.datacollector.collect(self) - if self.happy == self.schedule.get_agent_count(): + if self.happy == len(self.agents): self.running = False From a0f6ab6c660dd119a0ef222e35503de256de8526 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 17 Aug 2024 22:32:49 +0200 Subject: [PATCH 094/116] Final fixes --- examples/advanced/pd_grid/pd_grid/model.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/advanced/pd_grid/pd_grid/model.py b/examples/advanced/pd_grid/pd_grid/model.py index 388379ad37a..448e47450bf 100644 --- a/examples/advanced/pd_grid/pd_grid/model.py +++ b/examples/advanced/pd_grid/pd_grid/model.py @@ -39,6 +39,7 @@ def __init__( for y in range(height): agent = PDAgent(self.next_id(), self) self.grid.place_agent(agent, (x, y)) + self.schedule.add(agent) self.datacollector = mesa.DataCollector( { From 4eb166d4b38ceabb3b0b49396dfbec03163a305f Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 17 Aug 2024 22:32:49 +0200 Subject: [PATCH 095/116] Final fixes --- examples/basic/schelling/analysis.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/basic/schelling/analysis.ipynb b/examples/basic/schelling/analysis.ipynb index c3c93651f23..50f382c66a0 100644 --- a/examples/basic/schelling/analysis.ipynb +++ b/examples/basic/schelling/analysis.ipynb @@ -328,7 +328,7 @@ " Find the % of agents that only have neighbors of their same type.\n", " \"\"\"\n", " segregated_agents = 0\n", - " for agent in model.agents:\n", + " for agent in model.schedule.agents:\n", " segregated = True\n", " for neighbor in model.grid.iter_neighbors(agent.pos, True):\n", " if neighbor.type != agent.type:\n", From e112c0b62311710ef1a1e0314eafe6385c4a5854 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Thu, 22 Aug 2024 15:13:51 +0200 Subject: [PATCH 096/116] Revert PR #161: Replace schedulers with AgentSet functionality (#170) This commit reverts PR #161 https://github.com/projectmesa/mesa-examples/pull/161 That PR assumed that time advancement would be done automatically, like proposed in https://github.com/projectmesa/mesa/pull/2223 We encountered some underlying issues with time, which we couldn't resolve in time. --- .../epstein_civil_violence/model.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py index 70b3a841a91..6bce24ebe23 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py @@ -58,7 +58,7 @@ def __init__( self.movement = movement self.max_iters = max_iters self.iteration = 0 - + self.schedule = mesa.time.RandomActivation(self) self.grid = mesa.space.SingleGrid(width, height, torus=True) model_reporters = { @@ -86,7 +86,7 @@ def __init__( cop = Cop(unique_id, self, (x, y), vision=self.cop_vision) unique_id += 1 self.grid[x][y] = cop - + self.schedule.add(cop) elif self.random.random() < (self.cop_density + self.citizen_density): citizen = Citizen( unique_id, @@ -100,6 +100,7 @@ def __init__( ) unique_id += 1 self.grid[x][y] = citizen + self.schedule.add(citizen) self.running = True self.datacollector.collect(self) @@ -108,7 +109,7 @@ def step(self): """ Advance the model by one step and collect data. """ - self.agents.shuffle().do("step") + self.schedule.step() # collect data self.datacollector.collect(self) self.iteration += 1 @@ -121,7 +122,7 @@ def count_type_citizens(model, condition, exclude_jailed=True): Helper method to count agents by Quiescent/Active. """ count = 0 - for agent in model.agents: + for agent in model.schedule.agents: if agent.breed == "cop": continue if exclude_jailed and agent.jail_sentence > 0: @@ -136,7 +137,7 @@ def count_jailed(model): Helper method to count jailed agents. """ count = 0 - for agent in model.agents: + for agent in model.schedule.agents: if agent.breed == "citizen" and agent.jail_sentence > 0: count += 1 return count @@ -147,7 +148,7 @@ def count_cops(model): Helper method to count jailed agents. """ count = 0 - for agent in model.agents: + for agent in model.schedule.agents: if agent.breed == "cop": count += 1 return count From 9f27caff6bde6af77dc44443e1ce89443a22ef7e Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Thu, 22 Aug 2024 15:13:51 +0200 Subject: [PATCH 097/116] Revert PR #161: Replace schedulers with AgentSet functionality (#170) This commit reverts PR #161 https://github.com/projectmesa/mesa-examples/pull/161 That PR assumed that time advancement would be done automatically, like proposed in https://github.com/projectmesa/mesa/pull/2223 We encountered some underlying issues with time, which we couldn't resolve in time. --- examples/basic/boid_flockers/Flocker Test.ipynb | 2 +- .../boid_flockers/SimpleContinuousModule.py | 2 +- .../basic/boid_flockers/boid_flockers/model.py | 5 +++-- .../boltzmann_wealth_model/model.py | 8 ++++---- .../conways_game_of_life/cell.py | 4 ++-- .../conways_game_of_life/model.py | 17 ++++++++++++----- examples/basic/schelling/model.py | 6 ++++-- .../virus_on_network/virus_on_network/model.py | 6 +++--- 8 files changed, 30 insertions(+), 20 deletions(-) diff --git a/examples/basic/boid_flockers/Flocker Test.ipynb b/examples/basic/boid_flockers/Flocker Test.ipynb index c757f3a88ed..82ecc47b99f 100644 --- a/examples/basic/boid_flockers/Flocker Test.ipynb +++ b/examples/basic/boid_flockers/Flocker Test.ipynb @@ -25,7 +25,7 @@ "def draw_boids(model):\n", " x_vals = []\n", " y_vals = []\n", - " for boid in model.agents:\n", + " for boid in model.schedule.agents:\n", " x, y = boid.pos\n", " x_vals.append(x)\n", " y_vals.append(y)\n", diff --git a/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py b/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py index ec670d7af1c..42b3e9dd76f 100644 --- a/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py +++ b/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py @@ -18,7 +18,7 @@ def __init__(self, portrayal_method=None, canvas_height=500, canvas_width=500): def render(self, model): space_state = [] - for obj in model.agents: + for obj in model.schedule.agents: portrayal = self.portrayal_method(obj) x, y = obj.pos x = (x - model.space.x_min) / (model.space.x_max - model.space.x_min) diff --git a/examples/basic/boid_flockers/boid_flockers/model.py b/examples/basic/boid_flockers/boid_flockers/model.py index 6b032c33592..8ddfc11a2f4 100644 --- a/examples/basic/boid_flockers/boid_flockers/model.py +++ b/examples/basic/boid_flockers/boid_flockers/model.py @@ -120,7 +120,7 @@ def __init__( self.vision = vision self.speed = speed self.separation = separation - + self.schedule = mesa.time.RandomActivation(self) self.space = mesa.space.ContinuousSpace(width, height, True) self.factors = {"cohere": cohere, "separate": separate, "match": match} self.make_agents() @@ -144,6 +144,7 @@ def make_agents(self): **self.factors, ) self.space.place_agent(boid, pos) + self.schedule.add(boid) def step(self): - self.agents.shuffle().do("step") + self.schedule.step() diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py index c34d0937dd3..11a3e95878a 100644 --- a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -2,7 +2,7 @@ def compute_gini(model): - agent_wealths = [agent.wealth for agent in model.agents] + agent_wealths = [agent.wealth for agent in model.schedule.agents] x = sorted(agent_wealths) N = model.num_agents B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x)) @@ -21,14 +21,14 @@ def __init__(self, N=100, width=10, height=10): super().__init__() self.num_agents = N self.grid = mesa.space.MultiGrid(width, height, True) - + self.schedule = mesa.time.RandomActivation(self) self.datacollector = mesa.DataCollector( model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} ) # Create agents for i in range(self.num_agents): a = MoneyAgent(i, self) - + self.schedule.add(a) # Add the agent to a random grid cell x = self.random.randrange(self.grid.width) y = self.random.randrange(self.grid.height) @@ -38,7 +38,7 @@ def __init__(self, N=100, width=10, height=10): self.datacollector.collect(self) def step(self): - self.agents.shuffle().do("step") + self.schedule.step() # collect data self.datacollector.collect(self) diff --git a/examples/basic/conways_game_of_life/conways_game_of_life/cell.py b/examples/basic/conways_game_of_life/conways_game_of_life/cell.py index d9e0e7ba076..8639288d4ca 100644 --- a/examples/basic/conways_game_of_life/conways_game_of_life/cell.py +++ b/examples/basic/conways_game_of_life/conways_game_of_life/cell.py @@ -24,7 +24,7 @@ def isAlive(self): def neighbors(self): return self.model.grid.iter_neighbors((self.x, self.y), True) - def determine_state(self): + def step(self): """ Compute if the cell will be dead or alive at the next tick. This is based on the number of alive or dead neighbors. The state is not @@ -46,7 +46,7 @@ def determine_state(self): if live_neighbors == 3: self._nextState = self.ALIVE - def assume_state(self): + def advance(self): """ Set the state to the new computed state -- computed in step(). """ diff --git a/examples/basic/conways_game_of_life/conways_game_of_life/model.py b/examples/basic/conways_game_of_life/conways_game_of_life/model.py index 76d9ca9fef4..f6c9637a67e 100644 --- a/examples/basic/conways_game_of_life/conways_game_of_life/model.py +++ b/examples/basic/conways_game_of_life/conways_game_of_life/model.py @@ -14,6 +14,15 @@ def __init__(self, width=50, height=50): Create a new playing area of (width, height) cells. """ super().__init__() + + # Set up the grid and schedule. + + # Use SimultaneousActivation which simulates all the cells + # computing their next state simultaneously. This needs to + # be done because each cell's next state depends on the current + # state of all its neighbors -- before they've changed. + self.schedule = mesa.time.SimultaneousActivation(self) + # Use a simple grid, where edges wrap around. self.grid = mesa.space.SingleGrid(width, height, torus=True) @@ -24,14 +33,12 @@ def __init__(self, width=50, height=50): if self.random.random() < 0.1: cell.state = cell.ALIVE self.grid.place_agent(cell, (x, y)) + self.schedule.add(cell) self.running = True def step(self): """ - Perform the model step in two stages: - - First, all cells assume their next state (whether they will be dead or alive) - - Then, all cells change state to their next state + Have the scheduler advance each cell by one step """ - self.agents.do("determine_state") - self.agents.do("assume_state") + self.schedule.step() diff --git a/examples/basic/schelling/model.py b/examples/basic/schelling/model.py index 5aa71415446..dfba4efb2b1 100644 --- a/examples/basic/schelling/model.py +++ b/examples/basic/schelling/model.py @@ -68,6 +68,7 @@ def __init__( self.homophily = homophily self.radius = radius + self.schedule = mesa.time.RandomActivation(self) self.grid = mesa.space.SingleGrid(width, height, torus=True) self.happy = 0 @@ -84,6 +85,7 @@ def __init__( agent_type = 1 if self.random.random() < self.minority_pc else 0 agent = SchellingAgent(self.next_id(), self, agent_type) self.grid.place_agent(agent, pos) + self.schedule.add(agent) self.datacollector.collect(self) @@ -92,9 +94,9 @@ def step(self): Run one step of the model. """ self.happy = 0 # Reset counter of happy agents - self.agents.shuffle().do("step") + self.schedule.step() self.datacollector.collect(self) - if self.happy == len(self.agents): + if self.happy == self.schedule.get_agent_count(): self.running = False diff --git a/examples/basic/virus_on_network/virus_on_network/model.py b/examples/basic/virus_on_network/virus_on_network/model.py index 7c69bdcd94e..a33e7545861 100644 --- a/examples/basic/virus_on_network/virus_on_network/model.py +++ b/examples/basic/virus_on_network/virus_on_network/model.py @@ -47,7 +47,7 @@ def __init__( prob = avg_node_degree / self.num_nodes self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=prob) self.grid = mesa.space.NetworkGrid(self.G) - + self.schedule = mesa.time.RandomActivation(self) self.initial_outbreak_size = ( initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes ) @@ -75,7 +75,7 @@ def __init__( self.recovery_chance, self.gain_resistance_chance, ) - + self.schedule.add(a) # Add the agent to the node self.grid.place_agent(a, node) @@ -96,7 +96,7 @@ def resistant_susceptible_ratio(self): return math.inf def step(self): - self.agents.shuffle().do("step") + self.schedule.step() # collect data self.datacollector.collect(self) From 227b6b25ae91ad70af0deca7e7b5505e373384a5 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Wed, 28 Aug 2024 09:49:20 +0200 Subject: [PATCH 098/116] Ensure grasspatches info is only collected if gras is true (#181) the datacollector allways includes grasspatches even if grass is False. This is breaking (the proposed changes to agent storage in Model. The underlying problem is that Model.get_agents_of_type now raises a KeyError if type does not exist. I think this is desirable behavior becuase errrors should not be passed over in silence. But this requires fixing the wolf sheep example as done here. --- .../advanced/wolf_sheep/wolf_sheep/model.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/advanced/wolf_sheep/wolf_sheep/model.py b/examples/advanced/wolf_sheep/wolf_sheep/model.py index 59f1835c0be..12db261a86a 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/model.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/model.py @@ -79,15 +79,16 @@ def __init__( self.sheep_gain_from_food = sheep_gain_from_food self.grid = mesa.space.MultiGrid(self.width, self.height, torus=True) - self.datacollector = mesa.DataCollector( - { - "Wolves": lambda m: len(m.get_agents_of_type(Wolf)), - "Sheep": lambda m: len(m.get_agents_of_type(Sheep)), - "Grass": lambda m: len( - m.get_agents_of_type(GrassPatch).select(lambda a: a.fully_grown) - ), - } - ) + + collectors = { + "Wolves": lambda m: len(m.get_agents_of_type(Wolf)), + "Sheep": lambda m: len(m.get_agents_of_type(Sheep)), + } + + if grass: + collectors["Grass"] = lambda m: len(m.get_agents_of_type(GrassPatch)) + + self.datacollector = mesa.DataCollector(collectors) # Create sheep: for i in range(self.initial_sheep): From 64c9709c022d99eccfec87f7b9effa71122ad0de Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Thu, 29 Aug 2024 17:57:02 +0200 Subject: [PATCH 099/116] Update SugarscapeG1mt for automatic time advancement --- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index 015c745c061..6d82c07cf52 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -150,7 +150,6 @@ def step(self): if not self.enable_trade: # If trade is not enabled, return early - self._steps += 1 self.datacollector.collect(self) return @@ -159,7 +158,6 @@ def step(self): for agent in trader_shuffle: agent.trade_with_neighbors() - self._steps += 1 # collect model level data self.datacollector.collect(self) """ @@ -175,11 +173,11 @@ def step(self): """ # Need to remove excess data # Create local variable to store trade data - agent_trades = self.datacollector._agent_records[self._steps] + agent_trades = self.datacollector._agent_records[self.steps] # Get rid of all None to reduce data storage needs agent_trades = [agent for agent in agent_trades if agent[2] is not None] # Reassign the dictionary value with lean trade data - self.datacollector._agent_records[self._steps] = agent_trades + self.datacollector._agent_records[self.steps] = agent_trades def run_model(self, step_count=1000): for i in range(step_count): From b1d5a9398b59ca7b7d6a215e724a5c1fbb2f190b Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Fri, 30 Aug 2024 10:30:22 +0200 Subject: [PATCH 100/116] Reinstate PR #161: Replace schedulers with AgentSet functionality This reinstates PR #161 after the previous revert. --- .../epstein_civil_violence/model.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py index 6bce24ebe23..70b3a841a91 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py @@ -58,7 +58,7 @@ def __init__( self.movement = movement self.max_iters = max_iters self.iteration = 0 - self.schedule = mesa.time.RandomActivation(self) + self.grid = mesa.space.SingleGrid(width, height, torus=True) model_reporters = { @@ -86,7 +86,7 @@ def __init__( cop = Cop(unique_id, self, (x, y), vision=self.cop_vision) unique_id += 1 self.grid[x][y] = cop - self.schedule.add(cop) + elif self.random.random() < (self.cop_density + self.citizen_density): citizen = Citizen( unique_id, @@ -100,7 +100,6 @@ def __init__( ) unique_id += 1 self.grid[x][y] = citizen - self.schedule.add(citizen) self.running = True self.datacollector.collect(self) @@ -109,7 +108,7 @@ def step(self): """ Advance the model by one step and collect data. """ - self.schedule.step() + self.agents.shuffle().do("step") # collect data self.datacollector.collect(self) self.iteration += 1 @@ -122,7 +121,7 @@ def count_type_citizens(model, condition, exclude_jailed=True): Helper method to count agents by Quiescent/Active. """ count = 0 - for agent in model.schedule.agents: + for agent in model.agents: if agent.breed == "cop": continue if exclude_jailed and agent.jail_sentence > 0: @@ -137,7 +136,7 @@ def count_jailed(model): Helper method to count jailed agents. """ count = 0 - for agent in model.schedule.agents: + for agent in model.agents: if agent.breed == "citizen" and agent.jail_sentence > 0: count += 1 return count @@ -148,7 +147,7 @@ def count_cops(model): Helper method to count jailed agents. """ count = 0 - for agent in model.schedule.agents: + for agent in model.agents: if agent.breed == "cop": count += 1 return count From 90d668c2e735ba1b5d068632881e6b5ac28357a6 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Fri, 30 Aug 2024 10:30:22 +0200 Subject: [PATCH 101/116] Reinstate PR #161: Replace schedulers with AgentSet functionality This reinstates PR #161 after the previous revert. --- examples/basic/boid_flockers/Flocker Test.ipynb | 2 +- .../boid_flockers/SimpleContinuousModule.py | 2 +- .../basic/boid_flockers/boid_flockers/model.py | 5 ++--- .../boltzmann_wealth_model/model.py | 8 ++++---- .../conways_game_of_life/cell.py | 4 ++-- .../conways_game_of_life/model.py | 17 +++++------------ examples/basic/schelling/model.py | 6 ++---- .../virus_on_network/virus_on_network/model.py | 6 +++--- 8 files changed, 20 insertions(+), 30 deletions(-) diff --git a/examples/basic/boid_flockers/Flocker Test.ipynb b/examples/basic/boid_flockers/Flocker Test.ipynb index 82ecc47b99f..c757f3a88ed 100644 --- a/examples/basic/boid_flockers/Flocker Test.ipynb +++ b/examples/basic/boid_flockers/Flocker Test.ipynb @@ -25,7 +25,7 @@ "def draw_boids(model):\n", " x_vals = []\n", " y_vals = []\n", - " for boid in model.schedule.agents:\n", + " for boid in model.agents:\n", " x, y = boid.pos\n", " x_vals.append(x)\n", " y_vals.append(y)\n", diff --git a/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py b/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py index 42b3e9dd76f..ec670d7af1c 100644 --- a/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py +++ b/examples/basic/boid_flockers/boid_flockers/SimpleContinuousModule.py @@ -18,7 +18,7 @@ def __init__(self, portrayal_method=None, canvas_height=500, canvas_width=500): def render(self, model): space_state = [] - for obj in model.schedule.agents: + for obj in model.agents: portrayal = self.portrayal_method(obj) x, y = obj.pos x = (x - model.space.x_min) / (model.space.x_max - model.space.x_min) diff --git a/examples/basic/boid_flockers/boid_flockers/model.py b/examples/basic/boid_flockers/boid_flockers/model.py index 8ddfc11a2f4..6b032c33592 100644 --- a/examples/basic/boid_flockers/boid_flockers/model.py +++ b/examples/basic/boid_flockers/boid_flockers/model.py @@ -120,7 +120,7 @@ def __init__( self.vision = vision self.speed = speed self.separation = separation - self.schedule = mesa.time.RandomActivation(self) + self.space = mesa.space.ContinuousSpace(width, height, True) self.factors = {"cohere": cohere, "separate": separate, "match": match} self.make_agents() @@ -144,7 +144,6 @@ def make_agents(self): **self.factors, ) self.space.place_agent(boid, pos) - self.schedule.add(boid) def step(self): - self.schedule.step() + self.agents.shuffle().do("step") diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py index 11a3e95878a..c34d0937dd3 100644 --- a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -2,7 +2,7 @@ def compute_gini(model): - agent_wealths = [agent.wealth for agent in model.schedule.agents] + agent_wealths = [agent.wealth for agent in model.agents] x = sorted(agent_wealths) N = model.num_agents B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x)) @@ -21,14 +21,14 @@ def __init__(self, N=100, width=10, height=10): super().__init__() self.num_agents = N self.grid = mesa.space.MultiGrid(width, height, True) - self.schedule = mesa.time.RandomActivation(self) + self.datacollector = mesa.DataCollector( model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} ) # Create agents for i in range(self.num_agents): a = MoneyAgent(i, self) - self.schedule.add(a) + # Add the agent to a random grid cell x = self.random.randrange(self.grid.width) y = self.random.randrange(self.grid.height) @@ -38,7 +38,7 @@ def __init__(self, N=100, width=10, height=10): self.datacollector.collect(self) def step(self): - self.schedule.step() + self.agents.shuffle().do("step") # collect data self.datacollector.collect(self) diff --git a/examples/basic/conways_game_of_life/conways_game_of_life/cell.py b/examples/basic/conways_game_of_life/conways_game_of_life/cell.py index 8639288d4ca..d9e0e7ba076 100644 --- a/examples/basic/conways_game_of_life/conways_game_of_life/cell.py +++ b/examples/basic/conways_game_of_life/conways_game_of_life/cell.py @@ -24,7 +24,7 @@ def isAlive(self): def neighbors(self): return self.model.grid.iter_neighbors((self.x, self.y), True) - def step(self): + def determine_state(self): """ Compute if the cell will be dead or alive at the next tick. This is based on the number of alive or dead neighbors. The state is not @@ -46,7 +46,7 @@ def step(self): if live_neighbors == 3: self._nextState = self.ALIVE - def advance(self): + def assume_state(self): """ Set the state to the new computed state -- computed in step(). """ diff --git a/examples/basic/conways_game_of_life/conways_game_of_life/model.py b/examples/basic/conways_game_of_life/conways_game_of_life/model.py index f6c9637a67e..76d9ca9fef4 100644 --- a/examples/basic/conways_game_of_life/conways_game_of_life/model.py +++ b/examples/basic/conways_game_of_life/conways_game_of_life/model.py @@ -14,15 +14,6 @@ def __init__(self, width=50, height=50): Create a new playing area of (width, height) cells. """ super().__init__() - - # Set up the grid and schedule. - - # Use SimultaneousActivation which simulates all the cells - # computing their next state simultaneously. This needs to - # be done because each cell's next state depends on the current - # state of all its neighbors -- before they've changed. - self.schedule = mesa.time.SimultaneousActivation(self) - # Use a simple grid, where edges wrap around. self.grid = mesa.space.SingleGrid(width, height, torus=True) @@ -33,12 +24,14 @@ def __init__(self, width=50, height=50): if self.random.random() < 0.1: cell.state = cell.ALIVE self.grid.place_agent(cell, (x, y)) - self.schedule.add(cell) self.running = True def step(self): """ - Have the scheduler advance each cell by one step + Perform the model step in two stages: + - First, all cells assume their next state (whether they will be dead or alive) + - Then, all cells change state to their next state """ - self.schedule.step() + self.agents.do("determine_state") + self.agents.do("assume_state") diff --git a/examples/basic/schelling/model.py b/examples/basic/schelling/model.py index dfba4efb2b1..5aa71415446 100644 --- a/examples/basic/schelling/model.py +++ b/examples/basic/schelling/model.py @@ -68,7 +68,6 @@ def __init__( self.homophily = homophily self.radius = radius - self.schedule = mesa.time.RandomActivation(self) self.grid = mesa.space.SingleGrid(width, height, torus=True) self.happy = 0 @@ -85,7 +84,6 @@ def __init__( agent_type = 1 if self.random.random() < self.minority_pc else 0 agent = SchellingAgent(self.next_id(), self, agent_type) self.grid.place_agent(agent, pos) - self.schedule.add(agent) self.datacollector.collect(self) @@ -94,9 +92,9 @@ def step(self): Run one step of the model. """ self.happy = 0 # Reset counter of happy agents - self.schedule.step() + self.agents.shuffle().do("step") self.datacollector.collect(self) - if self.happy == self.schedule.get_agent_count(): + if self.happy == len(self.agents): self.running = False diff --git a/examples/basic/virus_on_network/virus_on_network/model.py b/examples/basic/virus_on_network/virus_on_network/model.py index a33e7545861..7c69bdcd94e 100644 --- a/examples/basic/virus_on_network/virus_on_network/model.py +++ b/examples/basic/virus_on_network/virus_on_network/model.py @@ -47,7 +47,7 @@ def __init__( prob = avg_node_degree / self.num_nodes self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=prob) self.grid = mesa.space.NetworkGrid(self.G) - self.schedule = mesa.time.RandomActivation(self) + self.initial_outbreak_size = ( initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes ) @@ -75,7 +75,7 @@ def __init__( self.recovery_chance, self.gain_resistance_chance, ) - self.schedule.add(a) + # Add the agent to the node self.grid.place_agent(a, node) @@ -96,7 +96,7 @@ def resistant_susceptible_ratio(self): return math.inf def step(self): - self.schedule.step() + self.agents.shuffle().do("step") # collect data self.datacollector.collect(self) From 83c56c54642d21e7b80a222eba3d0e46601f685d Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Tue, 3 Sep 2024 18:49:47 +0200 Subject: [PATCH 102/116] Replace `get_agents_of_type` method with `agents_by_type` property (#190) The Model method `get_agents_of_type()` is replaced by the `agents_by_type` property, which directly returns the dict. Instead of using: ```Python model.get_agents_of_type(Wolf) ``` You should now use: ```Python model.agents_by_type[Wolf] ``` --- .../sugarscape_g1mt/sugarscape_g1mt/model.py | 12 ++++++------ examples/advanced/wolf_sheep/wolf_sheep/model.py | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index 6d82c07cf52..06a5032b23d 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -73,12 +73,12 @@ def __init__( # initiate datacollector self.datacollector = mesa.DataCollector( model_reporters={ - "Trader": lambda m: len(m.get_agents_of_type(Trader)), + "Trader": lambda m: len(m.agents_by_type[Trader]), "Trade Volume": lambda m: sum( - len(a.trade_partners) for a in m.get_agents_of_type(Trader) + len(a.trade_partners) for a in m.agents_by_type[Trader] ), "Price": lambda m: geometric_mean( - flatten([a.prices for a in m.get_agents_of_type(Trader)]) + flatten([a.prices for a in m.agents_by_type[Trader]]) ), }, agent_reporters={"Trade Network": lambda a: get_trade(a)}, @@ -134,12 +134,12 @@ def step(self): and then randomly activates traders """ # step Resource agents - self.get_agents_of_type(Resource).do("step") + self.agents_by_type[Resource].do("step") # step trader agents # to account for agent death and removal we need a seperate data strcuture to # iterate - trader_shuffle = self.get_agents_of_type(Trader).shuffle() + trader_shuffle = self.agents_by_type[Trader].shuffle() for agent in trader_shuffle: agent.prices = [] @@ -153,7 +153,7 @@ def step(self): self.datacollector.collect(self) return - trader_shuffle = self.get_agents_of_type(Trader).shuffle() + trader_shuffle = self.agents_by_type[Trader].shuffle() for agent in trader_shuffle: agent.trade_with_neighbors() diff --git a/examples/advanced/wolf_sheep/wolf_sheep/model.py b/examples/advanced/wolf_sheep/wolf_sheep/model.py index 12db261a86a..1dc1e1a53e7 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/model.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/model.py @@ -81,12 +81,12 @@ def __init__( self.grid = mesa.space.MultiGrid(self.width, self.height, torus=True) collectors = { - "Wolves": lambda m: len(m.get_agents_of_type(Wolf)), - "Sheep": lambda m: len(m.get_agents_of_type(Sheep)), + "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.get_agents_of_type(GrassPatch)) + collectors["Grass"] = lambda m: len(m.agents_by_type[GrassPatch]) self.datacollector = mesa.DataCollector(collectors) @@ -128,7 +128,7 @@ def step(self): # 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.get_agents_of_type(agent_type).do("step") + self.agents_by_type[agent_type].do("step") # collect data self.datacollector.collect(self) From c1e051bf48bc77e9c9003eb62e7c7043ab9d8509 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Thu, 5 Sep 2024 11:37:12 +0200 Subject: [PATCH 103/116] Remove unique_id and model.next_id (#194) All examples can be updated to no longer pass a unique id, nor use model.next_id. This only fixes the examples, not the gis-examples or rl examples. Co-authored-by: Ewout ter Hoeven --- examples/basic/boid_flockers/boid_flockers/model.py | 7 ++----- .../boltzmann_wealth_model/model.py | 8 ++++---- .../conways_game_of_life/conways_game_of_life/cell.py | 2 +- examples/basic/schelling/model.py | 7 +++---- examples/basic/virus_on_network/virus_on_network/model.py | 6 ++---- 5 files changed, 12 insertions(+), 18 deletions(-) diff --git a/examples/basic/boid_flockers/boid_flockers/model.py b/examples/basic/boid_flockers/boid_flockers/model.py index 6b032c33592..312986bd207 100644 --- a/examples/basic/boid_flockers/boid_flockers/model.py +++ b/examples/basic/boid_flockers/boid_flockers/model.py @@ -26,7 +26,6 @@ class Boid(mesa.Agent): def __init__( self, - unique_id, model, speed, direction, @@ -40,7 +39,6 @@ def __init__( Create a new Boid flocker agent. Args: - unique_id: Unique agent identifier. speed: Distance to move per step. direction: numpy vector for the Boid's direction of movement. vision: Radius to look around for nearby Boids. @@ -49,7 +47,7 @@ def __init__( separate: the relative importance of avoiding close neighbors match: the relative importance of matching neighbors' headings """ - super().__init__(unique_id, model) + super().__init__(model) self.speed = speed self.direction = direction self.vision = vision @@ -129,13 +127,12 @@ def make_agents(self): """ Create self.population agents, with random positions and starting headings. """ - for i in range(self.population): + for _ in range(self.population): x = self.random.random() * self.space.x_max y = self.random.random() * self.space.y_max pos = np.array((x, y)) direction = np.random.random(2) * 2 - 1 boid = Boid( - unique_id=i, model=self, speed=self.speed, direction=direction, diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py index c34d0937dd3..5c6d1f88d7e 100644 --- a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -26,8 +26,8 @@ def __init__(self, N=100, width=10, height=10): model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} ) # Create agents - for i in range(self.num_agents): - a = MoneyAgent(i, self) + for _ in range(self.num_agents): + a = MoneyAgent(self) # Add the agent to a random grid cell x = self.random.randrange(self.grid.width) @@ -50,8 +50,8 @@ def run_model(self, n): class MoneyAgent(mesa.Agent): """An agent with fixed initial wealth.""" - def __init__(self, unique_id, model): - super().__init__(unique_id, model) + def __init__(self, model): + super().__init__(model) self.wealth = 1 def move(self): diff --git a/examples/basic/conways_game_of_life/conways_game_of_life/cell.py b/examples/basic/conways_game_of_life/conways_game_of_life/cell.py index d9e0e7ba076..35c8d3f2791 100644 --- a/examples/basic/conways_game_of_life/conways_game_of_life/cell.py +++ b/examples/basic/conways_game_of_life/conways_game_of_life/cell.py @@ -11,7 +11,7 @@ def __init__(self, pos, model, init_state=DEAD): """ Create a cell, in the given state, at the given x, y position. """ - super().__init__(pos, model) + super().__init__(model) self.x, self.y = pos self.state = init_state self._nextState = None diff --git a/examples/basic/schelling/model.py b/examples/basic/schelling/model.py index 5aa71415446..aec6d5b504e 100644 --- a/examples/basic/schelling/model.py +++ b/examples/basic/schelling/model.py @@ -6,16 +6,15 @@ class SchellingAgent(mesa.Agent): Schelling segregation agent """ - def __init__(self, unique_id, model, agent_type): + def __init__(self, model, agent_type): """ Create a new Schelling agent. Args: - unique_id: Unique identifier for the agent. x, y: Agent initial location. agent_type: Indicator for the agent's type (minority=1, majority=0) """ - super().__init__(unique_id, model) + super().__init__(model) self.type = agent_type def step(self): @@ -82,7 +81,7 @@ def __init__( for _, pos in self.grid.coord_iter(): if self.random.random() < self.density: agent_type = 1 if self.random.random() < self.minority_pc else 0 - agent = SchellingAgent(self.next_id(), self, agent_type) + agent = SchellingAgent(self, agent_type) self.grid.place_agent(agent, pos) self.datacollector.collect(self) diff --git a/examples/basic/virus_on_network/virus_on_network/model.py b/examples/basic/virus_on_network/virus_on_network/model.py index 7c69bdcd94e..2df667bcac6 100644 --- a/examples/basic/virus_on_network/virus_on_network/model.py +++ b/examples/basic/virus_on_network/virus_on_network/model.py @@ -65,9 +65,8 @@ def __init__( ) # Create agents - for i, node in enumerate(self.G.nodes()): + for node in self.G.nodes(): a = VirusAgent( - i, self, State.SUSCEPTIBLE, self.virus_spread_chance, @@ -112,7 +111,6 @@ class VirusAgent(mesa.Agent): def __init__( self, - unique_id, model, initial_state, virus_spread_chance, @@ -120,7 +118,7 @@ def __init__( recovery_chance, gain_resistance_chance, ): - super().__init__(unique_id, model) + super().__init__(model) self.state = initial_state From 15aa75708d4252322b65f3554700df6ca8961a5a Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Thu, 5 Sep 2024 11:37:12 +0200 Subject: [PATCH 104/116] Remove unique_id and model.next_id (#194) All examples can be updated to no longer pass a unique id, nor use model.next_id. This only fixes the examples, not the gis-examples or rl examples. Co-authored-by: Ewout ter Hoeven --- .../epstein_civil_violence/agent.py | 10 +++------- .../epstein_civil_violence/model.py | 7 ++----- examples/advanced/pd_grid/pd_grid/agent.py | 5 ++--- examples/advanced/pd_grid/pd_grid/model.py | 2 +- .../sugarscape_g1mt/sugarscape_g1mt/model.py | 8 ++------ .../sugarscape_g1mt/resource_agents.py | 4 ++-- .../sugarscape_g1mt/trader_agents.py | 3 +-- .../advanced/wolf_sheep/wolf_sheep/agents.py | 16 ++++++++-------- examples/advanced/wolf_sheep/wolf_sheep/model.py | 8 ++++---- .../wolf_sheep/wolf_sheep/random_walk.py | 4 ++-- 10 files changed, 27 insertions(+), 40 deletions(-) diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py index ea108faa698..b746a5a4aa7 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py @@ -9,7 +9,6 @@ class Citizen(mesa.Agent): Summary of rule: If grievance - risk > threshold, rebel. Attributes: - unique_id: unique int x, y: Grid coordinates hardship: Agent's 'perceived hardship (i.e., physical or economic privation).' Exogenous, drawn from U(0,1). @@ -30,7 +29,6 @@ class Citizen(mesa.Agent): def __init__( self, - unique_id, model, pos, hardship, @@ -42,7 +40,6 @@ def __init__( """ Create a new Citizen. Args: - unique_id: unique int x, y: Grid coordinates hardship: Agent's 'perceived hardship (i.e., physical or economic privation).' Exogenous, drawn from U(0,1). @@ -55,7 +52,7 @@ def __init__( agent can inspect. Exogenous. model: model instance """ - super().__init__(unique_id, model) + super().__init__(model) self.breed = "citizen" self.pos = pos self.hardship = hardship @@ -129,17 +126,16 @@ class Cop(mesa.Agent): able to inspect """ - def __init__(self, unique_id, model, pos, vision): + def __init__(self, model, pos, vision): """ Create a new Cop. Args: - unique_id: unique int x, y: Grid coordinates vision: number of cells in each direction (N, S, E and W) that agent can inspect. Exogenous. model: model instance """ - super().__init__(unique_id, model) + super().__init__(model) self.breed = "cop" self.pos = pos self.vision = vision diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py index 70b3a841a91..75b7791cfad 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py @@ -78,18 +78,16 @@ def __init__( self.datacollector = mesa.DataCollector( model_reporters=model_reporters, agent_reporters=agent_reporters ) - unique_id = 0 if self.cop_density + self.citizen_density > 1: raise ValueError("Cop density + citizen density must be less than 1") + for contents, (x, y) in self.grid.coord_iter(): if self.random.random() < self.cop_density: - cop = Cop(unique_id, self, (x, y), vision=self.cop_vision) - unique_id += 1 + cop = Cop(self, (x, y), vision=self.cop_vision) self.grid[x][y] = cop elif self.random.random() < (self.cop_density + self.citizen_density): citizen = Citizen( - unique_id, self, (x, y), hardship=self.random.random(), @@ -98,7 +96,6 @@ def __init__( threshold=self.active_threshold, vision=self.citizen_vision, ) - unique_id += 1 self.grid[x][y] = citizen self.running = True diff --git a/examples/advanced/pd_grid/pd_grid/agent.py b/examples/advanced/pd_grid/pd_grid/agent.py index d658ddc846e..40e6ca527f0 100644 --- a/examples/advanced/pd_grid/pd_grid/agent.py +++ b/examples/advanced/pd_grid/pd_grid/agent.py @@ -4,17 +4,16 @@ class PDAgent(mesa.Agent): """Agent member of the iterated, spatial prisoner's dilemma model.""" - def __init__(self, unique_id, model, starting_move=None): + def __init__(self, model, starting_move=None): """ Create a new Prisoner's Dilemma agent. Args: - unique_id: Unique identifier for the agent. model: model instance starting_move: If provided, determines the agent's initial state: C(ooperating) or D(efecting). Otherwise, random. """ - super().__init__(unique_id, model) + super().__init__(model) self.score = 0 if starting_move: self.move = starting_move diff --git a/examples/advanced/pd_grid/pd_grid/model.py b/examples/advanced/pd_grid/pd_grid/model.py index 448e47450bf..cf3512ad036 100644 --- a/examples/advanced/pd_grid/pd_grid/model.py +++ b/examples/advanced/pd_grid/pd_grid/model.py @@ -37,7 +37,7 @@ def __init__( # Create agents for x in range(width): for y in range(height): - agent = PDAgent(self.next_id(), self) + agent = PDAgent(self) self.grid.place_agent(agent, (x, y)) self.schedule.add(agent) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index 06a5032b23d..91bb001d9ad 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -88,15 +88,13 @@ def __init__( sugar_distribution = np.genfromtxt(Path(__file__).parent / "sugar-map.txt") spice_distribution = np.flip(sugar_distribution, 1) - agent_id = 0 for _, (x, y) in self.grid.coord_iter(): max_sugar = sugar_distribution[x, y] max_spice = spice_distribution[x, y] - resource = Resource(agent_id, self, max_sugar, max_spice) + resource = Resource(self, max_sugar, max_spice) self.grid.place_agent(resource, (x, y)) - agent_id += 1 - for i in range(self.initial_population): + for _ in range(self.initial_population): # get agent position x = self.random.randrange(self.width) y = self.random.randrange(self.height) @@ -115,7 +113,6 @@ def __init__( vision = int(self.random.uniform(self.vision_min, self.vision_max + 1)) # create Trader object trader = Trader( - agent_id, self, moore=False, sugar=sugar, @@ -126,7 +123,6 @@ def __init__( ) # place agent self.grid.place_agent(trader, (x, y)) - agent_id += 1 def step(self): """ diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py index 2be75de7af5..042f167214f 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py @@ -9,8 +9,8 @@ class Resource(mesa.Agent): - grows 1 amount of spice at each turn """ - def __init__(self, unique_id, model, max_sugar, max_spice): - super().__init__(unique_id, model) + def __init__(self, model, max_sugar, max_spice): + super().__init__(model) self.sugar_amount = max_sugar self.max_sugar = max_sugar self.spice_amount = max_spice diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py index 18c8c6269de..2c63a8a19c8 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py @@ -29,7 +29,6 @@ class Trader(mesa.Agent): def __init__( self, - unique_id, model, moore=False, sugar=0, @@ -38,7 +37,7 @@ def __init__( metabolism_spice=0, vision=0, ): - super().__init__(unique_id, model) + super().__init__(model) self.moore = moore self.sugar = sugar self.spice = spice diff --git a/examples/advanced/wolf_sheep/wolf_sheep/agents.py b/examples/advanced/wolf_sheep/wolf_sheep/agents.py index 688f25096f4..c0b06f3a509 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/agents.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/agents.py @@ -12,8 +12,8 @@ class Sheep(RandomWalker): energy = None - def __init__(self, unique_id, model, moore, energy=None): - super().__init__(unique_id, model, moore=moore) + def __init__(self, model, moore, energy=None): + super().__init__(model, moore=moore) self.energy = energy def step(self): @@ -44,7 +44,7 @@ def step(self): # Create a new sheep: if self.model.grass: self.energy /= 2 - lamb = Sheep(self.model.next_id(), self.model, self.moore, self.energy) + lamb = Sheep(self.model, self.moore, self.energy) self.model.grid.place_agent(lamb, self.pos) @@ -55,8 +55,8 @@ class Wolf(RandomWalker): energy = None - def __init__(self, unique_id, model, moore, energy=None): - super().__init__(unique_id, model, moore=moore) + def __init__(self, model, moore, energy=None): + super().__init__(model, moore=moore) self.energy = energy def step(self): @@ -83,7 +83,7 @@ def step(self): if self.random.random() < self.model.wolf_reproduce: # Create a new wolf cub self.energy /= 2 - cub = Wolf(self.model.next_id(), self.model, self.moore, self.energy) + cub = Wolf(self.model, self.moore, self.energy) self.model.grid.place_agent(cub, self.pos) @@ -92,7 +92,7 @@ class GrassPatch(mesa.Agent): A patch of grass that grows at a fixed rate and it is eaten by sheep """ - def __init__(self, unique_id, model, fully_grown, countdown): + def __init__(self, model, fully_grown, countdown): """ Creates a new patch of grass @@ -100,7 +100,7 @@ def __init__(self, unique_id, model, fully_grown, countdown): 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__(unique_id, model) + super().__init__(model) self.fully_grown = fully_grown self.countdown = countdown diff --git a/examples/advanced/wolf_sheep/wolf_sheep/model.py b/examples/advanced/wolf_sheep/wolf_sheep/model.py index 1dc1e1a53e7..85b60b73104 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/model.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/model.py @@ -95,15 +95,15 @@ def __init__( x = self.random.randrange(self.width) y = self.random.randrange(self.height) energy = self.random.randrange(2 * self.sheep_gain_from_food) - sheep = Sheep(self.next_id(), self, True, energy) + sheep = Sheep(self, True, energy) self.grid.place_agent(sheep, (x, y)) # Create wolves - for i in range(self.initial_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 = Wolf(self.next_id(), self, True, energy) + wolf = Wolf(self, True, energy) self.grid.place_agent(wolf, (x, y)) # Create grass patches @@ -116,7 +116,7 @@ def __init__( else: countdown = self.random.randrange(self.grass_regrowth_time) - patch = GrassPatch(self.next_id(), self, fully_grown, countdown) + patch = GrassPatch(self, fully_grown, countdown) self.grid.place_agent(patch, (x, y)) self.running = True diff --git a/examples/advanced/wolf_sheep/wolf_sheep/random_walk.py b/examples/advanced/wolf_sheep/wolf_sheep/random_walk.py index 920ae08ca1d..a204f9cc414 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/random_walk.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/random_walk.py @@ -18,7 +18,7 @@ class RandomWalker(mesa.Agent): y = None moore = True - def __init__(self, unique_id, model, moore=True): + def __init__(self, model, moore=True): """ grid: The MultiGrid object in which the agent lives. x: The agent's current x coordinate @@ -26,7 +26,7 @@ def __init__(self, unique_id, model, moore=True): moore: If True, may move in all 8 directions. Otherwise, only up, down, left, right. """ - super().__init__(unique_id, model) + super().__init__(model) self.moore = moore def random_move(self): From 26624c030ef8663df2f34f6fcb28578dded7158b Mon Sep 17 00:00:00 2001 From: Corvince <13568919+Corvince@users.noreply.github.com> Date: Tue, 17 Sep 2024 08:59:28 +0200 Subject: [PATCH 105/116] update examples to use new SolaraViz API (#193) --- examples/basic/boid_flockers/app.py | 9 +++++---- examples/basic/virus_on_network/app.py | 17 ++++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/examples/basic/boid_flockers/app.py b/examples/basic/boid_flockers/app.py index c8dd76bcd2c..205cb21858c 100644 --- a/examples/basic/boid_flockers/app.py +++ b/examples/basic/boid_flockers/app.py @@ -1,5 +1,5 @@ from boid_flockers.model import BoidFlockers -from mesa.visualization import SolaraViz +from mesa.visualization import SolaraViz, make_space_matplotlib def boid_draw(agent): @@ -15,11 +15,12 @@ def boid_draw(agent): "separation": 2, } +model = BoidFlockers(100, 100, 100, 5, 10, 2) + page = SolaraViz( - model_class=BoidFlockers, + model, + [make_space_matplotlib(agent_portrayal=boid_draw)], model_params=model_params, - measures=[], name="BoidFlockers", - agent_portrayal=boid_draw, ) page # noqa diff --git a/examples/basic/virus_on_network/app.py b/examples/basic/virus_on_network/app.py index 47ab294fd4e..caa1360f880 100644 --- a/examples/basic/virus_on_network/app.py +++ b/examples/basic/virus_on_network/app.py @@ -3,7 +3,7 @@ import solara from matplotlib.figure import Figure from matplotlib.ticker import MaxNLocator -from mesa.visualization import SolaraViz, make_text +from mesa.visualization import SolaraViz, make_space_matplotlib from virus_on_network.model import State, VirusOnNetwork, number_infected @@ -57,7 +57,7 @@ def make_plot(model): fig.legend() # Set integer x axis ax.xaxis.set_major_locator(MaxNLocator(integer=True)) - solara.FigureMatplotlib(fig) + return solara.FigureMatplotlib(fig) model_params = { @@ -119,14 +119,17 @@ def make_plot(model): }, } +SpacePlot = make_space_matplotlib(agent_portrayal) + +model1 = VirusOnNetwork() + page = SolaraViz( - VirusOnNetwork, - model_params, - measures=[ + model1, + [ + SpacePlot, make_plot, - make_text(get_resistant_susceptible_ratio), + get_resistant_susceptible_ratio, ], name="Virus Model", - agent_portrayal=agent_portrayal, ) page # noqa From 52f4c3df137b3a3413e7a79817b3a47bf2c882f2 Mon Sep 17 00:00:00 2001 From: Corvince <13568919+Corvince@users.noreply.github.com> Date: Tue, 17 Sep 2024 08:59:28 +0200 Subject: [PATCH 106/116] update examples to use new SolaraViz API (#193) --- examples/advanced/sugarscape_g1mt/app.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/app.py b/examples/advanced/sugarscape_g1mt/app.py index 9f01d8aaaf5..146d3d5c51f 100644 --- a/examples/advanced/sugarscape_g1mt/app.py +++ b/examples/advanced/sugarscape_g1mt/app.py @@ -1,12 +1,12 @@ import numpy as np import solara from matplotlib.figure import Figure -from mesa.visualization import SolaraViz +from mesa.visualization import SolaraViz, make_plot_measure from sugarscape_g1mt.model import SugarscapeG1mt from sugarscape_g1mt.trader_agents import Trader -def space_drawer(model, agent_portrayal): +def SpaceDrawer(model): def portray(g): layers = { "sugar": [[np.nan for j in range(g.height)] for i in range(g.width)], @@ -42,7 +42,7 @@ def portray(g): # Trader ax.scatter(**out["trader"]) ax.set_axis_off() - solara.FigureMatplotlib(fig) + return solara.FigureMatplotlib(fig) model_params = { @@ -50,12 +50,12 @@ def portray(g): "height": 50, } +model1 = SugarscapeG1mt(50, 50) + page = SolaraViz( - SugarscapeG1mt, - model_params, - measures=["Trader", "Price"], + model1, + components=[SpaceDrawer, make_plot_measure(["Trader", "Price"])], name="Sugarscape {G1, M, T}", - space_drawer=space_drawer, play_interval=1500, ) page # noqa From 4e76effa2ab28a52c07164e535787e3ec3132386 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 21 Sep 2024 10:43:22 +0200 Subject: [PATCH 107/116] Use performance optimized shuffle_do() method (#201) Replace shuffle().do() in 18 models with the performance optimized shuffle_do() method. --- examples/basic/boid_flockers/boid_flockers/model.py | 2 +- .../boltzmann_wealth_model/boltzmann_wealth_model/model.py | 2 +- examples/basic/schelling/model.py | 2 +- examples/basic/virus_on_network/virus_on_network/model.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/basic/boid_flockers/boid_flockers/model.py b/examples/basic/boid_flockers/boid_flockers/model.py index 312986bd207..ae3099f3549 100644 --- a/examples/basic/boid_flockers/boid_flockers/model.py +++ b/examples/basic/boid_flockers/boid_flockers/model.py @@ -143,4 +143,4 @@ def make_agents(self): self.space.place_agent(boid, pos) def step(self): - self.agents.shuffle().do("step") + self.agents.shuffle_do("step") diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py index 5c6d1f88d7e..ac091a6ce34 100644 --- a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -38,7 +38,7 @@ def __init__(self, N=100, width=10, height=10): self.datacollector.collect(self) def step(self): - self.agents.shuffle().do("step") + self.agents.shuffle_do("step") # collect data self.datacollector.collect(self) diff --git a/examples/basic/schelling/model.py b/examples/basic/schelling/model.py index aec6d5b504e..e995f31ec21 100644 --- a/examples/basic/schelling/model.py +++ b/examples/basic/schelling/model.py @@ -91,7 +91,7 @@ def step(self): Run one step of the model. """ self.happy = 0 # Reset counter of happy agents - self.agents.shuffle().do("step") + self.agents.shuffle_do("step") self.datacollector.collect(self) diff --git a/examples/basic/virus_on_network/virus_on_network/model.py b/examples/basic/virus_on_network/virus_on_network/model.py index 2df667bcac6..d892a0c4c06 100644 --- a/examples/basic/virus_on_network/virus_on_network/model.py +++ b/examples/basic/virus_on_network/virus_on_network/model.py @@ -95,7 +95,7 @@ def resistant_susceptible_ratio(self): return math.inf def step(self): - self.agents.shuffle().do("step") + self.agents.shuffle_do("step") # collect data self.datacollector.collect(self) From 0f4b9392bd2ffc2cd211f8f34f5c61b0e7f1e031 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 21 Sep 2024 10:43:22 +0200 Subject: [PATCH 108/116] Use performance optimized shuffle_do() method (#201) Replace shuffle().do() in 18 models with the performance optimized shuffle_do() method. --- .../epstein_civil_violence/epstein_civil_violence/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py index 75b7791cfad..b278f23e18d 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py @@ -105,7 +105,7 @@ def step(self): """ Advance the model by one step and collect data. """ - self.agents.shuffle().do("step") + self.agents.shuffle_do("step") # collect data self.datacollector.collect(self) self.iteration += 1 From eae3f0bfdddf93d529a0a06eecbc48cb1cdcc78d Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 21 Sep 2024 15:58:25 +0200 Subject: [PATCH 109/116] Replace the remaining schedulers with AgentSet functionality (#202) This PR completes the migration from schedulers to AgentSet functionality across the mesa-examples repository for all regular (non-`gis`/-`rl`) examples. Key changes include: - Replaced `RandomActivation`, `SimultaneousActivation`, and `RandomActivationByType` schedulers with appropriate AgentSet methods - Updated `Model.step()` implementations to use AgentSet activation - Removed references to `schedule.steps`, `schedule.agents`, and `schedule.agents_by_type` - Updated agent addition/removal logic to work with AgentSets - Adjusted data collection and visualization code to use `Model.steps` and `Model.agents` For more details on migrating from schedulers to AgentSets, see the migration guide: https://mesa.readthedocs.io/en/latest/migration_guide.html#time-and-schedulers --- examples/basic/schelling/analysis.ipynb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/basic/schelling/analysis.ipynb b/examples/basic/schelling/analysis.ipynb index 50f382c66a0..71d925c1802 100644 --- a/examples/basic/schelling/analysis.ipynb +++ b/examples/basic/schelling/analysis.ipynb @@ -65,9 +65,9 @@ } ], "source": [ - "while model.running and model.schedule.steps < 100:\n", + "while model.running and model.steps < 100:\n", " model.step()\n", - "print(model.schedule.steps) # Show how many steps have actually run" + "print(model.steps) # Show how many steps have actually run" ] }, { @@ -328,7 +328,7 @@ " Find the % of agents that only have neighbors of their same type.\n", " \"\"\"\n", " segregated_agents = 0\n", - " for agent in model.schedule.agents:\n", + " for agent in model.agents:\n", " segregated = True\n", " for neighbor in model.grid.iter_neighbors(agent.pos, True):\n", " if neighbor.type != agent.type:\n", @@ -336,7 +336,7 @@ " break\n", " if segregated:\n", " segregated_agents += 1\n", - " return segregated_agents / model.schedule.get_agent_count()" + " return segregated_agents / len(model.agents)" ] }, { From e23b804201f5ae9719d4e16fd6da89a0be0afdae Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 21 Sep 2024 15:58:25 +0200 Subject: [PATCH 110/116] Replace the remaining schedulers with AgentSet functionality (#202) This PR completes the migration from schedulers to AgentSet functionality across the mesa-examples repository for all regular (non-`gis`/-`rl`) examples. Key changes include: - Replaced `RandomActivation`, `SimultaneousActivation`, and `RandomActivationByType` schedulers with appropriate AgentSet methods - Updated `Model.step()` implementations to use AgentSet activation - Removed references to `schedule.steps`, `schedule.agents`, and `schedule.agents_by_type` - Updated agent addition/removal logic to work with AgentSets - Adjusted data collection and visualization code to use `Model.steps` and `Model.agents` For more details on migrating from schedulers to AgentSets, see the migration guide: https://mesa.readthedocs.io/en/latest/migration_guide.html#time-and-schedulers --- examples/advanced/pd_grid/analysis.ipynb | 2 +- examples/advanced/pd_grid/pd_grid/agent.py | 4 +-- examples/advanced/pd_grid/pd_grid/model.py | 31 +++++++++++-------- examples/advanced/pd_grid/pd_grid/server.py | 6 ++-- examples/advanced/pd_grid/readme.md | 2 +- examples/advanced/sugarscape_g1mt/tests.py | 4 +-- .../wolf_sheep/wolf_sheep/test_random_walk.py | 5 +-- 7 files changed, 28 insertions(+), 26 deletions(-) diff --git a/examples/advanced/pd_grid/analysis.ipynb b/examples/advanced/pd_grid/analysis.ipynb index 1fe69759743..e3f52170a1c 100644 --- a/examples/advanced/pd_grid/analysis.ipynb +++ b/examples/advanced/pd_grid/analysis.ipynb @@ -72,7 +72,7 @@ " grid[y][x] = 0\n", " ax.pcolormesh(grid, cmap=bwr, vmin=0, vmax=1)\n", " ax.axis(\"off\")\n", - " ax.set_title(f\"Steps: {model.schedule.steps}\")" + " ax.set_title(f\"Steps: {model.steps}\")" ] }, { diff --git a/examples/advanced/pd_grid/pd_grid/agent.py b/examples/advanced/pd_grid/pd_grid/agent.py index 40e6ca527f0..85121327826 100644 --- a/examples/advanced/pd_grid/pd_grid/agent.py +++ b/examples/advanced/pd_grid/pd_grid/agent.py @@ -33,7 +33,7 @@ def step(self): best_neighbor = max(neighbors, key=lambda a: a.score) self.next_move = best_neighbor.move - if self.model.schedule_type != "Simultaneous": + if self.model.activation_order != "Simultaneous": self.advance() def advance(self): @@ -42,7 +42,7 @@ def advance(self): def increment_score(self): neighbors = self.model.grid.get_neighbors(self.pos, True) - if self.model.schedule_type == "Simultaneous": + if self.model.activation_order == "Simultaneous": moves = [neighbor.next_move for neighbor in neighbors] else: moves = [neighbor.move for neighbor in neighbors] diff --git a/examples/advanced/pd_grid/pd_grid/model.py b/examples/advanced/pd_grid/pd_grid/model.py index cf3512ad036..b1c2a05c257 100644 --- a/examples/advanced/pd_grid/pd_grid/model.py +++ b/examples/advanced/pd_grid/pd_grid/model.py @@ -6,11 +6,7 @@ class PdGrid(mesa.Model): """Model class for iterated, spatial prisoner's dilemma model.""" - schedule_types = { - "Sequential": mesa.time.BaseScheduler, - "Random": mesa.time.RandomActivation, - "Simultaneous": mesa.time.SimultaneousActivation, - } + activation_regimes = ["Sequential", "Random", "Simultaneous"] # This dictionary holds the payoff for this agent, # keyed on: (my_move, other_move) @@ -18,33 +14,31 @@ class PdGrid(mesa.Model): payoff = {("C", "C"): 1, ("C", "D"): 0, ("D", "C"): 1.6, ("D", "D"): 0} def __init__( - self, width=50, height=50, schedule_type="Random", payoffs=None, seed=None + self, width=50, height=50, activation_order="Random", payoffs=None, seed=None ): """ Create a new Spatial Prisoners' Dilemma Model. Args: width, height: Grid size. There will be one agent per grid cell. - schedule_type: Can be "Sequential", "Random", or "Simultaneous". + activation_order: Can be "Sequential", "Random", or "Simultaneous". Determines the agent activation regime. payoffs: (optional) Dictionary of (move, neighbor_move) payoffs. """ super().__init__() + self.activation_order = activation_order self.grid = mesa.space.SingleGrid(width, height, torus=True) - self.schedule_type = schedule_type - self.schedule = self.schedule_types[self.schedule_type](self) # Create agents for x in range(width): for y in range(height): agent = PDAgent(self) self.grid.place_agent(agent, (x, y)) - self.schedule.add(agent) self.datacollector = mesa.DataCollector( { "Cooperating_Agents": lambda m: len( - [a for a in m.schedule.agents if a.move == "C"] + [a for a in m.agents if a.move == "C"] ) } ) @@ -53,8 +47,19 @@ def __init__( self.datacollector.collect(self) def step(self): - self.schedule.step() - # collect data + # Activate all agents, based on the activation regime + match self.activation_order: + case "Sequential": + self.agents.do("step") + case "Random": + self.agents.shuffle_do("step") + case "Simultaneous": + self.agents.do("step") + self.agents.do("advance") + case _: + raise ValueError(f"Unknown activation order: {self.activation_order}") + + # Collect data self.datacollector.collect(self) def run(self, n): diff --git a/examples/advanced/pd_grid/pd_grid/server.py b/examples/advanced/pd_grid/pd_grid/server.py index f2447da37c1..57785acccac 100644 --- a/examples/advanced/pd_grid/pd_grid/server.py +++ b/examples/advanced/pd_grid/pd_grid/server.py @@ -9,10 +9,10 @@ model_params = { "height": 50, "width": 50, - "schedule_type": mesa.visualization.Choice( - "Scheduler type", + "activation_order": mesa.visualization.Choice( + "Activation regime", value="Random", - choices=list(PdGrid.schedule_types.keys()), + choices=PdGrid.activation_regimes, ), } diff --git a/examples/advanced/pd_grid/readme.md b/examples/advanced/pd_grid/readme.md index 8b4bc40c88f..51b91fd4287 100644 --- a/examples/advanced/pd_grid/readme.md +++ b/examples/advanced/pd_grid/readme.md @@ -28,7 +28,7 @@ Launch the ``Demographic Prisoner's Dilemma Activation Schedule.ipynb`` notebook ## Files * ``run.py`` is the entry point for the font-end simulations. -* ``pd_grid/``: contains the model and agent classes; the model takes a ``schedule_type`` string as an argument, which determines what schedule type the model uses: Sequential, Random or Simultaneous. +* ``pd_grid/``: contains the model and agent classes; the model takes a ``activation_order`` string as an argument, which determines in which order agents are activated: Sequential, Random or Simultaneous. * ``Demographic Prisoner's Dilemma Activation Schedule.ipynb``: Jupyter Notebook for running the scheduling experiment. This runs the model three times, one for each activation type, and demonstrates how the activation regime drives the model to different outcomes. ## Further Reading diff --git a/examples/advanced/sugarscape_g1mt/tests.py b/examples/advanced/sugarscape_g1mt/tests.py index bcfcf73931d..274afa6bb89 100644 --- a/examples/advanced/sugarscape_g1mt/tests.py +++ b/examples/advanced/sugarscape_g1mt/tests.py @@ -23,7 +23,7 @@ def test_decreasing_price_variance(): model.datacollector._new_model_reporter( "price_variance", lambda m: np.var( - flatten([a.prices for a in m.schedule.agents_by_type[Trader].values()]) + flatten([a.prices for a in m.agents_by_type[Trader].values()]) ), ) model.run_model(step_count=50) @@ -40,7 +40,7 @@ def calculate_carrying_capacities(enable_trade): for vision_max in visions: model = SugarscapeG1mt(vision_max=vision_max, enable_trade=enable_trade) model.run_model(step_count=50) - carrying_capacities.append(len(model.schedule.agents_by_type[Trader])) + carrying_capacities.append(len(model.agents_by_type[Trader])) return carrying_capacities # Carrying capacity should increase over mean vision (figure IV-6). diff --git a/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py b/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py index d2340fedba3..393a46b18c4 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py @@ -5,7 +5,6 @@ from mesa import Model from mesa.space import MultiGrid -from mesa.time import RandomActivation from mesa.visualization.TextVisualization import TextGrid, TextVisualization from wolf_sheep.random_walk import RandomWalker @@ -40,17 +39,15 @@ def __init__(self, width, height, agent_count): self.grid = MultiGrid(self.width, self.height, torus=True) self.agent_count = agent_count - self.schedule = RandomActivation(self) # Create agents for i in range(self.agent_count): x = self.random.randrange(self.width) y = self.random.randrange(self.height) a = WalkerAgent(i, (x, y), self, True) - self.schedule.add(a) self.grid.place_agent(a, (x, y)) def step(self): - self.schedule.step() + self.agents.shuffle_do("step") class WalkerWorldViz(TextVisualization): From 8000505f9f5d05e27e098dfc250375c3f65b0d02 Mon Sep 17 00:00:00 2001 From: Wang Boyu Date: Fri, 11 Oct 2024 13:18:16 -0400 Subject: [PATCH 111/116] merge experimental schelling into schelling example --- examples/basic/schelling/README.md | 17 ++----- examples/basic/schelling/app.py | 43 ++++++++++++++++ examples/basic/schelling/requirements.txt | 2 +- examples/basic/schelling/run.py | 3 -- examples/basic/schelling/server.py | 60 ----------------------- 5 files changed, 48 insertions(+), 77 deletions(-) create mode 100644 examples/basic/schelling/app.py delete mode 100644 examples/basic/schelling/run.py delete mode 100644 examples/basic/schelling/server.py diff --git a/examples/basic/schelling/README.md b/examples/basic/schelling/README.md index fe8971f6fd1..b0116b55e76 100644 --- a/examples/basic/schelling/README.md +++ b/examples/basic/schelling/README.md @@ -16,21 +16,13 @@ To install the dependencies use pip and the requirements.txt in this directory. ## How to Run -To run the model interactively, run ``mesa runserver`` in this directory. e.g. +To run the model interactively, in this directory, run the following command ``` - $ mesa runserver + $ solara run app.py ``` -or - -Directly run the file ``run.py`` in the terminal. e.g. - -``` - $ python run.py -``` - -Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run. +Then open your browser to [http://127.0.0.1:8765/](http://127.0.0.1:8765/) and click the Play button. To view and run some example model analyses, launch the IPython Notebook and open ``analysis.ipynb``. Visualizing the analysis also requires [matplotlib](http://matplotlib.org/). @@ -40,10 +32,9 @@ To run the model with the grid displayed as an ASCII text, run `python run_ascii ## Files -* ``run.py``: Launches a model visualization server. +* ``app.py``: Code for the interactive visualization. * ``run_ascii.py``: Run the model in text mode. * ``schelling.py``: Contains the agent class, and the overall model class. -* ``server.py``: Defines classes for visualizing the model in the browser via Mesa's modular server, and instantiates a visualization server. * ``analysis.ipynb``: Notebook demonstrating how to run experiments and parameter sweeps on the model. ## Further Reading diff --git a/examples/basic/schelling/app.py b/examples/basic/schelling/app.py new file mode 100644 index 00000000000..fb837351d16 --- /dev/null +++ b/examples/basic/schelling/app.py @@ -0,0 +1,43 @@ +import solara +from mesa.visualization import ( + Slider, + SolaraViz, + make_plot_measure, + make_space_matplotlib, +) +from model import Schelling + + +def get_happy_agents(model): + """ + Display a text count of how many happy agents there are. + """ + return solara.Markdown(f"**Happy agents: {model.happy}**") + + +def agent_portrayal(agent): + return {"color": "tab:orange" if agent.type == 0 else "tab:blue"} + + +model_params = { + "density": Slider("Agent density", 0.8, 0.1, 1.0, 0.1), + "minority_pc": Slider("Fraction minority", 0.2, 0.0, 1.0, 0.05), + "homophily": Slider("Homophily", 3, 0, 8, 1), + "width": 20, + "height": 20, +} + +model1 = Schelling(20, 20, 0.8, 0.2, 3) + +HappyPlot = make_plot_measure("happy") + +page = SolaraViz( + model1, + components=[ + make_space_matplotlib(agent_portrayal), + make_plot_measure("happy"), + get_happy_agents, + ], + model_params=model_params, +) +page # noqa diff --git a/examples/basic/schelling/requirements.txt b/examples/basic/schelling/requirements.txt index da2b9972efd..79bc35553ee 100644 --- a/examples/basic/schelling/requirements.txt +++ b/examples/basic/schelling/requirements.txt @@ -1,3 +1,3 @@ jupyter matplotlib -mesa~=2.0 +mesa[viz]>=3.0.0b0 diff --git a/examples/basic/schelling/run.py b/examples/basic/schelling/run.py deleted file mode 100644 index f20cebcbd5f..00000000000 --- a/examples/basic/schelling/run.py +++ /dev/null @@ -1,3 +0,0 @@ -from server import server - -server.launch(open_browser=True) diff --git a/examples/basic/schelling/server.py b/examples/basic/schelling/server.py deleted file mode 100644 index 1f0d5f92408..00000000000 --- a/examples/basic/schelling/server.py +++ /dev/null @@ -1,60 +0,0 @@ -import mesa -from model import Schelling - - -def get_happy_agents(model): - """ - Display a text count of how many happy agents there are. - """ - return f"Happy agents: {model.happy}" - - -def schelling_draw(agent): - """ - Portrayal Method for canvas - """ - if agent is None: - return - portrayal = {"Shape": "circle", "r": 0.5, "Filled": "true", "Layer": 0} - - if agent.type == 0: - portrayal["Color"] = ["#FF0000", "#FF9999"] - portrayal["stroke_color"] = "#00FF00" - else: - portrayal["Color"] = ["#0000FF", "#9999FF"] - portrayal["stroke_color"] = "#000000" - return portrayal - - -canvas_element = mesa.visualization.CanvasGrid( - portrayal_method=schelling_draw, - grid_width=20, - grid_height=20, - canvas_width=500, - canvas_height=500, -) -happy_chart = mesa.visualization.ChartModule([{"Label": "happy", "Color": "Black"}]) - -model_params = { - "height": 20, - "width": 20, - "density": mesa.visualization.Slider( - name="Agent density", value=0.8, min_value=0.1, max_value=1.0, step=0.1 - ), - "minority_pc": mesa.visualization.Slider( - name="Fraction minority", value=0.2, min_value=0.00, max_value=1.0, step=0.05 - ), - "homophily": mesa.visualization.Slider( - name="Homophily", value=3, min_value=0, max_value=8, step=1 - ), - "radius": mesa.visualization.Slider( - name="Search Radius", value=1, min_value=1, max_value=5, step=1 - ), -} - -server = mesa.visualization.ModularServer( - model_cls=Schelling, - visualization_elements=[canvas_element, get_happy_agents, happy_chart], - name="Schelling Segregation Model", - model_params=model_params, -) From 0a77f58bf187629a4c67c778a1106e79366a4132 Mon Sep 17 00:00:00 2001 From: Wang Boyu Date: Sat, 12 Oct 2024 09:14:42 -0400 Subject: [PATCH 112/116] merge experimental boltzmann wealth into boltzmann wealth example --- .../basic/boltzmann_wealth_model/Readme.md | 31 ++-- examples/basic/boltzmann_wealth_model/app.py | 156 ++++++------------ .../boltzmann_wealth_model/__init__.py | 0 .../boltzmann_wealth_model/server.py | 40 ----- .../{boltzmann_wealth_model => }/model.py | 0 .../boltzmann_wealth_model/requirements.txt | 2 +- examples/basic/boltzmann_wealth_model/run.py | 3 - .../basic/boltzmann_wealth_model/st_app.py | 113 +++++++++++++ 8 files changed, 188 insertions(+), 157 deletions(-) delete mode 100644 examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py delete mode 100644 examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/server.py rename examples/basic/boltzmann_wealth_model/{boltzmann_wealth_model => }/model.py (100%) delete mode 100644 examples/basic/boltzmann_wealth_model/run.py create mode 100644 examples/basic/boltzmann_wealth_model/st_app.py diff --git a/examples/basic/boltzmann_wealth_model/Readme.md b/examples/basic/boltzmann_wealth_model/Readme.md index 4a6e21f142a..8f7f7c817c2 100644 --- a/examples/basic/boltzmann_wealth_model/Readme.md +++ b/examples/basic/boltzmann_wealth_model/Readme.md @@ -12,32 +12,41 @@ As the model runs, the distribution of wealth among agents goes from being perfe To follow the tutorial example, launch the Jupyter Notebook and run the code in ``Introduction to Mesa Tutorial Code.ipynb`` which you can find in the main mesa repo [here](https://github.com/projectmesa/mesa/blob/main/docs/tutorials/intro_tutorial.ipynb) -To launch the interactive server, as described in the [last section of the tutorial](https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html#adding-visualization), run: +Make sure to install the requirements first: ``` - $ python server.py + $ pip install -r requirements.txt ``` -Make sure to install the requirements first: +To launch the interactive server, as described in the [last section of the tutorial](https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html#adding-visualization), run: ``` - pip install -r requirements.txt + $ solara run app.py ``` -If your browser doesn't open automatically, point it to [http://127.0.0.1:8521/](http://127.0.0.1:8521/). When the visualization loads, press Reset, then Run. +If your browser doesn't open automatically, point it to [http://127.0.0.1:8765/](http://127.0.0.1:8765/). When the visualization loads, click on the Play button. ## Files -* ``boltzmann_wealth_model/model.py``: Final version of the model. -* ``boltzmann_wealth_model/server.py``: Code for the interactive visualization. -* ``run.py``: Launches the server. +* ``model.py``: Final version of the model. +* ``app.py``: Code for the interactive visualization. ## Optional -* ``boltzmann_wealth_model/app.py``: can be used to run the simulation via the streamlit interface. -* For this some additional packages like ``streamlit`` and ``altair`` needs to be installed. -* Once installed, the app can be opened in the browser using : ``streamlit run app.py`` +An optional visualization is also provided using Streamlit, which is another popular Python library for creating interactive web applications. + +To run the Streamlit app, you will need to install the `streamlit` and `altair` libraries: + +``` + $ pip install streamlit altair +``` + +Then, you can run the Streamlit app using the following command: + +``` + $ streamlit run st_app.py +``` ## Further Reading diff --git a/examples/basic/boltzmann_wealth_model/app.py b/examples/basic/boltzmann_wealth_model/app.py index 97f0f20c0fd..199b3a1a50e 100644 --- a/examples/basic/boltzmann_wealth_model/app.py +++ b/examples/basic/boltzmann_wealth_model/app.py @@ -1,113 +1,65 @@ -import time - -import altair as alt -import pandas as pd -import streamlit as st -from boltzmann_wealth_model.model import BoltzmannWealthModel - -model = st.title("Boltzman Wealth Model") -num_agents = st.slider( - "Choose how many agents to include in the model", - min_value=1, - max_value=100, - value=50, +from mesa.visualization import ( + SolaraViz, + make_plot_measure, + make_space_matplotlib, ) -num_ticks = st.slider( - "Select number of Simulation Runs", min_value=1, max_value=100, value=50 -) -height = st.slider("Select Grid Height", min_value=10, max_value=100, step=10, value=15) -width = st.slider("Select Grid Width", min_value=10, max_value=100, step=10, value=20) -model = BoltzmannWealthModel(num_agents, height, width) - +from model import BoltzmannWealthModel -status_text = st.empty() -run = st.button("Run Simulation") +def agent_portrayal(agent): + size = 10 + color = "tab:red" + if agent.wealth > 0: + size = 50 + color = "tab:blue" + return {"size": size, "color": color} -if run: - tick = time.time() - step = 0 - # init grid - df_grid = pd.DataFrame() - df_gini = pd.DataFrame({"step": [0], "gini": [-1]}) - for x in range(width): - for y in range(height): - df_grid = pd.concat( - [df_grid, pd.DataFrame({"x": [x], "y": [y], "agent_count": 0})], - ignore_index=True, - ) - heatmap = ( - alt.Chart(df_grid) - .mark_point(size=100) - .encode(x="x", y="y", color=alt.Color("agent_count")) - .interactive() - .properties(width=800, height=600) - ) +model_params = { + "N": { + "type": "SliderInt", + "value": 50, + "label": "Number of agents:", + "min": 10, + "max": 100, + "step": 1, + }, + "width": 10, + "height": 10, +} - line = ( - alt.Chart(df_gini) - .mark_line(point=True) - .encode(x="step", y="gini") - .properties(width=800, height=600) - ) +# Create initial model instance +model1 = BoltzmannWealthModel(50, 10, 10) - # init progress bar - my_bar = st.progress(0, text="Simulation Progress") # progress - placeholder = st.empty() - st.subheader("Agent Grid") - chart = st.altair_chart(heatmap) - st.subheader("Gini Values") - line_chart = st.altair_chart(line) +# Create visualization elements. The visualization elements are solara components +# that receive the model instance as a "prop" and display it in a certain way. +# Under the hood these are just classes that receive the model instance. +# You can also author your own visualization elements, which can also be functions +# that receive the model instance and return a valid solara component. +SpaceGraph = make_space_matplotlib(agent_portrayal) +GiniPlot = make_plot_measure("Gini") - color_scale = alt.Scale( - domain=[0, 1, 2, 3, 4], range=["red", "cyan", "white", "white", "blue"] - ) - for i in range(num_ticks): - model.step() - my_bar.progress((i / num_ticks), text="Simulation progress") - placeholder.text("Step = %d" % i) - for cell in model.grid.coord_iter(): - cell_content, (x, y) = cell - agent_count = len(cell_content) - selected_row = df_grid[(df_grid["x"] == x) & (df_grid["y"] == y)] - df_grid.loc[selected_row.index, "agent_count"] = ( - agent_count # random.choice([1,2]) - ) - - df_gini = pd.concat( - [ - df_gini, - pd.DataFrame( - {"step": [i], "gini": [model.datacollector.model_vars["Gini"][i]]} - ), - ] - ) - # st.table(df_grid) - heatmap = ( - alt.Chart(df_grid) - .mark_circle(size=100) - .encode(x="x", y="y", color=alt.Color("agent_count", scale=color_scale)) - .interactive() - .properties(width=800, height=600) - ) - chart.altair_chart(heatmap) - - line = ( - alt.Chart(df_gini) - .mark_line(point=True) - .encode(x="step", y="gini") - .properties(width=800, height=600) - ) - line_chart.altair_chart(line) +# Create the SolaraViz page. This will automatically create a server and display the +# visualization elements in a web browser. +# Display it using the following command in the example directory: +# solara run app.py +# It will automatically update and display any changes made to this file +page = SolaraViz( + model1, + components=[SpaceGraph, GiniPlot], + model_params=model_params, + name="Boltzmann Wealth Model", +) +page # noqa - time.sleep(0.01) - tock = time.time() - st.success(f"Simulation completed in {tock - tick:.2f} secs") +# In a notebook environment, we can also display the visualization elements directly +# SpaceGraph(model1) +# GiniPlot(model1) - # st.subheader('Agent Grid') - # fig = px.imshow(agent_counts,labels={'color':'Agent Count'}) - # st.plotly_chart(fig) - # st.subheader('Gini value over sim ticks (Plotly)') - # chart = st.line_chart(model.datacollector.model_vars['Gini']) +# The plots will be static. If you want to pick up model steps, +# you have to make the model reactive first +# reactive_model = solara.reactive(model1) +# SpaceGraph(reactive_model) +# In a different notebook block: +# reactive_model.value.step() diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/server.py b/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/server.py deleted file mode 100644 index a49546ce741..00000000000 --- a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/server.py +++ /dev/null @@ -1,40 +0,0 @@ -import mesa - -from .model import BoltzmannWealthModel - - -def agent_portrayal(agent): - portrayal = {"Shape": "circle", "Filled": "true", "r": 0.5} - - if agent.wealth > 0: - portrayal["Color"] = "red" - portrayal["Layer"] = 0 - else: - portrayal["Color"] = "grey" - portrayal["Layer"] = 1 - portrayal["r"] = 0.2 - return portrayal - - -grid = mesa.visualization.CanvasGrid(agent_portrayal, 10, 10, 500, 500) -chart = mesa.visualization.ChartModule( - [{"Label": "Gini", "Color": "#0000FF"}], data_collector_name="datacollector" -) - -model_params = { - "N": mesa.visualization.Slider( - "Number of agents", - 100, - 2, - 200, - 1, - description="Choose how many agents to include in the model", - ), - "width": 10, - "height": 10, -} - -server = mesa.visualization.ModularServer( - BoltzmannWealthModel, [grid, chart], "Money Model", model_params -) -server.port = 8521 diff --git a/examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/basic/boltzmann_wealth_model/model.py similarity index 100% rename from examples/basic/boltzmann_wealth_model/boltzmann_wealth_model/model.py rename to examples/basic/boltzmann_wealth_model/model.py diff --git a/examples/basic/boltzmann_wealth_model/requirements.txt b/examples/basic/boltzmann_wealth_model/requirements.txt index 25d263f4e84..95044bedf78 100644 --- a/examples/basic/boltzmann_wealth_model/requirements.txt +++ b/examples/basic/boltzmann_wealth_model/requirements.txt @@ -1 +1 @@ -mesa~=2.0 +mesa[viz]>=3.0.0b0 diff --git a/examples/basic/boltzmann_wealth_model/run.py b/examples/basic/boltzmann_wealth_model/run.py deleted file mode 100644 index f17675937cc..00000000000 --- a/examples/basic/boltzmann_wealth_model/run.py +++ /dev/null @@ -1,3 +0,0 @@ -from boltzmann_wealth_model.server import server - -server.launch(open_browser=True) diff --git a/examples/basic/boltzmann_wealth_model/st_app.py b/examples/basic/boltzmann_wealth_model/st_app.py new file mode 100644 index 00000000000..665f8067a6a --- /dev/null +++ b/examples/basic/boltzmann_wealth_model/st_app.py @@ -0,0 +1,113 @@ +import time + +import altair as alt +import pandas as pd +import streamlit as st +from model import BoltzmannWealthModel + +model = st.title("Boltzman Wealth Model") +num_agents = st.slider( + "Choose how many agents to include in the model", + min_value=1, + max_value=100, + value=50, +) +num_ticks = st.slider( + "Select number of Simulation Runs", min_value=1, max_value=100, value=50 +) +height = st.slider("Select Grid Height", min_value=10, max_value=100, step=10, value=15) +width = st.slider("Select Grid Width", min_value=10, max_value=100, step=10, value=20) +model = BoltzmannWealthModel(num_agents, height, width) + + +status_text = st.empty() +run = st.button("Run Simulation") + + +if run: + tick = time.time() + step = 0 + # init grid + df_grid = pd.DataFrame() + df_gini = pd.DataFrame({"step": [0], "gini": [-1]}) + for x in range(width): + for y in range(height): + df_grid = pd.concat( + [df_grid, pd.DataFrame({"x": [x], "y": [y], "agent_count": 0})], + ignore_index=True, + ) + + heatmap = ( + alt.Chart(df_grid) + .mark_point(size=100) + .encode(x="x", y="y", color=alt.Color("agent_count")) + .interactive() + .properties(width=800, height=600) + ) + + line = ( + alt.Chart(df_gini) + .mark_line(point=True) + .encode(x="step", y="gini") + .properties(width=800, height=600) + ) + + # init progress bar + my_bar = st.progress(0, text="Simulation Progress") # progress + placeholder = st.empty() + st.subheader("Agent Grid") + chart = st.altair_chart(heatmap) + st.subheader("Gini Values") + line_chart = st.altair_chart(line) + + color_scale = alt.Scale( + domain=[0, 1, 2, 3, 4], range=["red", "cyan", "white", "white", "blue"] + ) + for i in range(num_ticks): + model.step() + my_bar.progress((i / num_ticks), text="Simulation progress") + placeholder.text("Step = %d" % i) + for cell in model.grid.coord_iter(): + cell_content, (x, y) = cell + agent_count = len(cell_content) + selected_row = df_grid[(df_grid["x"] == x) & (df_grid["y"] == y)] + df_grid.loc[selected_row.index, "agent_count"] = ( + agent_count # random.choice([1,2]) + ) + + df_gini = pd.concat( + [ + df_gini, + pd.DataFrame( + {"step": [i], "gini": [model.datacollector.model_vars["Gini"][i]]} + ), + ] + ) + # st.table(df_grid) + heatmap = ( + alt.Chart(df_grid) + .mark_circle(size=100) + .encode(x="x", y="y", color=alt.Color("agent_count", scale=color_scale)) + .interactive() + .properties(width=800, height=600) + ) + chart.altair_chart(heatmap) + + line = ( + alt.Chart(df_gini) + .mark_line(point=True) + .encode(x="step", y="gini") + .properties(width=800, height=600) + ) + line_chart.altair_chart(line) + + time.sleep(0.01) + + tock = time.time() + st.success(f"Simulation completed in {tock - tick:.2f} secs") + + # st.subheader('Agent Grid') + # fig = px.imshow(agent_counts,labels={'color':'Agent Count'}) + # st.plotly_chart(fig) + # st.subheader('Gini value over sim ticks (Plotly)') + # chart = st.line_chart(model.datacollector.model_vars['Gini']) From 0c5b753cc3d9023bf795791c11628556217a3aad Mon Sep 17 00:00:00 2001 From: rht Date: Mon, 14 Oct 2024 05:59:03 -0400 Subject: [PATCH 113/116] refactor: Simplify Schelling code (#222) * refactor: Simplify Schelling code 1. Remove unused model attributes 2. Make `similar` calculation more natural language readable * Remove unused argument doc * Add type hints to agent class * refactor: Simplify self.running expression --- examples/basic/schelling/model.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/examples/basic/schelling/model.py b/examples/basic/schelling/model.py index e995f31ec21..b7523ef2b6d 100644 --- a/examples/basic/schelling/model.py +++ b/examples/basic/schelling/model.py @@ -6,24 +6,21 @@ class SchellingAgent(mesa.Agent): Schelling segregation agent """ - def __init__(self, model, agent_type): + def __init__(self, model: mesa.Model, agent_type: int) -> None: """ Create a new Schelling agent. Args: - x, y: Agent initial location. agent_type: Indicator for the agent's type (minority=1, majority=0) """ super().__init__(model) self.type = agent_type - def step(self): - similar = 0 - for neighbor in self.model.grid.iter_neighbors( + def step(self) -> None: + neighbors = self.model.grid.iter_neighbors( self.pos, moore=True, radius=self.model.radius - ): - if neighbor.type == self.type: - similar += 1 + ) + similar = sum(1 for neighbor in neighbors if neighbor.type == self.type) # If unhappy, move: if similar < self.model.homophily: @@ -60,10 +57,6 @@ def __init__( """ super().__init__(seed=seed) - self.height = height - self.width = width - self.density = density - self.minority_pc = minority_pc self.homophily = homophily self.radius = radius @@ -79,8 +72,8 @@ def __init__( # the coordinates of a cell as well as # its contents. (coord_iter) for _, pos in self.grid.coord_iter(): - if self.random.random() < self.density: - agent_type = 1 if self.random.random() < self.minority_pc else 0 + if self.random.random() < density: + agent_type = 1 if self.random.random() < minority_pc else 0 agent = SchellingAgent(self, agent_type) self.grid.place_agent(agent, pos) @@ -95,5 +88,4 @@ def step(self): self.datacollector.collect(self) - if self.happy == len(self.agents): - self.running = False + self.running = self.happy != len(self.agents) From e8978399a3b6100dafc348eaa801f29621b945bc Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Tue, 15 Oct 2024 09:15:22 +0200 Subject: [PATCH 114/116] Moving examples to use the new discrete spaces (#198) --- .../epstein_civil_violence/agent.py | 62 ++++----- .../epstein_civil_violence/model.py | 52 ++++---- examples/advanced/pd_grid/pd_grid/agent.py | 11 +- examples/advanced/pd_grid/pd_grid/model.py | 10 +- .../sugarscape_g1mt/sugarscape_g1mt/model.py | 20 +-- .../sugarscape_g1mt/resource_agents.py | 7 +- .../sugarscape_g1mt/trader_agents.py | 82 +++++------- .../advanced/wolf_sheep/wolf_sheep/agents.py | 124 ++++++++---------- .../advanced/wolf_sheep/wolf_sheep/model.py | 24 ++-- .../wolf_sheep/wolf_sheep/random_walk.py | 40 ------ .../wolf_sheep/wolf_sheep/test_random_walk.py | 78 ----------- 11 files changed, 175 insertions(+), 335 deletions(-) delete mode 100644 examples/advanced/wolf_sheep/wolf_sheep/random_walk.py delete mode 100644 examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py index b746a5a4aa7..edd1d1ebabf 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py @@ -3,13 +3,23 @@ import mesa -class Citizen(mesa.Agent): +class EpsteinAgent(mesa.experimental.cell_space.CellAgent): + def update_neighbors(self): + """ + Look around and see who my neighbors are + """ + self.neighborhood = self.cell.get_neighborhood(radius=self.vision) + + self.neighbors = self.neighborhood.agents + self.empty_neighbors = [c for c in self.neighborhood if c.is_empty] + + +class Citizen(EpsteinAgent): """ A member of the general population, may or may not be in active rebellion. Summary of rule: If grievance - risk > threshold, rebel. Attributes: - x, y: Grid coordinates hardship: Agent's 'perceived hardship (i.e., physical or economic privation).' Exogenous, drawn from U(0,1). regime_legitimacy: Agent's perception of regime legitimacy, equal @@ -30,7 +40,6 @@ class Citizen(mesa.Agent): def __init__( self, model, - pos, hardship, regime_legitimacy, risk_aversion, @@ -40,7 +49,7 @@ def __init__( """ Create a new Citizen. Args: - x, y: Grid coordinates + model: the model to which the agent belongs hardship: Agent's 'perceived hardship (i.e., physical or economic privation).' Exogenous, drawn from U(0,1). regime_legitimacy: Agent's perception of regime legitimacy, equal @@ -53,8 +62,6 @@ def __init__( model: model instance """ super().__init__(model) - self.breed = "citizen" - self.pos = pos self.hardship = hardship self.regime_legitimacy = regime_legitimacy self.risk_aversion = risk_aversion @@ -79,32 +86,21 @@ def step(self): self.condition = "Active" else: self.condition = "Quiescent" - if self.model.movement and self.empty_neighbors: - new_pos = self.random.choice(self.empty_neighbors) - self.model.grid.move_agent(self, new_pos) - def update_neighbors(self): - """ - Look around and see who my neighbors are - """ - self.neighborhood = self.model.grid.get_neighborhood( - self.pos, moore=True, radius=self.vision - ) - self.neighbors = self.model.grid.get_cell_list_contents(self.neighborhood) - self.empty_neighbors = [ - c for c in self.neighborhood if self.model.grid.is_cell_empty(c) - ] + if self.model.movement and self.empty_neighbors: + new_cell = self.random.choice(self.empty_neighbors) + self.move_to(new_cell) def update_estimated_arrest_probability(self): """ Based on the ratio of cops to actives in my neighborhood, estimate the p(Arrest | I go active). """ - cops_in_vision = len([c for c in self.neighbors if c.breed == "cop"]) + cops_in_vision = len([c for c in self.neighbors if isinstance(c, Cop)]) actives_in_vision = 1.0 # citizen counts herself for c in self.neighbors: if ( - c.breed == "citizen" + isinstance(c, Citizen) and c.condition == "Active" and c.jail_sentence == 0 ): @@ -114,7 +110,7 @@ def update_estimated_arrest_probability(self): ) -class Cop(mesa.Agent): +class Cop(EpsteinAgent): """ A cop for life. No defection. Summary of rule: Inspect local vision and arrest a random active agent. @@ -126,7 +122,7 @@ class Cop(mesa.Agent): able to inspect """ - def __init__(self, model, pos, vision): + def __init__(self, model, vision): """ Create a new Cop. Args: @@ -136,8 +132,6 @@ def __init__(self, model, pos, vision): model: model instance """ super().__init__(model) - self.breed = "cop" - self.pos = pos self.vision = vision def step(self): @@ -149,7 +143,7 @@ def step(self): active_neighbors = [] for agent in self.neighbors: if ( - agent.breed == "citizen" + isinstance(agent, Citizen) and agent.condition == "Active" and agent.jail_sentence == 0 ): @@ -161,16 +155,4 @@ def step(self): arrestee.condition = "Quiescent" if self.model.movement and self.empty_neighbors: new_pos = self.random.choice(self.empty_neighbors) - self.model.grid.move_agent(self, new_pos) - - def update_neighbors(self): - """ - Look around and see who my neighbors are. - """ - self.neighborhood = self.model.grid.get_neighborhood( - self.pos, moore=True, radius=self.vision - ) - self.neighbors = self.model.grid.get_cell_list_contents(self.neighborhood) - self.empty_neighbors = [ - c for c in self.neighborhood if self.model.grid.is_cell_empty(c) - ] + self.move_to(new_pos) diff --git a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py index b278f23e18d..8bf06bf1540 100644 --- a/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +++ b/examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py @@ -59,7 +59,9 @@ def __init__( self.max_iters = max_iters self.iteration = 0 - self.grid = mesa.space.SingleGrid(width, height, torus=True) + self.grid = mesa.experimental.cell_space.OrthogonalMooreGrid( + (width, height), capacity=1, torus=True + ) model_reporters = { "Quiescent": lambda m: self.count_type_citizens(m, "Quiescent"), @@ -68,9 +70,9 @@ def __init__( "Cops": self.count_cops, } agent_reporters = { - "x": lambda a: a.pos[0], - "y": lambda a: a.pos[1], - "breed": lambda a: a.breed, + "x": lambda a: a.cell.coordinate[0], + "y": lambda a: a.cell.coordinate[1], + "breed": lambda a: type(a).__name__, "jail_sentence": lambda a: getattr(a, "jail_sentence", None), "condition": lambda a: getattr(a, "condition", None), "arrest_probability": lambda a: getattr(a, "arrest_probability", None), @@ -81,22 +83,21 @@ def __init__( if self.cop_density + self.citizen_density > 1: raise ValueError("Cop density + citizen density must be less than 1") - for contents, (x, y) in self.grid.coord_iter(): + for cell in self.grid.all_cells: if self.random.random() < self.cop_density: - cop = Cop(self, (x, y), vision=self.cop_vision) - self.grid[x][y] = cop + cop = Cop(self, vision=self.cop_vision) + cop.move_to(cell) elif self.random.random() < (self.cop_density + self.citizen_density): citizen = Citizen( self, - (x, y), hardship=self.random.random(), regime_legitimacy=self.legitimacy, risk_aversion=self.random.random(), threshold=self.active_threshold, vision=self.citizen_vision, ) - self.grid[x][y] = citizen + citizen.move_to(cell) self.running = True self.datacollector.collect(self) @@ -117,34 +118,29 @@ def count_type_citizens(model, condition, exclude_jailed=True): """ Helper method to count agents by Quiescent/Active. """ - count = 0 - for agent in model.agents: - if agent.breed == "cop": - continue - if exclude_jailed and agent.jail_sentence > 0: - continue - if agent.condition == condition: - count += 1 - return count + citizens = model.agents_by_type[Citizen] + + if exclude_jailed: + return len( + [ + c + for c in citizens + if (c.condition == condition) and (c.jail_sentence == 0) + ] + ) + else: + return len([c for c in citizens if c.condition == condition]) @staticmethod def count_jailed(model): """ Helper method to count jailed agents. """ - count = 0 - for agent in model.agents: - if agent.breed == "citizen" and agent.jail_sentence > 0: - count += 1 - return count + return len([a for a in model.agents_by_type[Citizen] if a.jail_sentence > 0]) @staticmethod def count_cops(model): """ Helper method to count jailed agents. """ - count = 0 - for agent in model.agents: - if agent.breed == "cop": - count += 1 - return count + return len(model.agents_by_type[Cop]) diff --git a/examples/advanced/pd_grid/pd_grid/agent.py b/examples/advanced/pd_grid/pd_grid/agent.py index 85121327826..4890b74905b 100644 --- a/examples/advanced/pd_grid/pd_grid/agent.py +++ b/examples/advanced/pd_grid/pd_grid/agent.py @@ -1,7 +1,7 @@ -import mesa +from mesa.experimental.cell_space import CellAgent -class PDAgent(mesa.Agent): +class PDAgent(CellAgent): """Agent member of the iterated, spatial prisoner's dilemma model.""" def __init__(self, model, starting_move=None): @@ -22,14 +22,15 @@ def __init__(self, model, starting_move=None): self.next_move = None @property - def isCooroperating(self): + def is_cooroperating(self): return self.move == "C" def step(self): """Get the best neighbor's move, and change own move accordingly if better than own score.""" - neighbors = self.model.grid.get_neighbors(self.pos, True, include_center=True) + # neighbors = self.model.grid.get_neighbors(self.pos, True, include_center=True) + neighbors = [*list(self.cell.neighborhood.agents), self] best_neighbor = max(neighbors, key=lambda a: a.score) self.next_move = best_neighbor.move @@ -41,7 +42,7 @@ def advance(self): self.score += self.increment_score() def increment_score(self): - neighbors = self.model.grid.get_neighbors(self.pos, True) + neighbors = self.cell.neighborhood.agents if self.model.activation_order == "Simultaneous": moves = [neighbor.next_move for neighbor in neighbors] else: diff --git a/examples/advanced/pd_grid/pd_grid/model.py b/examples/advanced/pd_grid/pd_grid/model.py index b1c2a05c257..38ef5f5b945 100644 --- a/examples/advanced/pd_grid/pd_grid/model.py +++ b/examples/advanced/pd_grid/pd_grid/model.py @@ -1,4 +1,5 @@ import mesa +from mesa.experimental.cell_space import OrthogonalMooreGrid from .agent import PDAgent @@ -25,15 +26,18 @@ def __init__( Determines the agent activation regime. payoffs: (optional) Dictionary of (move, neighbor_move) payoffs. """ - super().__init__() + super().__init__(seed=seed) self.activation_order = activation_order - self.grid = mesa.space.SingleGrid(width, height, torus=True) + self.grid = OrthogonalMooreGrid((width, height), torus=True) + + if payoffs is not None: + self.payoff = payoffs # Create agents for x in range(width): for y in range(height): agent = PDAgent(self) - self.grid.place_agent(agent, (x, y)) + agent.cell = self.grid[(x, y)] self.datacollector = mesa.DataCollector( { diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index 91bb001d9ad..d51b8bc0746 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -2,6 +2,7 @@ import mesa import numpy as np +from mesa.experimental.cell_space import OrthogonalVonNeumannGrid from .resource_agents import Resource from .trader_agents import Trader @@ -69,7 +70,7 @@ def __init__( self.running = True # initiate mesa grid class - self.grid = mesa.space.MultiGrid(self.width, self.height, torus=False) + self.grid = OrthogonalVonNeumannGrid((self.width, self.height), torus=False) # initiate datacollector self.datacollector = mesa.DataCollector( model_reporters={ @@ -88,11 +89,10 @@ def __init__( sugar_distribution = np.genfromtxt(Path(__file__).parent / "sugar-map.txt") spice_distribution = np.flip(sugar_distribution, 1) - for _, (x, y) in self.grid.coord_iter(): - max_sugar = sugar_distribution[x, y] - max_spice = spice_distribution[x, y] - resource = Resource(self, max_sugar, max_spice) - self.grid.place_agent(resource, (x, y)) + 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) for _ in range(self.initial_population): # get agent position @@ -111,18 +111,18 @@ def __init__( ) # give agents vision vision = int(self.random.uniform(self.vision_min, self.vision_max + 1)) + + cell = self.grid[(x, y)] # create Trader object - trader = Trader( + Trader( self, - moore=False, + cell, sugar=sugar, spice=spice, metabolism_sugar=metabolism_sugar, metabolism_spice=metabolism_spice, vision=vision, ) - # place agent - self.grid.place_agent(trader, (x, y)) def step(self): """ diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py index 042f167214f..d9f276948c1 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py @@ -1,7 +1,7 @@ -import mesa +from mesa.experimental.cell_space import FixedAgent -class Resource(mesa.Agent): +class Resource(FixedAgent): """ Resource: - contains an amount of sugar and spice @@ -9,12 +9,13 @@ class Resource(mesa.Agent): - grows 1 amount of spice at each turn """ - def __init__(self, model, max_sugar, max_spice): + 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): """ diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py index 2c63a8a19c8..579f3470978 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py @@ -1,26 +1,26 @@ import math -import mesa +from mesa.experimental.cell_space import CellAgent from .resource_agents import Resource # Helper function -def get_distance(pos_1, pos_2): +def get_distance(cell_1, cell_2): """ Calculate the Euclidean distance between two positions used in trade.move() """ - x1, y1 = pos_1 - x2, y2 = pos_2 + x1, y1 = cell_1.coordinate + x2, y2 = cell_2.coordinate dx = x1 - x2 dy = y1 - y2 return math.sqrt(dx**2 + dy**2) -class Trader(mesa.Agent): +class Trader(CellAgent): """ Trader: - has a metabolism of sugar and spice @@ -30,7 +30,7 @@ class Trader(mesa.Agent): def __init__( self, model, - moore=False, + cell, sugar=0, spice=0, metabolism_sugar=0, @@ -38,7 +38,7 @@ def __init__( vision=0, ): super().__init__(model) - self.moore = moore + self.cell = cell self.sugar = sugar self.spice = spice self.metabolism_sugar = metabolism_sugar @@ -47,35 +47,31 @@ def __init__( self.prices = [] self.trade_partners = [] - def get_resource(self, pos): - this_cell = self.model.grid.get_cell_list_contents(pos) - for agent in this_cell: - if type(agent) is Resource: + 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 {pos}") + raise Exception(f"Resource agent not found in the position {cell.coordinate}") - def get_trader(self, pos): + def get_trader(self, cell): """ helper function used in self.trade_with_neighbors() """ - this_cell = self.model.grid.get_cell_list_contents(pos) - - for agent in this_cell: + for agent in cell.agents: if isinstance(agent, Trader): return agent - def is_occupied_by_other(self, pos): + def is_occupied_by_other(self, cell): """ helper function part 1 of self.move() """ - if pos == self.pos: + 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 - this_cell = self.model.grid.get_cell_list_contents(pos) - return any(isinstance(a, Trader) for a in this_cell) + return any(isinstance(a, Trader) for a in cell.agents) def calculate_welfare(self, sugar, spice): """ @@ -202,7 +198,7 @@ def trade(self, other): if math.isclose(mrs_self, mrs_other): return - # calcualte price + # calculate price price = math.sqrt(mrs_self * mrs_other) if mrs_self > mrs_other: @@ -242,22 +238,20 @@ def move(self): # 1. identify all possible moves - neighbors = [ - i - for i in self.model.grid.get_neighborhood( - self.pos, self.moore, True, self.vision - ) - if not self.is_occupied_by_other(i) + neighboring_cells = [ + cell + for cell in self.cell.get_neighborhood(self.vision, include_center=True) + if not self.is_occupied_by_other(cell) ] # 2. determine which move maximizes welfare welfares = [ self.calculate_welfare( - self.sugar + self.get_resource(pos).sugar_amount, - self.spice + self.get_resource(pos).spice_amount, + self.sugar + self.get_resource(cell).sugar_amount, + self.spice + self.get_resource(cell).spice_amount, ) - for pos in neighbors + for cell in neighboring_cells ] # 3. Find closest best option @@ -270,22 +264,20 @@ def move(self): ] # convert index to positions of those cells - candidates = [neighbors[i] for i in candidate_indices] + candidates = [neighboring_cells[i] for i in candidate_indices] - min_dist = min(get_distance(self.pos, pos) for pos in candidates) + min_dist = min(get_distance(self.cell, cell) for cell in candidates) final_candidates = [ - pos - for pos in candidates - if math.isclose(get_distance(self.pos, pos), min_dist, rel_tol=1e-02) + cell + for cell in candidates + if math.isclose(get_distance(self.cell, cell), min_dist, rel_tol=1e-02) ] - final_candidate = self.random.choice(final_candidates) - # 4. Move Agent - self.model.grid.move_agent(self, final_candidate) + self.cell = self.random.choice(final_candidates) def eat(self): - patch = self.get_resource(self.pos) + patch = self.get_resource(self.cell) if patch.sugar_amount > 0: self.sugar += patch.sugar_amount patch.sugar_amount = 0 @@ -302,7 +294,6 @@ def maybe_die(self): """ if self.is_starved(): - self.model.grid.remove_agent(self) self.remove() def trade_with_neighbors(self): @@ -315,11 +306,9 @@ def trade_with_neighbors(self): """ neighbor_agents = [ - self.get_trader(pos) - for pos in self.model.grid.get_neighborhood( - self.pos, self.moore, False, self.vision - ) - if self.is_occupied_by_other(pos) + 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: @@ -327,7 +316,6 @@ def trade_with_neighbors(self): # iterate through traders in neighboring cells and trade for a in neighbor_agents: - if a: - self.trade(a) + self.trade(a) return diff --git a/examples/advanced/wolf_sheep/wolf_sheep/agents.py b/examples/advanced/wolf_sheep/wolf_sheep/agents.py index c0b06f3a509..8e71988bc9a 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/agents.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/agents.py @@ -1,93 +1,81 @@ -import mesa +from mesa.experimental.cell_space import CellAgent, FixedAgent -from .random_walk import RandomWalker +class Animal(CellAgent): + """The base animal class.""" -class Sheep(RandomWalker): - """ - A sheep that walks around, reproduces (asexually) and gets eaten. - - The init is the same as the RandomWalker. - """ - - energy = None + def __init__(self, model, energy, p_reproduce, energy_from_food, cell): + """Initializes an animal. - def __init__(self, model, moore, energy=None): - super().__init__(model, moore=moore) + 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): - """ - A model step. Move, then eat grass and reproduce. - """ - self.random_move() - living = True - - if self.model.grass: - # Reduce energy - self.energy -= 1 - - # If there is grass available, eat it - this_cell = self.model.grid.get_cell_list_contents([self.pos]) - grass_patch = next(obj for obj in this_cell if isinstance(obj, GrassPatch)) - if grass_patch.fully_grown: - self.energy += self.model.sheep_gain_from_food - grass_patch.fully_grown = False + """One step of the agent.""" + self.cell = self.cell.neighborhood.select_random_cell() + self.energy -= 1 - # Death - if self.energy < 0: - self.model.grid.remove_agent(self) - self.remove() - living = False + self.feed() - if living and self.random.random() < self.model.sheep_reproduce: - # Create a new sheep: - if self.model.grass: - self.energy /= 2 - lamb = Sheep(self.model, self.moore, self.energy) - self.model.grid.place_agent(lamb, self.pos) + if self.energy < 0: + self.remove() + elif self.random.random() < self.p_reproduce: + self.spawn_offspring() -class Wolf(RandomWalker): - """ - A wolf that walks around, reproduces (asexually) and eats sheep. - """ +class Sheep(Animal): + """A sheep that walks around, reproduces (asexually) and gets eaten.""" - energy = None + 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 - def __init__(self, model, moore, energy=None): - super().__init__(model, moore=moore) - self.energy = energy - def step(self): - self.random_move() - self.energy -= 1 +class Wolf(Animal): + """A wolf that walks around, reproduces (asexually) and eats sheep.""" - # If there are sheep present, eat one - x, y = self.pos - this_cell = self.model.grid.get_cell_list_contents([self.pos]) - sheep = [obj for obj in this_cell if isinstance(obj, 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.model.wolf_gain_from_food + self.energy += self.energy_from_food # Kill the sheep - self.model.grid.remove_agent(sheep_to_eat) sheep_to_eat.remove() - # Death or reproduction - if self.energy < 0: - self.model.grid.remove_agent(self) - self.remove() - else: - if self.random.random() < self.model.wolf_reproduce: - # Create a new wolf cub - self.energy /= 2 - cub = Wolf(self.model, self.moore, self.energy) - self.model.grid.place_agent(cub, self.pos) - -class GrassPatch(mesa.Agent): +class GrassPatch(FixedAgent): """ A patch of grass that grows at a fixed rate and it is eaten by sheep """ diff --git a/examples/advanced/wolf_sheep/wolf_sheep/model.py b/examples/advanced/wolf_sheep/wolf_sheep/model.py index 85b60b73104..5b43b7912e1 100644 --- a/examples/advanced/wolf_sheep/wolf_sheep/model.py +++ b/examples/advanced/wolf_sheep/wolf_sheep/model.py @@ -10,6 +10,7 @@ """ import mesa +from mesa.experimental.cell_space import OrthogonalMooreGrid from .agents import GrassPatch, Sheep, Wolf @@ -50,6 +51,7 @@ def __init__( grass=False, grass_regrowth_time=30, sheep_gain_from_food=4, + seed=None, ): """ Create a new Wolf-Sheep model with the given parameters. @@ -65,20 +67,16 @@ def __init__( once it is eaten sheep_gain_from_food: Energy sheep gain from grass, if enabled. """ - super().__init__() + super().__init__(seed=None) # Set parameters self.width = width self.height = height self.initial_sheep = initial_sheep self.initial_wolves = initial_wolves - self.sheep_reproduce = sheep_reproduce - self.wolf_reproduce = wolf_reproduce - self.wolf_gain_from_food = wolf_gain_from_food self.grass = grass self.grass_regrowth_time = grass_regrowth_time - self.sheep_gain_from_food = sheep_gain_from_food - self.grid = mesa.space.MultiGrid(self.width, self.height, torus=True) + self.grid = OrthogonalMooreGrid((self.width, self.height), torus=True) collectors = { "Wolves": lambda m: len(m.agents_by_type[Wolf]), @@ -95,20 +93,20 @@ def __init__( x = self.random.randrange(self.width) y = self.random.randrange(self.height) energy = self.random.randrange(2 * self.sheep_gain_from_food) - sheep = Sheep(self, True, energy) - self.grid.place_agent(sheep, (x, y)) + 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 = Wolf(self, True, energy) - self.grid.place_agent(wolf, (x, y)) + Wolf(self, energy, wolf_reproduce, wolf_gain_from_food, self.grid[(x, y)]) # Create grass patches if self.grass: - for agent, (x, y) in self.grid.coord_iter(): + for cell in self.grid.all_cells: fully_grown = self.random.choice([True, False]) if fully_grown: @@ -117,7 +115,7 @@ def __init__( countdown = self.random.randrange(self.grass_regrowth_time) patch = GrassPatch(self, fully_grown, countdown) - self.grid.place_agent(patch, (x, y)) + patch.cell = cell self.running = True self.datacollector.collect(self) @@ -128,7 +126,7 @@ def step(self): # 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].do("step") + self.agents_by_type[agent_type].shuffle_do("step") # collect data self.datacollector.collect(self) diff --git a/examples/advanced/wolf_sheep/wolf_sheep/random_walk.py b/examples/advanced/wolf_sheep/wolf_sheep/random_walk.py deleted file mode 100644 index a204f9cc414..00000000000 --- a/examples/advanced/wolf_sheep/wolf_sheep/random_walk.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -Generalized behavior for random walking, one grid cell at a time. -""" - -import mesa - - -class RandomWalker(mesa.Agent): - """ - Class implementing random walker methods in a generalized manner. - - Not intended to be used on its own, but to inherit its methods to multiple - other agents. - """ - - grid = None - x = None - y = None - moore = True - - def __init__(self, model, moore=True): - """ - grid: The MultiGrid object in which the agent lives. - x: The agent's current x coordinate - y: The agent's current y coordinate - moore: If True, may move in all 8 directions. - Otherwise, only up, down, left, right. - """ - super().__init__(model) - self.moore = moore - - def random_move(self): - """ - Step one cell in any allowable direction. - """ - # Pick the next cell from the adjacent cells. - next_moves = self.model.grid.get_neighborhood(self.pos, self.moore, True) - next_move = self.random.choice(next_moves) - # Now move: - self.model.grid.move_agent(self, next_move) diff --git a/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py b/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py deleted file mode 100644 index 393a46b18c4..00000000000 --- a/examples/advanced/wolf_sheep/wolf_sheep/test_random_walk.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -Testing the RandomWalker by having an ABM composed only of random walker -agents. -""" - -from mesa import Model -from mesa.space import MultiGrid -from mesa.visualization.TextVisualization import TextGrid, TextVisualization -from wolf_sheep.random_walk import RandomWalker - - -class WalkerAgent(RandomWalker): - """ - Agent which only walks around. - """ - - def step(self): - self.random_move() - - -class WalkerWorld(Model): - """ - Random walker world. - """ - - height = 10 - width = 10 - - def __init__(self, width, height, agent_count): - """ - Create a new WalkerWorld. - - Args: - width, height: World size. - agent_count: How many agents to create. - """ - self.height = height - self.width = width - self.grid = MultiGrid(self.width, self.height, torus=True) - self.agent_count = agent_count - - # Create agents - for i in range(self.agent_count): - x = self.random.randrange(self.width) - y = self.random.randrange(self.height) - a = WalkerAgent(i, (x, y), self, True) - self.grid.place_agent(a, (x, y)) - - def step(self): - self.agents.shuffle_do("step") - - -class WalkerWorldViz(TextVisualization): - """ - ASCII Visualization for a WalkerWorld agent. - Each cell is displayed as the number of agents currently in that cell. - """ - - def __init__(self, model): - """ - Create a new visualization for a WalkerWorld instance. - - args: - model: An instance of a WalkerWorld model. - """ - self.model = model - grid_viz = TextGrid(self.model.grid, None) - grid_viz.converter = lambda x: str(len(x)) - self.elements = [grid_viz] - - -if __name__ == "__main__": - print("Testing 10x10 world, with 50 random walkers, for 10 steps.") - model = WalkerWorld(10, 10, 50) - viz = WalkerWorldViz(model) - for i in range(10): - print("Step:", str(i)) - viz.step() From 9d0d6c070d1d9951de22d4126c5e7c3b70e690cb Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Tue, 15 Oct 2024 10:52:17 +0200 Subject: [PATCH 115/116] Add Readme for examples Add a Readme for the Example folder, largely taken from the mesa-examples Readme. --- examples/README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 examples/README.md diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000000..544c530c60b --- /dev/null +++ b/examples/README.md @@ -0,0 +1,37 @@ +# Mesa core examples +This folder contains a collection of example models built using Mesa. These core models are maintained by the Mesa team and are intended to demonstrate the capabilities of Mesa. + +More user examples and showcases can be found in the [mesa-examples](https://github.com/projectmesa/mesa-examples) repository. + +## Basic Examples +The basic examples are relatively simple and only use stable Mesa features. They are good starting points for learning how to use Mesa. + +### [Boltzmann Wealth Model](basic/boltzmann_wealth_model) +Completed code to go along with the [tutorial](https://mesa.readthedocs.io/latest/tutorials/intro_tutorial.html) on making a simple model of how a highly-skewed wealth distribution can emerge from simple rules. + +### [Boids Flockers Model](basic/boid_flockers) +[Boids](https://en.wikipedia.org/wiki/Boids)-style flocking model, demonstrating the use of agents moving through a continuous space following direction vectors. + +### [Conway's Game of Life](basic/conways_game_of_life) +Implementation of [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life), a cellular automata where simple rules can give rise to complex patterns. + +### [Schelling Segregation Model](basic/schelling) +Mesa implementation of the classic [Schelling segregation](http://nifty.stanford.edu/2014/mccown-schelling-model-segregation/) model. + +### [Virus on a Network Model](basic/virus_on_network) +This model is based on the NetLogo [Virus on a Network](https://ccl.northwestern.edu/netlogo/models/VirusonaNetwork) model. + +## Advanced Examples +The advanced examples are more complex and may use experimental Mesa features. They are good starting points for learning how to build more complex models. + +### [Epstein Civil Violence Model](advanced/epstein_civil_violence) +Joshua Epstein's [model](http://www.uvm.edu/~pdodds/files/papers/others/2002/epstein2002a.pdf) of how a decentralized uprising can be suppressed or reach a critical mass of support. + +### [Demographic Prisoner's Dilemma on a Grid](advanced/pd_grid) +Grid-based demographic prisoner's dilemma model, demonstrating how simple rules can lead to the emergence of widespread cooperation -- and how a model activation regime can change its outcome. + +### [Sugarscape Model with Traders](advanced/sugarscape_g1mt) +This is Epstein & Axtell's Sugarscape model with Traders, a detailed description is in Chapter four of *Growing Artificial Societies: Social Science from the Bottom Up (1996)*. The model shows how emergent price equilibrium can happen via decentralized dynamics. + +### [Wolf-Sheep Predation Model](advanced/wolf_sheep) +Implementation of an ecological model of predation and reproduction, based on the NetLogo [Wolf Sheep Predation](http://ccl.northwestern.edu/netlogo/models/WolfSheepPredation) model. \ No newline at end of file From 9c8a581f4c35a5c4d5e44754069b151caf667194 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Tue, 15 Oct 2024 11:02:57 +0200 Subject: [PATCH 116/116] Exclude examples from ruff, fix remaining stuff Don't run ruff on the examples for now, and fix the remaining pre-commit issues (mainly codespell). --- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py index d51b8bc0746..35e6d9e0e7b 100644 --- a/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -55,7 +55,7 @@ def __init__( enable_trade=True, ): super().__init__() - # Initiate width and heigh of sugarscape + # Initiate width and height of sugarscape self.width = width self.height = height # Initiate population attributes @@ -133,7 +133,7 @@ def step(self): self.agents_by_type[Resource].do("step") # step trader agents - # to account for agent death and removal we need a seperate data strcuture to + # to account for agent death and removal we need a separate data structure to # iterate trader_shuffle = self.agents_by_type[Trader].shuffle() diff --git a/pyproject.toml b/pyproject.toml index fd48a03005a..1d0c3bdf095 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,7 @@ path = "mesa/__init__.py" # Hardcode to Python 3.10. # Reminder to update mesa-examples if the value below is changed. target-version = "py310" -extend-exclude = ["docs", "build"] +extend-exclude = ["docs", "build", "examples"] [tool.ruff.lint] select = [