From 5d820f08e30d3434f791e29d6d9277d6aaa89a9f Mon Sep 17 00:00:00 2001 From: David Masad Date: Thu, 9 Jul 2015 15:51:12 -0500 Subject: [PATCH] Adding SciPy2015 presentation material --- .gitignore | 59 +++ ...isoner's Dilemma Activation Schedule.ipynb | 207 +++++++++++ Scipy_Presentation/Schelling Model.ipynb | 346 ++++++++++++++++++ Scipy_Presentation/Schelling.py | 152 ++++++++ Scipy_Presentation/SchellingServer.py | 39 ++ Scipy_Presentation/pd_grid.py | 113 ++++++ 6 files changed, 916 insertions(+) create mode 100644 .gitignore create mode 100644 Scipy_Presentation/Demographic Prisoner's Dilemma Activation Schedule.ipynb create mode 100644 Scipy_Presentation/Schelling Model.ipynb create mode 100644 Scipy_Presentation/Schelling.py create mode 100644 Scipy_Presentation/SchellingServer.py create mode 100644 Scipy_Presentation/pd_grid.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe1ef1e --- /dev/null +++ b/.gitignore @@ -0,0 +1,59 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +*py~ +*swp + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +*.ipynb_checkpoints diff --git a/Scipy_Presentation/Demographic Prisoner's Dilemma Activation Schedule.ipynb b/Scipy_Presentation/Demographic Prisoner's Dilemma Activation Schedule.ipynb new file mode 100644 index 0000000..3e58539 --- /dev/null +++ b/Scipy_Presentation/Demographic Prisoner's Dilemma Activation Schedule.ipynb @@ -0,0 +1,207 @@ +{ + "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": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from pd_grid import PD_Model\n", + "\n", + "import random\n", + "import numpy as np\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.gridspec\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Helper functions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "bwr = plt.get_cmap(\"bwr\")\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": null, + "metadata": { + "collapsed": true + }, + "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)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Set the random seed\n", + "seed = 21" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Sequential Activation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "random.seed(seed)\n", + "m = PD_Model(50, 50, \"Sequential\")\n", + "run_model(m)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Random Activation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "random.seed(seed)\n", + "m = PD_Model(50, 50, \"Random\")\n", + "run_model(m)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "## Simultaneous Activation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "random.seed(seed)\n", + "m = PD_Model(50, 50, \"Simultaneous\")\n", + "run_model(m)" + ] + } + ], + "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/Scipy_Presentation/Schelling Model.ipynb b/Scipy_Presentation/Schelling Model.ipynb new file mode 100644 index 0000000..94f4f5b --- /dev/null +++ b/Scipy_Presentation/Schelling Model.ipynb @@ -0,0 +1,346 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Schelling Segregation Model\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "\n", + "from Schelling import SchellingModel" + ] + }, + { + "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": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "model = SchellingModel(20, 20, 0.85, 0.2, 3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "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": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "model_out = model.datacollector.get_model_vars_dataframe()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "model_out.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "model_out.unhappy.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "x_positions = model.datacollector.get_agent_vars_dataframe()" + ] + }, + { + "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": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from mesa.batchrunner import BatchRunner" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "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.neighbor_iter(agent.pos[0], agent.pos[1]):\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": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "parameters = {\"height\": 10, \"width\": 10, \"density\": 0.8, \"minority_pc\": 0.2, \n", + " \"homophily\": range(1,9)}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "model_reporters = {\"Segregated_Agents\": get_segregation}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "param_sweep = BatchRunner(SchellingModel, parameters, iterations=10, \n", + " max_steps=200,\n", + " model_reporters=model_reporters)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "param_sweep.run_all()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "df = param_sweep.get_model_vars_dataframe()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plt.scatter(df.homophily, df.Segregated_Agents)\n", + "plt.grid(True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "## Other Packages" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import holoviews as hv\n", + "%load_ext holoviews.ipython" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def get_color(agent):\n", + " if agent is None: \n", + " return 0\n", + " elif agent.type == 0:\n", + " return 1\n", + " elif agent.type == 1:\n", + " return 2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def get_grid(model):\n", + " width = model.grid.width\n", + " height = model.grid.height\n", + " data = np.zeros((width, height))\n", + " for x in range(width):\n", + " for y in range(height):\n", + " data[x][y] = get_color(model.grid[y][x])\n", + " return data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "hmap = hv.HoloMap()\n", + "model = SchellingModel(20, 20, 0.80, 0.2, 3)\n", + "for i in range(100):\n", + " data = get_grid(model)\n", + " hmap[i] = hv.Image(data)\n", + " model.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%opts Image plot[figure_inches=(7,7)]\n", + "hmap" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "http://nbviewer.ipython.org/gist/jlstevens/9c7835e4e21c5d844ded" + ] + }, + { + "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/Scipy_Presentation/Schelling.py b/Scipy_Presentation/Schelling.py new file mode 100644 index 0000000..c54f685 --- /dev/null +++ b/Scipy_Presentation/Schelling.py @@ -0,0 +1,152 @@ +''' +Schelling Segregation Model +========================================= + +A simple implementation of a Schelling segregation model. + +This version demonstrates the ASCII renderer. +To use, run this code from the command line, e.g. + $ ipython -i Schelling.py + +viz is the visualization wrapper around +To print the current state of the model: + viz.render() + +To advance the model by one step and print the new state: + viz.step() + +To advance the model by e.g. 10 steps and print the new state: + viz.step_forward(10) + +''' + +from __future__ import division # For Python 2.x compatibility + +import random + +from mesa import Model, Agent +from mesa.time import RandomActivation +from mesa.space import SingleGrid +from mesa.datacollection import DataCollector + +from mesa.visualization.TextVisualization import (TextData, TextGrid, + TextVisualization) + +X = 0 +Y = 1 + + +class SchellingModel(Model): + ''' + Model class for the Schelling segregation model. + ''' + + def __init__(self, height, width, density, minority_pc, homophily): + ''' + ''' + + self.height = height + self.width = width + self.density = density + self.minority_pc = minority_pc + self.homophily = homophily + + self.schedule = RandomActivation(self) + self.grid = SingleGrid(height, width, torus=True) + + self.happy = 0 + self.total_agents = 0 + self.datacollector = DataCollector( + {"unhappy": lambda m: m.total_agents - m.happy}, + # For testing purposes, agent's individual x and y + {"x": lambda a: a.pos[X], "y": lambda a: a.pos[Y]}) + + self.running = True + + # Set up agents + # We use a grid iterator that returns + # the coordinates of a cell as well as + # its contents. (coord_iter) + for cell, x, y in self.grid.coord_iter(): + if random.random() < self.density: + if random.random() < self.minority_pc: + agent_type = 1 + else: + agent_type = 0 + + agent = SchellingAgent(self.total_agents, agent_type) + self.grid.position_agent(agent, x, y) + self.schedule.add(agent) + self.total_agents += 1 + + 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() + self.datacollector.collect(self) + + if self.happy == self.total_agents: + self.running = False + + +class SchellingAgent(Agent): + ''' + Schelling segregation agent + ''' + def __init__(self, coords, 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) + ''' + self.unique_id = coords + self.pos = coords + self.type = agent_type + + def step(self, model): + similar = 0 + for neighbor in model.grid.neighbor_iter(self.pos[X], self.pos[Y]): + if neighbor.type == self.type: + similar += 1 + + # If unhappy, move: + if similar < model.homophily: + model.grid.move_to_empty(self) + else: + model.happy += 1 + + +class SchellingTextVisualization(TextVisualization): + ''' + ASCII visualization for schelling model + ''' + + def __init__(self, model): + ''' + Create new Schelling ASCII visualization. + ''' + self.model = model + + grid_viz = TextGrid(self.model.grid, self.ascii_agent) + happy_viz = TextData(self.model, 'happy') + self.elements = [grid_viz, happy_viz] + + @staticmethod + def ascii_agent(a): + ''' + Minority agents are X, Majority are O. + ''' + if a.type == 0: + return 'O' + if a.type == 1: + return '#' + +if __name__ == "__main__": + model = SchellingModel(10, 10, 0.85, 0.2, 3) + viz = SchellingTextVisualization(model) + diff --git a/Scipy_Presentation/SchellingServer.py b/Scipy_Presentation/SchellingServer.py new file mode 100644 index 0000000..4427f79 --- /dev/null +++ b/Scipy_Presentation/SchellingServer.py @@ -0,0 +1,39 @@ +from Schelling import SchellingModel +from mesa.visualization.ModularVisualization import ModularServer +from mesa.visualization.modules import CanvasGrid, ChartModule, TextElement + + +class HappyElement(TextElement): + ''' + Display a text count of how many happy agents there are. + ''' + def __init__(self): + pass + + def render(self, model): + return "Happy agents: " + str(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"] = "Red" + else: + portrayal["Color"] = "Blue" + return portrayal + +happy_element = HappyElement() +canvas_element = CanvasGrid(schelling_draw, 20, 20, 500, 500) +happy_chart = ChartModule([{"Label": "unhappy", "Color": "Black"}]) +server = ModularServer(SchellingModel, + [canvas_element, happy_element, happy_chart], + "Schelling", 20, 20, 0.8, 0.2, 4) +server.verbose = False +server.port = 8889 +server.launch() diff --git a/Scipy_Presentation/pd_grid.py b/Scipy_Presentation/pd_grid.py new file mode 100644 index 0000000..ca57e63 --- /dev/null +++ b/Scipy_Presentation/pd_grid.py @@ -0,0 +1,113 @@ +''' +Spatial Demographic Prisoner's Dilemma +========================================= + +In this model, agents are situated on a grid and play the Prisoner's Dilemma +with all of their neighbors simultaneously; an agent can either be Cooperating +or Defecting. +''' + +from mesa import Agent, Model +from mesa.datacollection import DataCollector +from mesa.time import BaseScheduler, RandomActivation, SimultaneousActivation +from mesa.space import SingleGrid +import random + + +class PD_Agent(Agent): + + def __init__(self, pos, starting_move=None): + ''' + Create a new Prisoner's Dilemma agent. + + Args: + pos: (x, y) tuple of the agent's position. + starting_move: If provided, determines the agent's initial state: + C(ooperating) or D(efecting). Otherwise, random. + ''' + self.pos = pos + self.score = 0 + if starting_move: + self.move = starting_move + else: + self.move = random.choice(["C", "D"]) + self.next_move = None + + def step(self, model): + ''' + Get the neighbors' moves, and change own move accordingly. + ''' + x, y = self.pos + neighbors = model.grid.get_neighbors(x, y, True, include_center=True) + best_neighbor = max(neighbors, key=lambda a: a.score) + self.next_move = best_neighbor.move + + if model.schedule_type != "Simultaneous": + self.advance(model) + + def advance(self, model): + self.move = self.next_move + self.score += self.increment_score(model) + + def increment_score(self, model): + x, y = self.pos + neighbors = model.grid.get_neighbors(self.pos[0], self.pos[1], True) + if model.schedule_type == "Simultaneous": + moves = [neighbor.next_move for neighbor in neighbors] + else: + moves = [neighbor.move for neighbor in neighbors] + return sum(model.payoff[(self.move, move)] for move in moves) + + +class PD_Model(Model): + ''' + Model class for iterated, spatial prisoner's dilemma model. + ''' + + schedule_types = {"Sequential": BaseScheduler, + "Random": RandomActivation, + "Simultaneous": 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, height, width, schedule_type, payoffs=None): + ''' + Create a new Spatial Prisoners' Dilemma Model. + + Args: + height, width: 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.running = True + self.grid = SingleGrid(height, width, 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 = PD_Agent((x, y)) + self.grid.place_agent(agent, (x, y)) + self.schedule.add(agent) + + self.datacollector = DataCollector({"Cooperating_Agents": + lambda m: len([a for a in m.schedule.agents if a.move == "C"])}) + + def step(self): + self.datacollector.collect(self) + self.schedule.step() + + def run(self, n): + ''' + Run the model for a certain number of steps. + ''' + for _ in range(n): + self.step()