{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# BatchRunner\n", "\n", "### The Boltzmann Wealth Model " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you want to get straight to the tutorial checkout these environment providers:
\n", "(with Google Account) [![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/mesa/mesa/blob/main/docs/tutorials/7_batch_run.ipynb)
\n", "(No Google Account) [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/mesa/mesa/main?labpath=docs%2Ftutorials%2F7_batch_run.ipynb) (This can take 30 seconds to 5 minutes to load)\n", "\n", "*If you are running locally, please ensure you have the latest Mesa version installed.*\n", "\n", "## Tutorial Description\n", "\n", "This tutorial extends the Boltzmann wealth model from the [Collecting Data tutorial](5_collecting_data.ipynb), by showing how users can use `batch_run` to conduct parameter sweeps of their models.\n", "\n", "*If you are starting here please see the [Running Your First Model tutorial](0_first_model.ipynb) for dependency and start-up instructions*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### IN COLAB? - Run the next cell " ] }, { "cell_type": "raw", "metadata": { "vscode": { "languageId": "raw" } }, "source": [ "%pip install --quiet mesa[rec]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Import Dependencies\n", "This includes importing of dependencies needed for the tutorial." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "# Has multi-dimensional arrays and matrices.\n", "# Has a large collection of mathematical functions to operate on these arrays.\n", "import numpy as np\n", "\n", "# Data manipulation and analysis.\n", "import pandas as pd\n", "\n", "# Data visualization tools.\n", "import seaborn as sns\n", "\n", "import mesa\n", "\n", "# Import Cell Agent and OrthogonalMooreGrid\n", "from mesa.discrete_space import CellAgent, OrthogonalMooreGrid" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Base Model\n", "\n", "The below provides the base model from which we will add batch_run functionality. Of note, this is the same as the [collecting data tutorial](5_collecting_data.ipynb) but we add one agent reporter that counts if money is not given to that agent during a time step.\n", "\n", "We also added `self.running=True` in the `MoneyModel` class. This allows users to provide a conditional stop attribute (e.g. all sheep and wolves die) as opposed to a step count.)\n", "\n", "This is from the [Running Your First Model tutorial](0_first_model.ipynb) tutorial. If you have any questions about it functionality please review that tutorial." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def compute_gini(model):\n", " agent_wealths = [agent.wealth for agent in model.agents]\n", " x = sorted(agent_wealths)\n", " n = model.num_agents\n", " B = sum(xi * (n - i) for i, xi in enumerate(x)) / (n * sum(x))\n", " return 1 + (1 / n) - 2 * B\n", "\n", "\n", "class MoneyAgent(CellAgent):\n", " \"\"\"An agent with fixed initial wealth.\"\"\"\n", "\n", " def __init__(self, model, cell):\n", " super().__init__(model)\n", " self.cell = cell\n", " self.wealth = 1\n", " self.steps_not_given = 0\n", "\n", " def move(self):\n", " self.cell = self.cell.neighborhood.select_random_cell()\n", "\n", " def give_money(self):\n", " cellmates = [a for a in self.cell.agents if a is not self]\n", "\n", " if len(cellmates) > 0 and self.wealth > 0:\n", " other = self.random.choice(cellmates)\n", " other.wealth += 1\n", " self.wealth -= 1\n", " self.steps_not_given = 0\n", " else:\n", " self.steps_not_given += 1\n", "\n", "\n", "class MoneyModel(mesa.Model):\n", " \"\"\"A model with some number of agents.\"\"\"\n", "\n", " def __init__(self, n, width, height, seed=None):\n", " super().__init__(seed=seed)\n", " self.num_agents = n\n", " self.grid = OrthogonalMooreGrid((width, height), torus=True, random=self.random)\n", " # Instantiate DataCollector\n", " self.datacollector = mesa.DataCollector(\n", " model_reporters={\"Gini\": compute_gini},\n", " agent_reporters={\"Wealth\": \"wealth\", \"Steps_not_given\": \"steps_not_given\"},\n", " )\n", " self.running = True\n", "\n", " # Create agents\n", " agents = MoneyAgent.create_agents(\n", " self,\n", " self.num_agents,\n", " self.random.choices(self.grid.all_cells.cells, k=self.num_agents),\n", " )\n", "\n", " def step(self):\n", " # Collect data each step\n", " self.datacollector.collect(self)\n", " self.agents.shuffle_do(\"move\")\n", " self.agents.do(\"give_money\")" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "model = MoneyModel(100, 10, 10)\n", "for _ in range(100):\n", " model.step()\n", "\n", "gini = model.datacollector.get_model_vars_dataframe()\n", "g = sns.lineplot(data=gini)\n", "g.set(title=\"Gini Coefficient over Time\", ylabel=\"Gini Coefficient\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Batch Run\n", "\n", "Modelers typically won't run a model just once, but multiple times, with fixed parameters to find the overall distributions the model generates, and with varying parameters to analyze how these variables drive the model's outputs and behaviors. This is commonly referred to as parameter sweeps. Instead of needing to write nested for-loops for each model, Mesa provides a [`batch_run`](https://mesa.readthedocs.io/latest/apis/batchrunner.html) function which automates parameter sweeps and allows the model variants to run on multiple processors." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Batch run parameters\n", "\n", "We call `batch_run` with the following arguments:\n", "\n", "* `model_cls`\n", " The model class that is used for the batch run.\n", "* `parameters`\n", " A dictionary containing all the parameters of the model class and desired values to use for the batch run as key-value pairs. Each value can either be fixed ( e.g. `{\"height\": 10, \"width\": 10}`) or an iterable (e.g. `{\"n\": range(10, 500, 10)}`). `batch_run` will then generate all possible parameter combinations based on this dictionary and run the model `iterations` times for each combination.\n", "* `number_processes`\n", " If not specified, defaults to 1. Set it to `None` to use all the available processors.\n", " Note: Multiprocessing does make debugging challenging. If your parameter sweeps are resulting in unexpected errors set `number_processes=1`.\n", "* `rng`\n", " a valid value or iterable of values for seeding the random number generator in the model. Defaults to a single None value meaning the model is ran once.\n", "* `data_collection_period`\n", " The length of the period (number of steps) after which the model and agent reporters collect data. Optional. If not specified, defaults to -1, i.e. only at the end of each episode.\n", "* `max_steps`\n", " The maximum number of time steps after which the model halts. An episode does either end when `self.running` of the model class is set to `False` or when `model.steps == max_steps` is reached. Optional. If not specified, defaults to 1000.\n", "* `display_progress`\n", " Display the batch run progress. Optional. If not specified, defaults to `True`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the following example, we hold the height and width fixed, and vary the number of agents. We tell the batch runner to run 5 instantiations of the model with each number of agents, and to run each for 100 steps. \n", "\n", "We want to keep track of\n", "\n", "1. The Gini coefficient value at each time step\n", "2. The individual agent's wealth development and steps without giving money.\n", "\n", "**Important:** Since for the latter, changes at each time step might be interesting, we set `data_collection_period=1`. By default, it only collects data at the end of each episode.\n", "\n", "Note: The total number of runs is 100 (20 different populations * 5 iterations per population). " ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "import sys\n", "\n", "rng = np.random.default_rng(42)\n", "seed_values = rng.integers(0, sys.maxsize, size=(5,))" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "2a208c5972c84a9ebc77fbc828763c94", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/100 [00:00