From 3deb576ef1bd6a0e028e5ffadc425a32c0d5a425 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 22 May 2017 20:09:46 +0000 Subject: [PATCH] Refactor tutorials to use properly named model, files This also clarifies which snippets go in which files, and some other minor cleanup of repetitive bits. --- docs/best-practices.rst | 5 +- docs/tutorials/adv_tutorial.rst | 85 ++++++++++------- docs/tutorials/intro_tutorial.rst | 153 ++++++++++++------------------ 3 files changed, 115 insertions(+), 128 deletions(-) diff --git a/docs/best-practices.rst b/docs/best-practices.rst index c6f19cabb59..34ade0cdb3b 100644 --- a/docs/best-practices.rst +++ b/docs/best-practices.rst @@ -17,7 +17,10 @@ underscores, such as ``thunder_cats``. Within that directory: * ``model.py`` should contain the model class. If the file gets large, it may make sense to move the complex bits into other files, but this is the first - place readers will look to figure out how the model lworks. + place readers will look to figure out how the model works. + +* ``server.py`` should contain the visualization support, including the server + class. * ``run.py`` is a Python script that will run the model when invoked as ``python run.py``. diff --git a/docs/tutorials/adv_tutorial.rst b/docs/tutorials/adv_tutorial.rst index f12c0dae3a5..2f8fc34b4e4 100644 --- a/docs/tutorials/adv_tutorial.rst +++ b/docs/tutorials/adv_tutorial.rst @@ -34,20 +34,19 @@ Grid Visualization ^^^^^^^^^^^^^^^^^^ 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. +moving around the grid. For this, you will need to create a server that +will support visualization in a web browser. Set that up in ``server.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. .. code:: python + # server.py from mesa.visualization.modules import CanvasGrid from mesa.visualization.ModularVisualization import ModularServer - - # If MoneyModel.py is where your code is: - # from MoneyModel import MoneyModel + from model import MoneyModel ``CanvasGrid`` works by looping over every cell in a grid, and generating a portrayal for every agent it finds. A portrayal is a @@ -59,6 +58,7 @@ fills half of each cell. .. code:: python + # server.py def agent_portrayal(agent): portrayal = {"Shape": "circle", "Color": "red", @@ -88,15 +88,23 @@ following arguments: 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. .. code:: python + # server.py server = ModularServer(MoneyModel, [grid], "Money Model", 100, 10, 10) + +Finally, when you're ready to run the visualization, use the server's +``launch()`` method, in ``run.py``. In this arrangmenet, ``run.py`` is +very short! + +.. code:: python + + # run.py + from server import server server.port = 8521 # The default server.launch() @@ -104,10 +112,10 @@ The full code should now look like: .. code:: python - from MoneyModel import * + # server.py from mesa.visualization.modules import CanvasGrid from mesa.visualization.ModularVisualization import ModularServer - + from model import MoneyModel def agent_portrayal(agent): portrayal = {"Shape": "circle", @@ -122,8 +130,6 @@ The full code should now look like: [grid], "Money Model", 100, 10, 10) - server.port = 8521 # The default - server.launch() Now run this file; this should launch the interactive visualization server and open your web browser automatically. (If the browser doesn't @@ -171,6 +177,7 @@ to change the portrayal based on the agent properties. .. code:: python + # server.py def agent_portrayal(agent): portrayal = {"Shape": "circle", "Filled": "true", @@ -205,6 +212,7 @@ provides. .. code:: python + # server.py from mesa.visualization.modules import ChartModule The basic chart pulls data from the model's DataCollector, and draws it @@ -221,6 +229,7 @@ chart will appear underneath the grid. .. code:: python + # server.py chart = ChartModule([{"Label": "Gini", "Color": "Black"}], data_collector_name='datacollector') @@ -297,6 +306,7 @@ the class itself: .. code:: javascript + // HistogramModule.js var HistogramModule = function(bins, canvas_width, canvas_height) { // The actual code will go here. }; @@ -314,6 +324,7 @@ context, which is required for doing anything with it. .. code:: javascript + // HistogramModule.js var HistogramModule = function(bins, canvas_width, canvas_height) { // Create the tag: var canvas_tag = ") - - -.. image:: intro_tutorial_files/intro_tutorial_19_1.png - - -If you are running from a text editor or IDE, you'll also need to add -this line, to make the graph appear. - -.. code:: python - - plt.show() - You'll probably see something like the distribution shown below. Yours will almost certainly look at least slightly different, since each run of the model is random, after all. +.. image:: intro_tutorial_files/intro_tutorial_19_1.png + To get a better idea of how a model behaves, we can create multiple model runs and see the distribution that emerges from all of them. We can do this with a nested for loop: .. code:: python + # run.py all_wealth = [] for j in range(100): # Run the model @@ -332,8 +323,6 @@ can do this with a nested for loop: plt.hist(all_wealth, bins=range(max(all_wealth)+1)) - - .. parsed-literal:: (array([ 437., 303., 144., 75., 28., 9., 4.]), @@ -341,8 +330,6 @@ can do this with a nested for loop: ) - - .. image:: intro_tutorial_files/intro_tutorial_21_1.png @@ -384,6 +371,7 @@ to share a cell, we use ``MultiGrid``. .. code:: python + # model.py from mesa.space import MultiGrid We instantiate a grid with width and height parameters, and a boolean as @@ -395,6 +383,7 @@ coordinates to place the agent. .. code:: python + # model.py class MoneyModel(Model): """A model with some number of agents.""" def __init__(self, N, width, height): @@ -446,6 +435,7 @@ With that in mind, the agent's ``move`` method looks like this: .. code:: python + # model.py class MoneyAgent(Agent): #... def move(self): @@ -464,6 +454,7 @@ single tuple if we only care about one cell. .. code:: python + # model.py class MoneyAgent(Agent): #... def give_money(self): @@ -477,6 +468,7 @@ And with those two methods, the agent's ``step`` method becomes: .. code:: python + # model.py class MoneyAgent(Agent): # ... def step(self): @@ -488,6 +480,11 @@ Now, putting that all together should look like this: .. code:: python + # model.py + from mesa.space import MultiGrid + from mesa import Agent, Model + from mesa.time import RandomActivation + class MoneyModel(Model): """A model with some number of agents.""" def __init__(self, N, width, height): @@ -537,6 +534,7 @@ steps. .. code:: python + # run.py model = MoneyModel(50, 10, 10) for i in range(20): model.step() @@ -549,6 +547,7 @@ grid, giving us each cell's coordinates and contents in turn. .. code:: python + # run.py import numpy as np agent_counts = np.zeros((model.grid.width, model.grid.height)) @@ -560,7 +559,7 @@ grid, giving us each cell's coordinates and contents in turn. plt.colorbar() # If running from a text editor or IDE, remember you'll need the following: - # plt.show() + plt.show() @@ -611,8 +610,12 @@ measure of wealth inequality. .. code:: python + # model.py from mesa.datacollection import DataCollector + class MoneyAgent(Agent): + # ... + def compute_gini(model): agent_wealths = [agent.wealth for agent in model.schedule.agents] x = sorted(agent_wealths) @@ -620,32 +623,6 @@ measure of wealth inequality. B = sum( xi * (N-i) for i,xi in enumerate(x) ) / (N*sum(x)) return (1 + (1/N) - 2*B) - class MoneyAgent(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 = 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 = random.choice(cellmates) - other.wealth += 1 - self.wealth -= 1 - - def step(self): - self.move() - if self.wealth > 0: - self.give_money() - class MoneyModel(Model): """A model with some number of agents.""" def __init__(self, N, width, height): @@ -681,6 +658,7 @@ interactive analysis. .. code:: python + # run.py model = MoneyModel(50, 10, 10) for i in range(100): model.step() @@ -689,6 +667,7 @@ To get the series of Gini coefficients as a pandas DataFrame: .. code:: python + # run.py gini = model.datacollector.get_model_vars_dataframe() gini.plot() @@ -811,41 +790,19 @@ drive the model's outputs and behaviors. Instead of needing to write nested for-loops for each model, Mesa provides a BatchRunner class which automates it for you. -.. code:: python - - from mesa.batchrunner import BatchRunner - -The BatchRunner also requires an additional variable running for the +The BatchRunner also requires an additional instance attribute ``running`` for the MoneyModel class. This variable enables conditional shut off of the model once a condition is met. In this example it will be set as True -indefinitely. +indefinitely in ``__init__``. .. code:: python + # model.py class MoneyModel(Model): """A model with some number of agents.""" def __init__(self, N, width, height): - self.num_agents = N - self.grid = MultiGrid(width, height, True) - self.schedule = RandomActivation(self) self.running = True - - # 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 = random.randrange(self.grid.width) - y = random.randrange(self.grid.height) - self.grid.place_agent(a, (x, y)) - - self.datacollector = DataCollector( - model_reporters={"Gini": compute_gini}, - agent_reporters={"Wealth": lambda a: a.wealth}) - - def step(self): - self.datacollector.collect(self) - self.schedule.step() + # ... We instantiate a BatchRunner with a model class to run, and a dictionary mapping parameters to values for them to take. If any of these @@ -867,6 +824,9 @@ Now, we can set up and run the BatchRunner: .. code:: python + # run.py + from mesa.batchrunner import BatchRunner + parameters = {"width": 10, "height": 10, "N": range(10, 500, 10)} @@ -904,6 +864,15 @@ Notice that each row is a model run, and gives us the parameter values associated with that run. We can use this data to view a scatter-plot comparing the number of agents to the final Gini. +Model Best Practices +~~~~~~~~~~~~~~~~~~~~ + +If you would like to share your model with other people, or to remind yourself +of its details when you return to it, you will want to add a few extra bits. + +The :doc:`../best-practices` document describes the recommended layout for +models, including a README and ``requirements.txt``. + Happy Modeling! ~~~~~~~~~~~~~~~