From d06742e0a091845bd56ceee7d5e7b6cbabd2651a Mon Sep 17 00:00:00 2001 From: Jatin Khilnani Date: Mon, 24 Apr 2023 16:48:52 -0400 Subject: [PATCH 1/2] Update pip statement for zsh --- CONTRIBUTING.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index f65b7baa1c0..c901713c1c7 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -28,7 +28,7 @@ discuss via `Matrix`_ OR via `an issue`_. - `Clone your repository`_ from Github to your machine. - Create a new branch in your fork: ``git checkout -b BRANCH_NAME`` - Run ``git config pull.rebase true``. This prevents messy merge commits when updating your branch on top of Mesa main branch. -- Install an editable version with developer requirements locally: ``pip install -e .[dev]`` +- Install an editable version with developer requirements locally: ``pip install -e ".[dev]"`` - Edit the code. Save. - Git add the new files and files with changes: ``git add FILE_NAME`` - Git commit your changes with a meaningful message: ``git commit -m "Fix issue X"`` From 6fd6dec67af5d62e080d14f3ee23079b68a88aeb Mon Sep 17 00:00:00 2001 From: Jatin Khilnani Date: Mon, 24 Apr 2023 20:53:01 -0400 Subject: [PATCH 2/2] Update advanced tutorial --- docs/tutorials/MoneyModel.py | 65 ++++++++++ docs/tutorials/adv_tutorial.ipynb | 198 ++++++++++++++++++------------ 2 files changed, 186 insertions(+), 77 deletions(-) create mode 100644 docs/tutorials/MoneyModel.py diff --git a/docs/tutorials/MoneyModel.py b/docs/tutorials/MoneyModel.py new file mode 100644 index 00000000000..d6b8de0d66e --- /dev/null +++ b/docs/tutorials/MoneyModel.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 + + +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 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() + + +class MoneyModel(mesa.Model): + """A model with some number of agents.""" + + def __init__(self, N, width, height): + self.num_agents = N + self.grid = mesa.space.MultiGrid(width, height, True) + self.schedule = mesa.time.RandomActivation(self) + + # 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.datacollector = mesa.DataCollector( + model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} + ) + + def step(self): + self.datacollector.collect(self) + self.schedule.step() diff --git a/docs/tutorials/adv_tutorial.ipynb b/docs/tutorials/adv_tutorial.ipynb index db213e34e27..6610db9d6c6 100644 --- a/docs/tutorials/adv_tutorial.ipynb +++ b/docs/tutorials/adv_tutorial.ipynb @@ -8,6 +8,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -15,7 +16,7 @@ "\n", "So far, we've built a model, run it, and analyzed some output afterwards. However, one of the advantages of agent-based models is that we can often watch them run step by step, potentially spotting unexpected patterns, behaviors or bugs, or developing new intuitions, hypotheses, or insights. Other times, watching a model run can explain it to an unfamiliar audience better than static explanations. Like many ABM frameworks, Mesa allows you to create an interactive visualization of the model. In this section we'll walk through creating a visualization using built-in components, and (for advanced users) how to create a new visualization element.\n", "\n", - "**Note for Jupyter users: Due to conflicts with the tornado server Mesa uses and Jupyter, the interactive browser of your model will load but likely not work. This will require you to use run the code from .py files. The Mesa development team is working to develop a** [Jupyter compatible interface](https://github.com/projectmesa/mesa/issues/1363).**\n", + "**Note for Jupyter users: Due to conflicts with the tornado server Mesa uses and Jupyter, the interactive browser of your model will load but likely not work. This will require you to use run the code from .py files. The Mesa development team is working to develop a** [Jupyter compatible interface](https://github.com/projectmesa/mesa/issues/1363).\n", "\n", "First, a quick explanation of how Mesa's interactive visualization works. Visualization is done in a browser window, using JavaScript to draw the different things being visualized at each step of the model. To do this, Mesa launches a small web server, which runs the model, turns each step into a JSON object (essentially, structured plain text) and sends those steps to the browser.\n", "\n", @@ -23,38 +24,39 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "#### Grid Visualization\n", "\n", - "To start with, let's have a visualization where we can watch the agents moving around the grid. For this, you will need to put your model code in a separate Python source file; for example, `MoneyModel.py`. Next, either in the same file or in a new one (e.g. `MoneyModel_Viz.py`) import the server class and the Canvas Grid class (so-called because it uses HTML5 canvas to draw a grid). If you're in a new file, you'll also need to import the actual model object." + "To start with, let's have a visualization where we can watch the agents moving around the grid. For this, you will need to put your model code in a separate Python source file. For now, let us use the `MoneyModel` created in the [Introductory Tutorial](https://mesa.readthedocs.io/en/main/tutorials/intro_tutorial.html) saved to `MoneyModel.py` file provided.\n", + "Next, in a new source file (e.g. `MoneyModel_Viz.py`) include the code shown in the following cells to run and avoid Jupyter compatibility issue." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": { "tags": [] }, "outputs": [], "source": [ - "import mesa\n", - "\n", "# If MoneyModel.py is where your code is:\n", - "# from MoneyModel import MoneyModel" + "from MoneyModel import mesa, MoneyModel" ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ - "`CanvasGrid` works by looping over every cell in a grid, and generating a portrayal for every agent it finds. A portrayal is a dictionary (which can easily be turned into a JSON object) which tells the JavaScript side how to draw it. The only thing we need to provide is a function which takes an agent, and returns a portrayal object. Here's the simplest one: it'll draw each agent as a red, filled circle which fills half of each cell." + "Mesa's `CanvasGrid` visualization class works by looping over every cell in a grid, and generating a portrayal for every agent it finds. A portrayal is a dictionary (which can easily be turned into a JSON object) which tells the JavaScript side how to draw it. The only thing we need to provide is a function which takes an agent, and returns a portrayal object. Here's the simplest one: it'll draw each agent as a red, filled circle which fills half of each cell." ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": { "tags": [] }, @@ -80,7 +82,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": { "tags": [] }, @@ -90,63 +92,49 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "attachments": {}, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "\"\"\"\n", - "The full code should now look like:\n", - "\"\"\"\n", - "# from MoneyModel import *\n", - "import mesa\n", - "\n", - "\n", - "def agent_portrayal(agent):\n", - " portrayal = {\n", - " \"Shape\": \"circle\",\n", - " \"Filled\": \"true\",\n", - " \"Layer\": 0,\n", - " \"Color\": \"red\",\n", - " \"r\": 0.5,\n", - " }\n", - " return portrayal\n", + "Now we create and launch the actual server. We do this with the following arguments:\n", "\n", + "* The model class we're running and visualizing; in this case, `MoneyModel`.\n", + "* A list of module objects to include in the visualization; here, just `[grid]`\n", + "* The title of the model: \"Money Model\"\n", + "* Any inputs or arguments for the model itself. In this case, 100 agents, and height and width of 10.\n", "\n", - "grid = mesa.visualization.CanvasGrid(agent_portrayal, 10, 10, 500, 500)\n", + "Once we create the server, we set the port (use default 8521 here) for it to listen on (you can treat this as just a piece of the URL you’ll open in the browser). " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ "server = mesa.visualization.ModularServer(\n", " MoneyModel, [grid], \"Money Model\", {\"N\": 100, \"width\": 10, \"height\": 10}\n", ")\n", - "server.port = 8521 # The default\n", - "server.launch()" + "server.port = 8521 # the default" ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ - "Now we create and launch the actual server. We do this with the following arguments:\n", - "\n", - "* The model class we're running and visualizing; in this case, `MoneyModel`.\n", - "* A list of module objects to include in the visualization; here, just `[grid]`\n", - "* The title of the model: \"Money Model\"\n", - "* Any inputs or arguments for the model itself. In this case, 100 agents, and height and width of 10.\n", - "\n", - "Once we create the server, we set the port for it to listen on (you can treat this as just a piece of the URL you'll open in the browser). Finally, when you're ready to run the visualization, use the server's `launch()` method.\n", - "\n", - "```python\n", - "server = ModularServer(MoneyModel,\n", - " [grid],\n", - " \"Money Model\",\n", - " {\"N\":100, \"width\":10, \"height\":10})\n", - "server.port = 8521 # The default\n", - "server.launch()\n", - "```\n", - "The full code should now look like:\n", + "Finally, when you’re ready to run the visualization, use the server’s launch() method." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The full code for source file `MoneyModel_Viz.py` should now look like:\n", "\n", "```python\n", - "from MoneyModel import *\n", - "import mesa\n", + "from MoneyModel import mesa, MoneyModel\n", "\n", "\n", "def agent_portrayal(agent):\n", @@ -158,9 +146,7 @@ " return portrayal\n", "\n", "grid = mesa.visualization.CanvasGrid(agent_portrayal, 10, 10, 500, 500)\n", - "server = mesa.visualization.ModularServer(\n", - " MoneyModel, [grid], \"Money Model\", {\"N\": 100, \"width\": 10, \"height\": 10}\n", - ")server = ModularServer(MoneyModel,\n", + "server = mesa.visualization.ModularServer(MoneyModel,\n", " [grid],\n", " \"Money Model\",\n", " {\"N\":100, \"width\":10, \"height\":10})\n", @@ -173,16 +159,17 @@ "\n", "![Empty Visualization](files/viz_empty.png)\n", "\n", - "Click the 'reset' button on the control panel, and you should see the grid fill up with red circles, representing agents.\n", + "Click the `Reset` button on the control panel, and you should see the grid fill up with red circles, representing agents.\n", "\n", "![Redcircles Visualization](files/viz_redcircles.png)\n", "\n", - "Click 'step' to advance the model by one step, and the agents will move around. Click 'run' and the agents will keep moving around, at the rate set by the 'fps' (frames per second) slider at the top. Try moving it around and see how the speed of the model changes. Pressing 'pause' will (as you'd expect) pause the model; presing 'run' again will restart it. Finally, 'reset' will start a new instantiation of the model.\n", + "Click `Step` to advance the model by one step, and the agents will move around. Click `Start` and the agents will keep moving around, at the rate set by the 'fps' (frames per second) slider at the top. Try moving it around and see how the speed of the model changes. Pressing `Stop` will pause the model; presing `Start` again will restart it. Finally, `Reset` will start a new instantiation of the model.\n", "\n", "To stop the visualization server, go back to the terminal where you launched it, and press Control+c." ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -190,13 +177,17 @@ "\n", "In the visualization above, all we could see is the agents moving around -- but not how much money they had, or anything else of interest. Let's change it so that agents who are broke (wealth 0) are drawn in grey, smaller, and above agents who still have money.\n", "\n", - "To do this, we go back to our `agent_portrayal` code and add some code to change the portrayal based on the agent properties.\n", - "\n", - "```python\n", + "To do this, we go back to our `agent_portrayal` code and add some code to change the portrayal based on the agent properties and launch the server again." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ "def agent_portrayal(agent):\n", - " portrayal = {\"Shape\": \"circle\",\n", - " \"Filled\": \"true\",\n", - " \"r\": 0.5}\n", + " portrayal = {\"Shape\": \"circle\", \"Filled\": \"true\", \"r\": 0.5}\n", "\n", " if agent.wealth > 0:\n", " portrayal[\"Color\"] = \"red\"\n", @@ -205,15 +196,21 @@ " portrayal[\"Color\"] = \"grey\"\n", " portrayal[\"Layer\"] = 1\n", " portrayal[\"r\"] = 0.2\n", - " return portrayal\n", - "```\n", - "\n", - "Now launch the server again - this will open a new browser window pointed at the updated visualization. Initially it looks the same, but advance the model and smaller grey circles start to appear. Note that since the zero-wealth agents have a higher layer number, they are drawn on top of the red agents.\n", + " return portrayal" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This will open a new browser window pointed at the updated visualization. Initially it looks the same, but advance the model and smaller grey circles start to appear. Note that since the zero-wealth agents have a higher layer number, they are drawn on top of the red agents.\n", "\n", "![Greycircles Visualization](files/viz_greycircles.png)" ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -223,20 +220,67 @@ "\n", "The basic chart pulls data from the model's DataCollector, and draws it as a line graph using the [Charts.js](http://www.chartjs.org/) JavaScript libraries. We instantiate a chart element with a list of series for the chart to track. Each series is defined in a dictionary, and has a `Label` (which must match the name of a model-level variable collected by the DataCollector) and a `Color` name. We can also give the chart the name of the DataCollector object in the model.\n", "\n", - "Finally, we add the chart to the list of elements in the server. The elements are added to the visualization in the order they appear, so the chart will appear underneath the grid.\n", + "Finally, we add the chart to the list of elements in the server. The elements are added to the visualization in the order they appear, so the chart will appear underneath the grid." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "chart = mesa.visualization.ChartModule(\n", + " [{\"Label\": \"Gini\", \"Color\": \"Black\"}], data_collector_name=\"datacollector\"\n", + ")\n", + "\n", + "server = mesa.visualization.ModularServer(\n", + " MoneyModel, [grid, chart], \"Money Model\", {\"N\": 100, \"width\": 10, \"height\": 10}\n", + ")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Launch the visualization and start a model run, either by launching the server here or through the full code for source file `MoneyModel_Viz.py`.\n", "\n", "```python\n", - "chart = mesa.visualization.ChartModule([{\"Label\": \"Gini\", \n", - " \"Color\": \"Black\"}],\n", - " data_collector_name='datacollector')\n", + "from MoneyModel import mesa, MoneyModel\n", "\n", - "server = mesa.visualization.ModularServer(MoneyModel, \n", - " [grid, chart], \n", - " \"Money Model\", \n", - " {\"N\":100, \"width\":10, \"height\":10})\n", - "```\n", "\n", - "Launch the visualization and start a model run, and you'll see a line chart underneath the grid. Every step of the model, the line chart updates along with the grid. Reset the model, and the chart resets too.\n", + "def agent_portrayal(agent):\n", + " portrayal = {\"Shape\": \"circle\", \"Filled\": \"true\", \"r\": 0.5}\n", + "\n", + " if agent.wealth > 0:\n", + " portrayal[\"Color\"] = \"red\"\n", + " portrayal[\"Layer\"] = 0\n", + " else:\n", + " portrayal[\"Color\"] = \"grey\"\n", + " portrayal[\"Layer\"] = 1\n", + " portrayal[\"r\"] = 0.2\n", + " return portrayal\n", + "\n", + "\n", + "grid = mesa.visualization.CanvasGrid(agent_portrayal, 10, 10, 500, 500)\n", + "chart = mesa.visualization.ChartModule(\n", + " [{\"Label\": \"Gini\", \"Color\": \"Black\"}], data_collector_name=\"datacollector\"\n", + ")\n", + "\n", + "server = mesa.visualization.ModularServer(\n", + " MoneyModel, [grid, chart], \"Money Model\", {\"N\": 100, \"width\": 10, \"height\": 10}\n", + ")\n", + "server.port = 8521 # The default\n", + "server.launch()\n", + "```" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You'll see a line chart underneath the grid. Every step of the model, the line chart updates along with the grid. Reset the model, and the chart resets too.\n", "\n", "![Chart Visualization](files/viz_chart.png)\n", "\n", @@ -468,7 +512,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.6" + "version": "3.11.3" } }, "nbformat": 4,