diff --git a/README.md b/README.md index 6364996d..542b537b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # Mesa Examples +## Core Mesa examples +The core Mesa examples are available at the main Mesa repository: https://github.com/projectmesa/mesa/tree/main/examples -This repository contains examples that work with Mesa and illustrate different features of Mesa. For more information on each model, see its own Readme and documentation. +Those core examples are fully tested, updated and guaranteed to work with the Mesa release that they are included with. They are also included in the Mesa package, so you can access them directly from your Python environment. + +## Mesa user examples +This repository contains user examples and showcases that illustrate different features of Mesa. For more information on each model, see its own Readme and documentation. - Mesa examples that work on the Mesa and Mesa-Geo main development branches are available here on the [`main`](https://github.com/projectmesa/mesa-examples) branch. - Mesa examples that work with Mesa 2.x releases and Mesa-Geo 0.8.x releases are available here on the [`mesa-2.x`](https://github.com/projectmesa/mesa-examples/tree/mesa-2.x) branch. @@ -39,18 +44,10 @@ Table of Contents A highly abstracted, simplified model of an economy, with only one type of agent and a single bank representing all banks in an economy. -### [Boltzmann Wealth Model](https://github.com/projectmesa/mesa-examples/tree/main/examples/boltzmann_wealth_model) - -Completed code to go along with the [tutorial](https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html) on making a simple model of how a highly-skewed wealth distribution can emerge from simple rules. - ### [Color Patches Model](https://github.com/projectmesa/mesa-examples/tree/main/examples/color_patches) A cellular automaton model where agents opinions are influenced by that of their neighbors. As the model evolves, color patches representing the prevailing opinion in a given area expand, contract, and sometimes disappear. -### [Conway's Game Of "Life" Model](https://github.com/projectmesa/mesa-examples/tree/main/examples/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. - ### [Conway's Game Of "Life" Model (Fast)](https://github.com/projectmesa/mesa-examples/tree/main/examples/conways_game_of_life_fast) A very fast performance optimized version of Conway's Game of Life using the Mesa [`PropertyLayer`](https://github.com/projectmesa/mesa/pull/1898). About 100x as fast as the regular versions, but limited visualisation (for [now](https://github.com/projectmesa/mesa/issues/2138)). @@ -59,14 +56,6 @@ A very fast performance optimized version of Conway's Game of Life using the Mes Conway's game of life on a hexagonal grid. -### [Demographic Prisoner's Dilemma on a Grid](https://github.com/projectmesa/mesa-examples/tree/main/examples/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. - -### [Epstein Civil Violence Model](https://github.com/projectmesa/mesa-examples/tree/main/examples/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. - ### [Forest Fire Model](https://github.com/projectmesa/mesa-examples/tree/main/examples/forest_fire) Simple cellular automata of a fire spreading through a forest of cells on a grid, based on the NetLogo [Fire](http://ccl.northwestern.edu/netlogo/models/Fire) model. @@ -75,28 +64,10 @@ Simple cellular automata of a fire spreading through a forest of cells on a grid This project is an agent-based model implemented using the Mesa framework in Python. It simulates market dynamics based on Hotelling's Law, exploring the behavior of stores in a competitive market environment. Stores adjust their prices and locations if it's increases market share to maximize revenue, providing insights into the effects of competition and customer behavior on market outcomes. -### [Schelling Segregation Model](https://github.com/projectmesa/mesa-examples/tree/main/examples/schelling) - -Mesa implementation of the classic [Schelling segregation](http://nifty.stanford.edu/2014/mccown-schelling-model-segregation/) model. - -### [Sugarscape Constant Growback Model](https://github.com/projectmesa/mesa-examples/tree/main/examples/sugarscape_cg) - -This is Epstein & Axtell's Sugarscape Constant Growback model, with a detailed description in the Chapter Two of *Growing Artificial Societies: Social Science from the Bottom Up*. It is based on the Netlogo -[Sugarscape 2 Constant Growback](http://ccl.northwestern.edu/netlogo/models/Sugarscape2ConstantGrowback) model. - -### [Sugarscape Constant Growback Model with Traders](https://github.com/projectmesa/mesa-examples/tree/main/examples/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 an emergent price equilibrium can happen via a decentralized dynamics. - -### [Wolf-Sheep Predation Model](https://github.com/projectmesa/mesa-examples/tree/main/examples/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. ## Continuous Space Examples +_No user examples available yet._ -### [Boids Flockers Model](https://github.com/projectmesa/mesa-examples/tree/main/examples/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. ## Network Examples @@ -104,20 +75,12 @@ Implementation of an ecological model of predation and reproduction, based on th This is the same [Boltzmann Wealth](https://github.com/projectmesa/mesa-examples/tree/main/examples/boltzmann_wealth_model) Model, but with a network grid implementation. -### [Virus on a Network Model](https://github.com/projectmesa/mesa-examples/tree/main/examples/virus_on_network) - -This model is based on the NetLogo [Virus on a Network](https://ccl.northwestern.edu/netlogo/models/VirusonaNetwork) model. - ### [Ant System for Traveling Salesman Problem](https://github.com/projectmesa/mesa-examples/tree/main/examples/aco_tsp) This is based on Dorigo's Ant System "Swarm Intelligence" algorithm for generating solutions for the Traveling Salesman Problem. ## Visualization Examples -### [Boltzmann Wealth Model)](https://github.com/projectmesa/mesa-examples/tree/main/examples/boltzmann_wealth_model) - -Boltzmann Wealth model with an optional visualization using Streamlit. - ### [Charts Example](https://github.com/projectmesa/mesa-examples/tree/main/examples/charts) A modified version of the [Bank Reserves](https://github.com/projectmesa/mesa-examples/tree/main/examples/bank_reserves) example made to provide examples of Mesa's charting tools. diff --git a/examples/boid_flockers/Flocker Test.ipynb b/examples/boid_flockers/Flocker Test.ipynb deleted file mode 100644 index c757f3a8..00000000 --- a/examples/boid_flockers/Flocker Test.ipynb +++ /dev/null @@ -1,113 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "from boid_flockers.model import BoidFlockers\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.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/boid_flockers/Readme.md b/examples/boid_flockers/Readme.md deleted file mode 100644 index d1f4a987..00000000 --- a/examples/boid_flockers/Readme.md +++ /dev/null @@ -1,47 +0,0 @@ -# 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 - -* 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 run.py -``` - -* Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run. - -## Files - -* [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. - -## Further Reading - -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/boid_flockers/app.py b/examples/boid_flockers/app.py deleted file mode 100644 index 205cb218..00000000 --- a/examples/boid_flockers/app.py +++ /dev/null @@ -1,26 +0,0 @@ -from boid_flockers.model import BoidFlockers -from mesa.visualization import SolaraViz, make_space_matplotlib - - -def boid_draw(agent): - return {"color": "tab:red"} - - -model_params = { - "population": 100, - "width": 100, - "height": 100, - "speed": 5, - "vision": 10, - "separation": 2, -} - -model = BoidFlockers(100, 100, 100, 5, 10, 2) - -page = SolaraViz( - model, - [make_space_matplotlib(agent_portrayal=boid_draw)], - model_params=model_params, - name="BoidFlockers", -) -page # noqa diff --git a/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py b/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py deleted file mode 100644 index ec670d7a..00000000 --- a/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py +++ /dev/null @@ -1,29 +0,0 @@ -import mesa - - -class SimpleCanvas(mesa.visualization.VisualizationElement): - local_includes = ["boid_flockers/simple_continuous_canvas.js"] - - def __init__(self, portrayal_method=None, 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 = ( - f"new Simple_Continuous_Module({self.canvas_width}, {self.canvas_height})" - ) - self.js_code = "elements.push(" + new_element + ");" - - def render(self, model): - space_state = [] - 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) - 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/boid_flockers/boid_flockers/model.py b/examples/boid_flockers/boid_flockers/model.py deleted file mode 100644 index ae3099f3..00000000 --- a/examples/boid_flockers/boid_flockers/model.py +++ /dev/null @@ -1,146 +0,0 @@ -""" -Flockers -============================================================= -A Mesa implementation of Craig Reynolds's Boids flocker model. -Uses numpy arrays to represent vectors. -""" - -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 direction (a vector) - define their movement. Separation is their desired minimum distance from - any other Boid. - """ - - def __init__( - self, - model, - speed, - direction, - vision, - separation, - cohere=0.03, - separate=0.015, - match=0.05, - ): - """ - Create a new Boid flocker agent. - - Args: - 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__(model) - 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 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) - 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) - - -class BoidFlockers(mesa.Model): - """ - Flocker model class. Handles agent creation, placement and scheduling. - """ - - def __init__( - self, - seed=None, - population=100, - width=100, - height=100, - vision=10, - speed=1, - separation=1, - cohere=0.03, - separate=0.015, - match=0.05, - ): - """ - 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. - """ - super().__init__(seed=seed) - self.population = population - self.vision = vision - self.speed = speed - self.separation = separation - - self.space = mesa.space.ContinuousSpace(width, height, True) - self.factors = {"cohere": cohere, "separate": separate, "match": match} - self.make_agents() - - def make_agents(self): - """ - Create self.population agents, with random positions and starting headings. - """ - 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( - model=self, - speed=self.speed, - direction=direction, - vision=self.vision, - separation=self.separation, - **self.factors, - ) - self.space.place_agent(boid, pos) - - def step(self): - self.agents.shuffle_do("step") diff --git a/examples/boid_flockers/boid_flockers/server.py b/examples/boid_flockers/boid_flockers/server.py deleted file mode 100644 index 190c6533..00000000 --- a/examples/boid_flockers/boid_flockers/server.py +++ /dev/null @@ -1,64 +0,0 @@ -import mesa - -from .model import BoidFlockers -from .SimpleContinuousModule import SimpleCanvas - - -def boid_draw(agent): - 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( - portrayal_method=boid_draw, canvas_height=500, canvas_width=500 -) -model_params = { - "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": 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( - model_cls=BoidFlockers, - visualization_elements=[boid_canvas], - name="Boid Flocking Model", - model_params=model_params, -) diff --git a/examples/boid_flockers/boid_flockers/simple_continuous_canvas.js b/examples/boid_flockers/boid_flockers/simple_continuous_canvas.js deleted file mode 100644 index 812cadce..00000000 --- a/examples/boid_flockers/boid_flockers/simple_continuous_canvas.js +++ /dev/null @@ -1,78 +0,0 @@ -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/boid_flockers/requirements.txt b/examples/boid_flockers/requirements.txt deleted file mode 100644 index da2b9972..00000000 --- a/examples/boid_flockers/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -jupyter -matplotlib -mesa~=2.0 diff --git a/examples/boid_flockers/run.py b/examples/boid_flockers/run.py deleted file mode 100644 index 0d9ca624..00000000 --- a/examples/boid_flockers/run.py +++ /dev/null @@ -1,3 +0,0 @@ -from boid_flockers.server import server - -server.launch(open_browser=True) diff --git a/examples/boltzmann_wealth_model/Readme.md b/examples/boltzmann_wealth_model/Readme.md deleted file mode 100644 index 8f7f7c81..00000000 --- a/examples/boltzmann_wealth_model/Readme.md +++ /dev/null @@ -1,60 +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. - -## Optional - -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 - -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/boltzmann_wealth_model/app.py b/examples/boltzmann_wealth_model/app.py deleted file mode 100644 index 199b3a1a..00000000 --- a/examples/boltzmann_wealth_model/app.py +++ /dev/null @@ -1,65 +0,0 @@ -from mesa.visualization import ( - SolaraViz, - make_plot_measure, - make_space_matplotlib, -) -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, -} - -# Create initial model instance -model1 = BoltzmannWealthModel(50, 10, 10) - -# 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") - -# 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 - - -# In a notebook environment, we can also display the visualization elements directly -# SpaceGraph(model1) -# GiniPlot(model1) - -# 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/boltzmann_wealth_model/model.py b/examples/boltzmann_wealth_model/model.py deleted file mode 100644 index ac091a6c..00000000 --- a/examples/boltzmann_wealth_model/model.py +++ /dev/null @@ -1,77 +0,0 @@ -import mesa - - -def compute_gini(model): - 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)) - 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): - super().__init__() - self.num_agents = N - self.grid = mesa.space.MultiGrid(width, height, True) - - self.datacollector = mesa.DataCollector( - model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} - ) - # Create agents - for _ in range(self.num_agents): - a = MoneyAgent(self) - - # 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.agents.shuffle_do("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, model): - super().__init__(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/boltzmann_wealth_model/requirements.txt b/examples/boltzmann_wealth_model/requirements.txt deleted file mode 100644 index 95044bed..00000000 --- a/examples/boltzmann_wealth_model/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -mesa[viz]>=3.0.0b0 diff --git a/examples/boltzmann_wealth_model/st_app.py b/examples/boltzmann_wealth_model/st_app.py deleted file mode 100644 index 665f8067..00000000 --- a/examples/boltzmann_wealth_model/st_app.py +++ /dev/null @@ -1,113 +0,0 @@ -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']) diff --git a/examples/conways_game_of_life/Readme.md b/examples/conways_game_of_life/Readme.md deleted file mode 100644 index 85b591aa..00000000 --- a/examples/conways_game_of_life/Readme.md +++ /dev/null @@ -1,37 +0,0 @@ -# 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 - -* ``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/conways_game_of_life/app.py b/examples/conways_game_of_life/app.py deleted file mode 100644 index 5be8327a..00000000 --- a/examples/conways_game_of_life/app.py +++ /dev/null @@ -1,71 +0,0 @@ -import time - -import altair as alt -import numpy as np -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) -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") diff --git a/examples/conways_game_of_life/conways_game_of_life/cell.py b/examples/conways_game_of_life/conways_game_of_life/cell.py deleted file mode 100644 index 35c8d3f2..00000000 --- a/examples/conways_game_of_life/conways_game_of_life/cell.py +++ /dev/null @@ -1,53 +0,0 @@ -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__(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 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 - 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 assume_state(self): - """ - Set the state to the new computed state -- computed in step(). - """ - self.state = self._nextState diff --git a/examples/conways_game_of_life/conways_game_of_life/model.py b/examples/conways_game_of_life/conways_game_of_life/model.py deleted file mode 100644 index 76d9ca9f..00000000 --- a/examples/conways_game_of_life/conways_game_of_life/model.py +++ /dev/null @@ -1,37 +0,0 @@ -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. - """ - super().__init__() - # Use a simple grid, where edges wrap around. - self.grid = mesa.space.SingleGrid(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.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 - """ - self.agents.do("determine_state") - self.agents.do("assume_state") diff --git a/examples/conways_game_of_life/conways_game_of_life/portrayal.py b/examples/conways_game_of_life/conways_game_of_life/portrayal.py deleted file mode 100644 index 4f68468d..00000000 --- a/examples/conways_game_of_life/conways_game_of_life/portrayal.py +++ /dev/null @@ -1,19 +0,0 @@ -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/conways_game_of_life/conways_game_of_life/server.py b/examples/conways_game_of_life/conways_game_of_life/server.py deleted file mode 100644 index 6da932f3..00000000 --- a/examples/conways_game_of_life/conways_game_of_life/server.py +++ /dev/null @@ -1,11 +0,0 @@ -import mesa - -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) - -server = mesa.visualization.ModularServer( - ConwaysGameOfLife, [canvas_element], "Game of Life", {"height": 50, "width": 50} -) diff --git a/examples/conways_game_of_life/requirements.txt b/examples/conways_game_of_life/requirements.txt deleted file mode 100644 index ecd07eaf..00000000 --- a/examples/conways_game_of_life/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -mesa~=2.0 \ No newline at end of file diff --git a/examples/conways_game_of_life/run.py b/examples/conways_game_of_life/run.py deleted file mode 100644 index 70958165..00000000 --- a/examples/conways_game_of_life/run.py +++ /dev/null @@ -1,3 +0,0 @@ -from conways_game_of_life.server import server - -server.launch(open_browser=True) diff --git a/examples/epstein_civil_violence/Epstein Civil Violence.ipynb b/examples/epstein_civil_violence/Epstein Civil Violence.ipynb deleted file mode 100644 index e5d4d9a2..00000000 --- a/examples/epstein_civil_violence/Epstein Civil Violence.ipynb +++ /dev/null @@ -1,116 +0,0 @@ -{ - "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": [ - "%matplotlib inline\n", - "\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/epstein_civil_violence/Readme.md b/examples/epstein_civil_violence/Readme.md deleted file mode 100644 index 2e715b33..00000000 --- a/examples/epstein_civil_violence/Readme.md +++ /dev/null @@ -1,33 +0,0 @@ -# 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/epstein_civil_violence/epstein_civil_violence/__init__.py b/examples/epstein_civil_violence/epstein_civil_violence/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/epstein_civil_violence/epstein_civil_violence/agent.py b/examples/epstein_civil_violence/epstein_civil_violence/agent.py deleted file mode 100644 index edd1d1eb..00000000 --- a/examples/epstein_civil_violence/epstein_civil_violence/agent.py +++ /dev/null @@ -1,158 +0,0 @@ -import math - -import mesa - - -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: - 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, - model, - hardship, - regime_legitimacy, - risk_aversion, - threshold, - vision, - ): - """ - Create a new Citizen. - Args: - 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 - 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__(model) - 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.grievance - net_risk > self.threshold: - self.condition = "Active" - else: - self.condition = "Quiescent" - - 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 isinstance(c, Cop)]) - actives_in_vision = 1.0 # citizen counts herself - for c in self.neighbors: - if ( - isinstance(c, 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(EpsteinAgent): - """ - 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, model, vision): - """ - Create a new Cop. - Args: - 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__(model) - 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 ( - isinstance(agent, 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 - arrestee.condition = "Quiescent" - if self.model.movement and self.empty_neighbors: - new_pos = self.random.choice(self.empty_neighbors) - self.move_to(new_pos) diff --git a/examples/epstein_civil_violence/epstein_civil_violence/model.py b/examples/epstein_civil_violence/epstein_civil_violence/model.py deleted file mode 100644 index 8bf06bf1..00000000 --- a/examples/epstein_civil_violence/epstein_civil_violence/model.py +++ /dev/null @@ -1,146 +0,0 @@ -import mesa - -from .agent import Citizen, Cop - - -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.grid = mesa.experimental.cell_space.OrthogonalMooreGrid( - (width, height), capacity=1, 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.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), - } - self.datacollector = mesa.DataCollector( - model_reporters=model_reporters, agent_reporters=agent_reporters - ) - if self.cop_density + self.citizen_density > 1: - raise ValueError("Cop density + citizen density must be less than 1") - - for cell in self.grid.all_cells: - if self.random.random() < self.cop_density: - cop = Cop(self, vision=self.cop_vision) - cop.move_to(cell) - - elif self.random.random() < (self.cop_density + self.citizen_density): - citizen = Citizen( - self, - hardship=self.random.random(), - regime_legitimacy=self.legitimacy, - risk_aversion=self.random.random(), - threshold=self.active_threshold, - vision=self.citizen_vision, - ) - citizen.move_to(cell) - - self.running = True - self.datacollector.collect(self) - - def step(self): - """ - Advance the model by one step and collect data. - """ - self.agents.shuffle_do("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. - """ - 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. - """ - 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. - """ - return len(model.agents_by_type[Cop]) diff --git a/examples/epstein_civil_violence/epstein_civil_violence/portrayal.py b/examples/epstein_civil_violence/epstein_civil_violence/portrayal.py deleted file mode 100644 index 80134adc..00000000 --- a/examples/epstein_civil_violence/epstein_civil_violence/portrayal.py +++ /dev/null @@ -1,33 +0,0 @@ -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/epstein_civil_violence/epstein_civil_violence/server.py b/examples/epstein_civil_violence/epstein_civil_violence/server.py deleted file mode 100644 index 560b94e5..00000000 --- a/examples/epstein_civil_violence/epstein_civil_violence/server.py +++ /dev/null @@ -1,81 +0,0 @@ -import mesa - -from .agent import Citizen, Cop -from .model import EpsteinCivilViolence - -COP_COLOR = "#000000" -AGENT_QUIET_COLOR = "#648FFF" -AGENT_REBEL_COLOR = "#FE6100" -JAIL_COLOR = "#808080" -JAIL_SHAPE = "rect" - - -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 - shape = JAIL_SHAPE if agent.jail_sentence else "circle" - portrayal["Color"] = color - 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.9 - portrayal["Layer"] = 1 - - return portrayal - - -model_params = { - "height": 40, - "width": 40, - "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, - chart, - ], - "Epstein Civil Violence", - model_params, -) diff --git a/examples/epstein_civil_violence/requirements.txt b/examples/epstein_civil_violence/requirements.txt deleted file mode 100644 index da2b9972..00000000 --- a/examples/epstein_civil_violence/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -jupyter -matplotlib -mesa~=2.0 diff --git a/examples/epstein_civil_violence/run.py b/examples/epstein_civil_violence/run.py deleted file mode 100644 index a4b62c85..00000000 --- a/examples/epstein_civil_violence/run.py +++ /dev/null @@ -1,3 +0,0 @@ -from epstein_civil_violence.server import server - -server.launch(open_browser=True) diff --git a/examples/pd_grid/analysis.ipynb b/examples/pd_grid/analysis.ipynb deleted file mode 100644 index e3f52170..00000000 --- a/examples/pd_grid/analysis.ipynb +++ /dev/null @@ -1,228 +0,0 @@ -{ - "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": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "from pd_grid.model import PdGrid\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(f\"Steps: {model.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+www47cNdd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\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/pd_grid/pd_grid/__init__.py b/examples/pd_grid/pd_grid/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/pd_grid/pd_grid/agent.py b/examples/pd_grid/pd_grid/agent.py deleted file mode 100644 index 4890b749..00000000 --- a/examples/pd_grid/pd_grid/agent.py +++ /dev/null @@ -1,50 +0,0 @@ -from mesa.experimental.cell_space import CellAgent - - -class PDAgent(CellAgent): - """Agent member of the iterated, spatial prisoner's dilemma model.""" - - def __init__(self, model, starting_move=None): - """ - Create a new Prisoner's Dilemma agent. - - Args: - model: model instance - starting_move: If provided, determines the agent's initial state: - C(ooperating) or D(efecting). Otherwise, random. - """ - super().__init__(model) - self.score = 0 - if starting_move: - self.move = starting_move - else: - self.move = self.random.choice(["C", "D"]) - self.next_move = None - - @property - 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 = [*list(self.cell.neighborhood.agents), self] - best_neighbor = max(neighbors, key=lambda a: a.score) - self.next_move = best_neighbor.move - - if self.model.activation_order != "Simultaneous": - self.advance() - - def advance(self): - self.move = self.next_move - self.score += self.increment_score() - - def increment_score(self): - neighbors = self.cell.neighborhood.agents - if self.model.activation_order == "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/pd_grid/pd_grid/model.py b/examples/pd_grid/pd_grid/model.py deleted file mode 100644 index 38ef5f5b..00000000 --- a/examples/pd_grid/pd_grid/model.py +++ /dev/null @@ -1,72 +0,0 @@ -import mesa -from mesa.experimental.cell_space import OrthogonalMooreGrid - -from .agent import PDAgent - - -class PdGrid(mesa.Model): - """Model class for iterated, spatial prisoner's dilemma model.""" - - activation_regimes = ["Sequential", "Random", "Simultaneous"] - - # 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, 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. - activation_order: Can be "Sequential", "Random", or "Simultaneous". - Determines the agent activation regime. - payoffs: (optional) Dictionary of (move, neighbor_move) payoffs. - """ - super().__init__(seed=seed) - self.activation_order = activation_order - 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) - agent.cell = self.grid[(x, y)] - - self.datacollector = mesa.DataCollector( - { - "Cooperating_Agents": lambda m: len( - [a for a in m.agents if a.move == "C"] - ) - } - ) - - self.running = True - self.datacollector.collect(self) - - def step(self): - # 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): - """Run the model for n steps.""" - for _ in range(n): - self.step() diff --git a/examples/pd_grid/pd_grid/portrayal.py b/examples/pd_grid/pd_grid/portrayal.py deleted file mode 100644 index a7df44a4..00000000 --- a/examples/pd_grid/pd_grid/portrayal.py +++ /dev/null @@ -1,19 +0,0 @@ -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/pd_grid/pd_grid/server.py b/examples/pd_grid/pd_grid/server.py deleted file mode 100644 index 57785acc..00000000 --- a/examples/pd_grid/pd_grid/server.py +++ /dev/null @@ -1,21 +0,0 @@ -import mesa - -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) - -model_params = { - "height": 50, - "width": 50, - "activation_order": mesa.visualization.Choice( - "Activation regime", - value="Random", - choices=PdGrid.activation_regimes, - ), -} - -server = mesa.visualization.ModularServer( - PdGrid, [canvas_element], "Prisoner's Dilemma", model_params -) diff --git a/examples/pd_grid/readme.md b/examples/pd_grid/readme.md deleted file mode 100644 index 51b91fd4..00000000 --- a/examples/pd_grid/readme.md +++ /dev/null @@ -1,42 +0,0 @@ -# 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 ``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 - -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/pd_grid/requirements.txt b/examples/pd_grid/requirements.txt deleted file mode 100644 index da2b9972..00000000 --- a/examples/pd_grid/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -jupyter -matplotlib -mesa~=2.0 diff --git a/examples/pd_grid/run.py b/examples/pd_grid/run.py deleted file mode 100644 index ae142aaa..00000000 --- a/examples/pd_grid/run.py +++ /dev/null @@ -1,3 +0,0 @@ -from pd_grid.server import server - -server.launch(open_browser=True) diff --git a/examples/schelling/README.md b/examples/schelling/README.md deleted file mode 100644 index b0116b55..00000000 --- a/examples/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/schelling/__init__.py b/examples/schelling/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/schelling/analysis.ipynb b/examples/schelling/analysis.ipynb deleted file mode 100644 index 71d925c1..00000000 --- a/examples/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.steps < 100:\n", - " model.step()\n", - "print(model.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.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 / len(model.agents)" - ] - }, - { - "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/schelling/app.py b/examples/schelling/app.py deleted file mode 100644 index fb837351..00000000 --- a/examples/schelling/app.py +++ /dev/null @@ -1,43 +0,0 @@ -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/schelling/model.py b/examples/schelling/model.py deleted file mode 100644 index b7523ef2..00000000 --- a/examples/schelling/model.py +++ /dev/null @@ -1,91 +0,0 @@ -import mesa - - -class SchellingAgent(mesa.Agent): - """ - Schelling segregation agent - """ - - def __init__(self, model: mesa.Model, agent_type: int) -> None: - """ - Create a new Schelling agent. - - Args: - agent_type: Indicator for the agent's type (minority=1, majority=0) - """ - super().__init__(model) - self.type = agent_type - - def step(self) -> None: - neighbors = self.model.grid.iter_neighbors( - self.pos, moore=True, radius=self.model.radius - ) - similar = sum(1 for neighbor in neighbors if neighbor.type == self.type) - - # 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, - 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.homophily = homophily - self.radius = radius - - self.grid = mesa.space.SingleGrid(width, height, torus=True) - - self.happy = 0 - self.datacollector = mesa.DataCollector( - 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 _, pos in self.grid.coord_iter(): - 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) - - self.datacollector.collect(self) - - def step(self): - """ - Run one step of the model. - """ - self.happy = 0 # Reset counter of happy agents - self.agents.shuffle_do("step") - - self.datacollector.collect(self) - - self.running = self.happy != len(self.agents) diff --git a/examples/schelling/requirements.txt b/examples/schelling/requirements.txt deleted file mode 100644 index 79bc3555..00000000 --- a/examples/schelling/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -jupyter -matplotlib -mesa[viz]>=3.0.0b0 diff --git a/examples/schelling/run_ascii.py b/examples/schelling/run_ascii.py deleted file mode 100644 index 460fabbb..00000000 --- a/examples/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("---") diff --git a/examples/sugarscape_cg/Readme.md b/examples/sugarscape_cg/Readme.md deleted file mode 100644 index 0334a628..00000000 --- a/examples/sugarscape_cg/Readme.md +++ /dev/null @@ -1,60 +0,0 @@ -# Sugarscape Constant Growback model - -## Summary - -This is Epstein & Axtell's Sugarscape Constant Growback model, with a detailed -description in the chapter 2 of Growing Artificial Societies: Social Science from the Bottom Up - -A simple ecological model, consisting of two agent types: ants, and sugar -patches. - -The ants wander around according to Epstein's rule M: -- Look out as far as vision pennies in the four principal lattice directions and identify the unoccupied site(s) having the most sugar. The order in which each agent search es the four directions is random. -- If the greatest sugar value appears on multiple sites then select the nearest one. That is, if the largest sugar within an agent s vision is four, but the value occurs twice, once at a lattice position two units away and again at a site three units away, the former is chosen. If it appears at multiple sites the same distance away, the first site encountered is selected (the site search order being random). -- Move to this site. Notice that there is no distinction between how far an agent can move and how far it can see. So, if vision equals 5, the agent can move up to 5 lattice positions north , south, east, or west. -- Collect all the sugar at this new position. - -The sugar patches grow at a constant rate of 1 until it reaches maximum capacity. If ant metabolizes to the point it has zero or negative sugar, it dies. - - -The model is tests and demonstrates several Mesa concepts and features: - - MultiGrid - - Multiple agent types (ants, sugar patches) - - Overlay arbitrary text (wolf's energy) on agent's shapes while drawing on CanvasGrid - - Dynamically removing agents from the grid and model when they die - -## 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 - -* ``sugarscape/agents.py``: Defines the SsAgent, and Sugar agent classes. -* ``sugarscape/model.py``: Defines the Sugarscape Constant Growback model itself -* ``sugarscape/server.py``: Sets up the interactive visualization server -* ``run.py``: Launches a model visualization server. - -## Further Reading - -This model is based on the Netlogo Sugarscape 2 Constant Growback: - -Li, J. and Wilensky, U. (2009). NetLogo Sugarscape 2 Constant Growback model. -http://ccl.northwestern.edu/netlogo/models/Sugarscape2ConstantGrowback. -Center for Connected Learning and Computer-Based Modeling, -Northwestern University, Evanston, IL. - -The ant sprite is taken from https://openclipart.org/detail/229519/ant-silhouette, with CC0 1.0 license. diff --git a/examples/sugarscape_cg/requirements.txt b/examples/sugarscape_cg/requirements.txt deleted file mode 100644 index 9531680f..00000000 --- a/examples/sugarscape_cg/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -jupyter -mesa~=2.0 diff --git a/examples/sugarscape_cg/run.py b/examples/sugarscape_cg/run.py deleted file mode 100644 index 47445d4d..00000000 --- a/examples/sugarscape_cg/run.py +++ /dev/null @@ -1,3 +0,0 @@ -from sugarscape_cg.server import server - -server.launch(open_browser=True) diff --git a/examples/sugarscape_cg/sugarscape_cg/__init__.py b/examples/sugarscape_cg/sugarscape_cg/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/sugarscape_cg/sugarscape_cg/agents.py b/examples/sugarscape_cg/sugarscape_cg/agents.py deleted file mode 100644 index 01b24012..00000000 --- a/examples/sugarscape_cg/sugarscape_cg/agents.py +++ /dev/null @@ -1,76 +0,0 @@ -import math - -from mesa.experimental.cell_space import CellAgent, FixedAgent - - -def get_distance(cell_1, cell_2): - """Get the distance between two point - - Args: - pos_1, pos_2: Coordinate tuples for both points. - """ - x1, y1 = cell_1.coordinate - x2, y2 = cell_2.coordinate - dx = x1 - x2 - dy = y1 - y2 - return math.sqrt(dx**2 + dy**2) - - -class SsAgent(CellAgent): - def __init__(self, model, cell, sugar=0, metabolism=0, vision=0): - super().__init__(model) - self.cell = cell - self.sugar = sugar - self.metabolism = metabolism - self.vision = vision - - def get_sugar(self, cell): - for agent in cell.agents: - if isinstance(agent, Sugar): - return agent - - def is_occupied(self, cell): - return any(isinstance(agent, SsAgent) for agent in cell.agents) - - def move(self): - # Get neighborhood within vision - neighbors = [ - cell - for cell in self.cell.get_neighborhood(radius=self.vision) - if not self.is_occupied(cell) - ] - neighbors.append(self.cell) - # Look for location with the most sugar - max_sugar = max(self.get_sugar(cell).amount for cell in neighbors) - candidates = [ - cell for cell in neighbors if self.get_sugar(cell).amount == max_sugar - ] - # Narrow down to the nearest ones - min_dist = min(get_distance(self.cell, cell) for cell in candidates) - final_candidates = [ - cell for cell in candidates if get_distance(self.cell, cell) == min_dist - ] - self.random.shuffle(final_candidates) - self.cell = final_candidates[0] - - def eat(self): - sugar_patch = self.get_sugar(self.cell) - self.sugar = self.sugar - self.metabolism + sugar_patch.amount - sugar_patch.amount = 0 - - def step(self): - self.move() - self.eat() - if self.sugar <= 0: - self.remove() - - -class Sugar(FixedAgent): - def __init__(self, model, max_sugar, cell): - super().__init__(model) - self.amount = max_sugar - self.max_sugar = max_sugar - self.cell = cell - - def step(self): - self.amount = min([self.max_sugar, self.amount + 1]) diff --git a/examples/sugarscape_cg/sugarscape_cg/model.py b/examples/sugarscape_cg/sugarscape_cg/model.py deleted file mode 100644 index 8569320b..00000000 --- a/examples/sugarscape_cg/sugarscape_cg/model.py +++ /dev/null @@ -1,92 +0,0 @@ -""" -Sugarscape Constant Growback Model -================================ - -Replication of the model found in Netlogo: -Li, J. and Wilensky, U. (2009). NetLogo Sugarscape 2 Constant Growback model. -http://ccl.northwestern.edu/netlogo/models/Sugarscape2ConstantGrowback. -Center for Connected Learning and Computer-Based Modeling, -Northwestern University, Evanston, IL. -""" - -from pathlib import Path - -import mesa -from mesa.experimental.cell_space import OrthogonalVonNeumannGrid - -from .agents import SsAgent, Sugar - - -class SugarscapeCg(mesa.Model): - """ - Sugarscape 2 Constant Growback - """ - - verbose = True # Print-monitoring - - def __init__(self, width=50, height=50, initial_population=100, seed=None): - """ - Create a new constant grow back model with the given parameters. - - Args: - width (int): Width of the Sugarscape 2 Constant Growback model. - height (int): Height of the Sugarscape 2 Constant Growback model. - initial_population: Number of population to start with - seed (int): Seed for the random number generator - - """ - super().__init__(seed=seed) - - # Set parameters - self.width = width - self.height = height - self.initial_population = initial_population - - self.grid = OrthogonalVonNeumannGrid((self.width, self.height), torus=True) - self.datacollector = mesa.DataCollector( - {"SsAgent": lambda m: len(m.agents_by_type[SsAgent])} - ) - - # Create sugar - import numpy as np - - sugar_distribution = np.genfromtxt(Path(__file__).parent / "sugar-map.txt") - for cell in self.grid.all_cells: - max_sugar = sugar_distribution[cell.coordinate] - Sugar(self, max_sugar, cell) - - # Create agent: - for i in range(self.initial_population): - x = self.random.randrange(self.width) - y = self.random.randrange(self.height) - sugar = self.random.randrange(6, 25) - metabolism = self.random.randrange(2, 4) - vision = self.random.randrange(1, 6) - cell = self.grid[(x, y)] - SsAgent(self, cell, sugar, metabolism, vision) - - self.running = True - self.datacollector.collect(self) - - def step(self): - # Step suger and agents - self.agents_by_type[Sugar].do("step") - self.agents_by_type[SsAgent].shuffle_do("step") - # collect data - self.datacollector.collect(self) - if self.verbose: - print(f"Step: {self.steps}, SsAgents: {len(self.agents_by_type[SsAgent])}") - - def run_model(self, step_count=200): - if self.verbose: - print( - f"Initial number Sugarscape Agents: {len(self.agents_by_type[SsAgent])}" - ) - - for i in range(step_count): - self.step() - - if self.verbose: - print( - f"\nFinal number Sugarscape Agents: {len(self.agents_by_type[SsAgent])}" - ) diff --git a/examples/sugarscape_cg/sugarscape_cg/resources/ant.png b/examples/sugarscape_cg/sugarscape_cg/resources/ant.png deleted file mode 100644 index f2c85825..00000000 Binary files a/examples/sugarscape_cg/sugarscape_cg/resources/ant.png and /dev/null differ diff --git a/examples/sugarscape_cg/sugarscape_cg/server.py b/examples/sugarscape_cg/sugarscape_cg/server.py deleted file mode 100644 index 0461cdce..00000000 --- a/examples/sugarscape_cg/sugarscape_cg/server.py +++ /dev/null @@ -1,38 +0,0 @@ -import mesa - -from .agents import SsAgent, Sugar -from .model import SugarscapeCg - -color_dic = {4: "#005C00", 3: "#008300", 2: "#00AA00", 1: "#00F800"} - - -def SsAgent_portrayal(agent): - if agent is None: - return - - if type(agent) is SsAgent: - return {"Shape": "sugarscape_cg/resources/ant.png", "scale": 0.9, "Layer": 1} - - elif type(agent) is Sugar: - color = color_dic[agent.amount] if agent.amount != 0 else "#D6F5D6" - return { - "Color": color, - "Shape": "rect", - "Filled": "true", - "Layer": 0, - "w": 1, - "h": 1, - } - - return {} - - -canvas_element = mesa.visualization.CanvasGrid(SsAgent_portrayal, 50, 50, 500, 500) -chart_element = mesa.visualization.ChartModule( - [{"Label": "SsAgent", "Color": "#AA0000"}] -) - -server = mesa.visualization.ModularServer( - SugarscapeCg, [canvas_element, chart_element], "Sugarscape 2 Constant Growback" -) -# server.launch() diff --git a/examples/sugarscape_cg/sugarscape_cg/sugar-map.txt b/examples/sugarscape_cg/sugarscape_cg/sugar-map.txt deleted file mode 100644 index 1357a667..00000000 --- a/examples/sugarscape_cg/sugarscape_cg/sugar-map.txt +++ /dev/null @@ -1,50 +0,0 @@ -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/sugarscape_g1mt/Readme.md b/examples/sugarscape_g1mt/Readme.md deleted file mode 100644 index 5a658cec..00000000 --- a/examples/sugarscape_g1mt/Readme.md +++ /dev/null @@ -1,87 +0,0 @@ -# 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)*. 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: - -- **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. (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). -- 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 -b -``` - -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 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. - -## 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/sugarscape_g1mt/app.py b/examples/sugarscape_g1mt/app.py deleted file mode 100644 index 146d3d5c..00000000 --- a/examples/sugarscape_g1mt/app.py +++ /dev/null @@ -1,61 +0,0 @@ -import numpy as np -import solara -from matplotlib.figure import Figure -from mesa.visualization import SolaraViz, make_plot_measure -from sugarscape_g1mt.model import SugarscapeG1mt -from sugarscape_g1mt.trader_agents import Trader - - -def SpaceDrawer(model): - 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 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. - 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() - ax = fig.subplots() - out = portray(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() - return solara.FigureMatplotlib(fig) - - -model_params = { - "width": 50, - "height": 50, -} - -model1 = SugarscapeG1mt(50, 50) - -page = SolaraViz( - model1, - components=[SpaceDrawer, make_plot_measure(["Trader", "Price"])], - name="Sugarscape {G1, M, T}", - play_interval=1500, -) -page # noqa diff --git a/examples/sugarscape_g1mt/requirements.txt b/examples/sugarscape_g1mt/requirements.txt deleted file mode 100644 index 14c03478..00000000 --- a/examples/sugarscape_g1mt/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -jupyter -mesa~=2.0 -numpy -matplotlib -networkx -pandas diff --git a/examples/sugarscape_g1mt/run.py b/examples/sugarscape_g1mt/run.py deleted file mode 100644 index f1056fa4..00000000 --- a/examples/sugarscape_g1mt/run.py +++ /dev/null @@ -1,105 +0,0 @@ -import sys - -import matplotlib.pyplot as plt -import mesa -import networkx as nx -import pandas as pd -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(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) - 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 -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) - - else: - raise Exception("Option not found") - - -if __name__ == "__main__": - main() diff --git a/examples/sugarscape_g1mt/sugarscape_g1mt/__init__.py b/examples/sugarscape_g1mt/sugarscape_g1mt/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/sugarscape_g1mt/sugarscape_g1mt/model.py deleted file mode 100644 index d51b8bc0..00000000 --- a/examples/sugarscape_g1mt/sugarscape_g1mt/model.py +++ /dev/null @@ -1,180 +0,0 @@ -from pathlib import Path - -import mesa -import numpy as np -from mesa.experimental.cell_space import OrthogonalVonNeumannGrid - -from .resource_agents import Resource -from .trader_agents import Trader - - -# 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, - enable_trade=True, - ): - super().__init__() - # 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.enable_trade = enable_trade - self.running = True - - # initiate mesa grid class - self.grid = OrthogonalVonNeumannGrid((self.width, self.height), torus=False) - # initiate datacollector - self.datacollector = mesa.DataCollector( - model_reporters={ - "Trader": lambda m: len(m.agents_by_type[Trader]), - "Trade Volume": lambda m: sum( - len(a.trade_partners) for a in m.agents_by_type[Trader] - ), - "Price": lambda m: geometric_mean( - flatten([a.prices for a in m.agents_by_type[Trader]]) - ), - }, - agent_reporters={"Trade Network": lambda a: get_trade(a)}, - ) - - # read in landscape file from supplmentary material - sugar_distribution = np.genfromtxt(Path(__file__).parent / "sugar-map.txt") - spice_distribution = np.flip(sugar_distribution, 1) - - 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 - 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)) - - cell = self.grid[(x, y)] - # create Trader object - Trader( - self, - cell, - sugar=sugar, - spice=spice, - metabolism_sugar=metabolism_sugar, - metabolism_spice=metabolism_spice, - vision=vision, - ) - - def step(self): - """ - Unique step function that does staged activation of sugar and spice - and then randomly activates traders - """ - # step Resource agents - 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.agents_by_type[Trader].shuffle() - - for agent in trader_shuffle: - agent.prices = [] - agent.trade_partners = [] - agent.move() - 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.agents_by_type[Trader].shuffle() - - for agent in trader_shuffle: - agent.trade_with_neighbors() - - # 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.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 - - def run_model(self, step_count=1000): - for i in range(step_count): - self.step() diff --git a/examples/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py b/examples/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py deleted file mode 100644 index d9f27694..00000000 --- a/examples/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py +++ /dev/null @@ -1,26 +0,0 @@ -from mesa.experimental.cell_space import FixedAgent - - -class Resource(FixedAgent): - """ - Resource: - - contains an amount of sugar and spice - - grows 1 amount of sugar at each turn - - grows 1 amount of spice at each turn - """ - - 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): - """ - Growth function, adds one unit of sugar and spice each step up to - max amount - """ - 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/sugarscape_g1mt/sugarscape_g1mt/server.py b/examples/sugarscape_g1mt/sugarscape_g1mt/server.py deleted file mode 100644 index 3ef00668..00000000 --- a/examples/sugarscape_g1mt/sugarscape_g1mt/server.py +++ /dev/null @@ -1,61 +0,0 @@ -import mesa - -from .model import SugarscapeG1mt -from .resource_agents import Resource -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"} - - -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, 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", - "Filled": "true", - "Layer": layer, - "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/sugarscape_g1mt/sugarscape_g1mt/sugar-map.txt b/examples/sugarscape_g1mt/sugarscape_g1mt/sugar-map.txt deleted file mode 100644 index 1357a667..00000000 --- a/examples/sugarscape_g1mt/sugarscape_g1mt/sugar-map.txt +++ /dev/null @@ -1,50 +0,0 @@ -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/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py b/examples/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py deleted file mode 100644 index 579f3470..00000000 --- a/examples/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py +++ /dev/null @@ -1,321 +0,0 @@ -import math - -from mesa.experimental.cell_space import CellAgent - -from .resource_agents import Resource - - -# Helper function -def get_distance(cell_1, cell_2): - """ - Calculate the Euclidean distance between two positions - - used in trade.move() - """ - - 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(CellAgent): - """ - Trader: - - has a metabolism of sugar and spice - - harvest and trade sugar and spice to survive - """ - - def __init__( - self, - model, - cell, - sugar=0, - spice=0, - metabolism_sugar=0, - metabolism_spice=0, - vision=0, - ): - super().__init__(model) - self.cell = cell - 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_resource(self, cell): - for agent in cell.agents: - if isinstance(agent, Resource): - return agent - raise Exception(f"Resource agent not found in the position {cell.coordinate}") - - def get_trader(self, cell): - """ - helper function used in self.trade_with_neighbors() - """ - - for agent in cell.agents: - if isinstance(agent, Trader): - return agent - - def is_occupied_by_other(self, cell): - """ - helper function part 1 of self.move() - """ - - 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 - return any(isinstance(a, Trader) for a in cell.agents) - - 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, sugar, spice): - """ - Helper function for - - self.trade() - - self.maybe_self_spice() - - Determines what trader agent needs and can give up - """ - - return (spice / self.metabolism_spice) / (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 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 - - # 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 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) - welfare_other = other.calculate_welfare(other.sugar, other.spice) - - if math.isclose(mrs_self, mrs_other): - return - - # calculate 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 - - 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(cell).sugar_amount, - self.spice + self.get_resource(cell).spice_amount, - ) - for cell in neighboring_cells - ] - - # 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 = [neighboring_cells[i] for i in candidate_indices] - - min_dist = min(get_distance(self.cell, cell) for cell in candidates) - - final_candidates = [ - cell - for cell in candidates - if math.isclose(get_distance(self.cell, cell), min_dist, rel_tol=1e-02) - ] - # 4. Move Agent - self.cell = self.random.choice(final_candidates) - - def eat(self): - patch = self.get_resource(self.cell) - if patch.sugar_amount > 0: - self.sugar += patch.sugar_amount - patch.sugar_amount = 0 - self.sugar -= self.metabolism_sugar - - if patch.spice_amount > 0: - self.spice += patch.spice_amount - patch.spice_amount = 0 - self.spice -= self.metabolism_spice - - def maybe_die(self): - """ - Function to remove Traders who have consumed all their sugar or spice - """ - - if self.is_starved(): - self.remove() - - 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(cell) - for cell in self.cell.get_neighborhood(radius=self.vision) - if self.is_occupied_by_other(cell) - ] - - if len(neighbor_agents) == 0: - return - - # iterate through traders in neighboring cells and trade - for a in neighbor_agents: - self.trade(a) - - return diff --git a/examples/sugarscape_g1mt/tests.py b/examples/sugarscape_g1mt/tests.py deleted file mode 100644 index 274afa6b..00000000 --- a/examples/sugarscape_g1mt/tests.py +++ /dev/null @@ -1,72 +0,0 @@ -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.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.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 diff --git a/examples/virus_on_network/README.md b/examples/virus_on_network/README.md deleted file mode 100644 index f6a51fd5..00000000 --- a/examples/virus_on_network/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Virus on a Network - -## Summary - -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. - -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. - -or - -Directly run the file ``run.py`` in the terminal. e.g. - -``` - $ python run.py -``` - - -## 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/virus_on_network/app.py b/examples/virus_on_network/app.py deleted file mode 100644 index caa1360f..00000000 --- a/examples/virus_on_network/app.py +++ /dev/null @@ -1,135 +0,0 @@ -import math - -import solara -from matplotlib.figure import Figure -from matplotlib.ticker import MaxNLocator -from mesa.visualization import SolaraViz, make_space_matplotlib -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 f"Resistant/Susceptible Ratio: {ratio_text}
Infected Remaining: {infected_text}" - - -def make_plot(model): - # This is for the case when we want to plot multiple measures in 1 figure. - 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)) - return 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, - }, -} - -SpacePlot = make_space_matplotlib(agent_portrayal) - -model1 = VirusOnNetwork() - -page = SolaraViz( - model1, - [ - SpacePlot, - make_plot, - get_resistant_susceptible_ratio, - ], - name="Virus Model", -) -page # noqa diff --git a/examples/virus_on_network/requirements.txt b/examples/virus_on_network/requirements.txt deleted file mode 100644 index 03e3c237..00000000 --- a/examples/virus_on_network/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -networkx>=2.0 -mesa~=2.0 \ No newline at end of file diff --git a/examples/virus_on_network/run.py b/examples/virus_on_network/run.py deleted file mode 100644 index c911c372..00000000 --- a/examples/virus_on_network/run.py +++ /dev/null @@ -1,3 +0,0 @@ -from virus_on_network.server import server - -server.launch(open_browser=True) diff --git a/examples/virus_on_network/virus_on_network/model.py b/examples/virus_on_network/virus_on_network/model.py deleted file mode 100644 index d892a0c4..00000000 --- a/examples/virus_on_network/virus_on_network/model.py +++ /dev/null @@ -1,166 +0,0 @@ -import math -from enum import Enum - -import mesa -import networkx as nx - - -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, - ): - 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) - self.grid = mesa.space.NetworkGrid(self.G) - - 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 node in self.G.nodes(): - a = VirusAgent( - self, - State.SUSCEPTIBLE, - self.virus_spread_chance, - self.virus_check_frequency, - self.recovery_chance, - self.gain_resistance_chance, - ) - - # 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.agents.shuffle_do("step") - # collect data - self.datacollector.collect(self) - - def run_model(self, n): - for i in range(n): - self.step() - - -class VirusAgent(mesa.Agent): - """ - Individual Agent definition and its properties/interaction methods - """ - - def __init__( - self, - model, - initial_state, - virus_spread_chance, - virus_check_frequency, - recovery_chance, - gain_resistance_chance, - ): - super().__init__(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_neighborhood( - 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) and ( - 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/virus_on_network/virus_on_network/server.py b/examples/virus_on_network/virus_on_network/server.py deleted file mode 100644 index dcc7643f..00000000 --- a/examples/virus_on_network/virus_on_network/server.py +++ /dev/null @@ -1,140 +0,0 @@ -import math - -import mesa - -from .model import State, VirusOnNetwork, 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 = {} - 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( - portrayal_method=network_portrayal, - canvas_height=500, - canvas_width=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 f"Resistant/Susceptible Ratio: {ratio_text}
Infected Remaining: {infected_text}" - - -model_params = { - "num_nodes": mesa.visualization.Slider( - 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( - name="Avg Node Degree", - value=3, - min_value=3, - max_value=8, - step=1, - description="Avg Node Degree", - ), - "initial_outbreak_size": mesa.visualization.Slider( - name="Initial Outbreak Size", - value=1, - min_value=1, - max_value=10, - step=1, - description="Initial Outbreak Size", - ), - "virus_spread_chance": mesa.visualization.Slider( - 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( - 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( - 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( - 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( - model_cls=VirusOnNetwork, - visualization_elements=[network, get_resistant_susceptible_ratio, chart], - name="Virus on Network Model", - model_params=model_params, -) -server.port = 8521 diff --git a/examples/wolf_sheep/Readme.md b/examples/wolf_sheep/Readme.md deleted file mode 100644 index 30794a6e..00000000 --- a/examples/wolf_sheep/Readme.md +++ /dev/null @@ -1,57 +0,0 @@ -# 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/wolf_sheep/__init__.py b/examples/wolf_sheep/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/wolf_sheep/requirements.txt b/examples/wolf_sheep/requirements.txt deleted file mode 100644 index 25d263f4..00000000 --- a/examples/wolf_sheep/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -mesa~=2.0 diff --git a/examples/wolf_sheep/run.py b/examples/wolf_sheep/run.py deleted file mode 100644 index 89e3b548..00000000 --- a/examples/wolf_sheep/run.py +++ /dev/null @@ -1,3 +0,0 @@ -from wolf_sheep.server import server - -server.launch(open_browser=True) diff --git a/examples/wolf_sheep/wolf_sheep/__init__.py b/examples/wolf_sheep/wolf_sheep/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/wolf_sheep/wolf_sheep/agents.py b/examples/wolf_sheep/wolf_sheep/agents.py deleted file mode 100644 index 8e71988b..00000000 --- a/examples/wolf_sheep/wolf_sheep/agents.py +++ /dev/null @@ -1,102 +0,0 @@ -from mesa.experimental.cell_space import CellAgent, FixedAgent - - -class Animal(CellAgent): - """The base animal class.""" - - def __init__(self, model, energy, p_reproduce, energy_from_food, cell): - """Initializes an animal. - - 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): - """One step of the agent.""" - self.cell = self.cell.neighborhood.select_random_cell() - self.energy -= 1 - - self.feed() - - if self.energy < 0: - self.remove() - elif self.random.random() < self.p_reproduce: - self.spawn_offspring() - - -class Sheep(Animal): - """A sheep that walks around, reproduces (asexually) and gets eaten.""" - - 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 - - -class Wolf(Animal): - """A wolf that walks around, reproduces (asexually) and eats 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.energy_from_food - - # Kill the sheep - sheep_to_eat.remove() - - -class GrassPatch(FixedAgent): - """ - A patch of grass that grows at a fixed rate and it is eaten by sheep - """ - - def __init__(self, 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__(model) - self.fully_grown = fully_grown - self.countdown = countdown - - 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/wolf_sheep/wolf_sheep/model.py b/examples/wolf_sheep/wolf_sheep/model.py deleted file mode 100644 index 5b43b791..00000000 --- a/examples/wolf_sheep/wolf_sheep/model.py +++ /dev/null @@ -1,136 +0,0 @@ -""" -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 mesa.experimental.cell_space import OrthogonalMooreGrid - -from .agents import GrassPatch, Sheep, Wolf - - -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 - - 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, - seed=None, - ): - """ - 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__(seed=None) - # Set parameters - self.width = width - self.height = height - self.initial_sheep = initial_sheep - self.initial_wolves = initial_wolves - self.grass = grass - self.grass_regrowth_time = grass_regrowth_time - - self.grid = OrthogonalMooreGrid((self.width, self.height), torus=True) - - collectors = { - "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.agents_by_type[GrassPatch]) - - self.datacollector = mesa.DataCollector(collectors) - - # 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( - 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(self, energy, wolf_reproduce, wolf_gain_from_food, self.grid[(x, y)]) - - # Create grass patches - if self.grass: - for cell in self.grid.all_cells: - 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, fully_grown, countdown) - patch.cell = cell - - self.running = True - self.datacollector.collect(self) - - def step(self): - # 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.agents_by_type[agent_type].shuffle_do("step") - - # collect data - self.datacollector.collect(self) - - def run_model(self, step_count=200): - for i in range(step_count): - self.step() diff --git a/examples/wolf_sheep/wolf_sheep/resources/sheep.png b/examples/wolf_sheep/wolf_sheep/resources/sheep.png deleted file mode 100644 index dfb81b0e..00000000 Binary files a/examples/wolf_sheep/wolf_sheep/resources/sheep.png and /dev/null differ diff --git a/examples/wolf_sheep/wolf_sheep/resources/wolf.png b/examples/wolf_sheep/wolf_sheep/resources/wolf.png deleted file mode 100644 index 5357b855..00000000 Binary files a/examples/wolf_sheep/wolf_sheep/resources/wolf.png and /dev/null differ diff --git a/examples/wolf_sheep/wolf_sheep/server.py b/examples/wolf_sheep/wolf_sheep/server.py deleted file mode 100644 index 112c1a2d..00000000 --- a/examples/wolf_sheep/wolf_sheep/server.py +++ /dev/null @@ -1,78 +0,0 @@ -import mesa -from wolf_sheep.agents import GrassPatch, Sheep, Wolf -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/pyproject.toml b/pyproject.toml index 574f2f5b..589075ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = [ {name = "Project Mesa Team", email = "projectmesa@googlegroups.com"} ] license = {file = "LICENSE"} -readme = "README.rst" +readme = "README.md" requires-python = ">=3.8" [project.optional-dependencies] diff --git a/setup.cfg b/setup.cfg index fcc4451c..82ab5c57 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,3 @@ [options] -package_dir = - mesa_models.boltzmann_wealth_model = examples/boltzmann_wealth_model - mesa_models.schelling = examples/schelling - mesa_models.epstein_civil_violence = examples/epstein_civil_violence/epstein_civil_violence - mesa_models.wolf_sheep = examples/wolf_sheep/wolf_sheep - mesa_models.sugarscape_cg = examples/sugarscape_cg/sugarscape_cg +packages = + examples