From 5a47c2c6d1df5906939a165a3d69b5c93288da95 Mon Sep 17 00:00:00 2001 From: jackiekazil Date: Wed, 16 Nov 2022 12:36:29 -0500 Subject: [PATCH 01/56] Move examples from mesa repo to mesa-examples repo. --- examples/boid_flockers/Flocker Test.ipynb | 114 ++++++++++++ examples/boid_flockers/Readme.md | 34 ++++ .../boid_flockers/SimpleContinuousModule.py | 32 ++++ examples/boid_flockers/boid_flockers/boid.py | 104 +++++++++++ examples/boid_flockers/boid_flockers/model.py | 76 ++++++++ .../boid_flockers/boid_flockers/server.py | 23 +++ .../boid_flockers/simple_continuous_canvas.js | 79 +++++++++ examples/boid_flockers/requirements.txt | 3 + examples/boid_flockers/run.py | 3 + examples/conways_game_of_life/Readme.md | 30 ++++ .../conways_game_of_life/cell.py | 53 ++++++ .../conways_game_of_life/model.py | 43 +++++ .../conways_game_of_life/portrayal.py | 19 ++ .../conways_game_of_life/server.py | 12 ++ .../conways_game_of_life/requirements.txt | 1 + examples/conways_game_of_life/run.py | 3 + examples/wolf_sheep/Readme.md | 57 ++++++ examples/wolf_sheep/requirements.txt | 1 + examples/wolf_sheep/run.py | 3 + examples/wolf_sheep/wolf_sheep/__init__.py | 0 examples/wolf_sheep/wolf_sheep/agents.py | 120 +++++++++++++ examples/wolf_sheep/wolf_sheep/model.py | 166 ++++++++++++++++++ examples/wolf_sheep/wolf_sheep/random_walk.py | 41 +++++ .../wolf_sheep/wolf_sheep/resources/sheep.png | Bin 0 -> 1322 bytes .../wolf_sheep/wolf_sheep/resources/wolf.png | Bin 0 -> 1473 bytes examples/wolf_sheep/wolf_sheep/scheduler.py | 28 +++ examples/wolf_sheep/wolf_sheep/server.py | 79 +++++++++ .../wolf_sheep/wolf_sheep/test_random_walk.py | 82 +++++++++ 28 files changed, 1206 insertions(+) create mode 100644 examples/boid_flockers/Flocker Test.ipynb create mode 100644 examples/boid_flockers/Readme.md create mode 100644 examples/boid_flockers/boid_flockers/SimpleContinuousModule.py create mode 100644 examples/boid_flockers/boid_flockers/boid.py create mode 100644 examples/boid_flockers/boid_flockers/model.py create mode 100644 examples/boid_flockers/boid_flockers/server.py create mode 100644 examples/boid_flockers/boid_flockers/simple_continuous_canvas.js create mode 100644 examples/boid_flockers/requirements.txt create mode 100644 examples/boid_flockers/run.py create mode 100644 examples/conways_game_of_life/Readme.md create mode 100644 examples/conways_game_of_life/conways_game_of_life/cell.py create mode 100644 examples/conways_game_of_life/conways_game_of_life/model.py create mode 100644 examples/conways_game_of_life/conways_game_of_life/portrayal.py create mode 100644 examples/conways_game_of_life/conways_game_of_life/server.py create mode 100644 examples/conways_game_of_life/requirements.txt create mode 100644 examples/conways_game_of_life/run.py create mode 100644 examples/wolf_sheep/Readme.md create mode 100644 examples/wolf_sheep/requirements.txt create mode 100644 examples/wolf_sheep/run.py create mode 100644 examples/wolf_sheep/wolf_sheep/__init__.py create mode 100644 examples/wolf_sheep/wolf_sheep/agents.py create mode 100644 examples/wolf_sheep/wolf_sheep/model.py create mode 100644 examples/wolf_sheep/wolf_sheep/random_walk.py create mode 100644 examples/wolf_sheep/wolf_sheep/resources/sheep.png create mode 100644 examples/wolf_sheep/wolf_sheep/resources/wolf.png create mode 100644 examples/wolf_sheep/wolf_sheep/scheduler.py create mode 100644 examples/wolf_sheep/wolf_sheep/server.py create mode 100644 examples/wolf_sheep/wolf_sheep/test_random_walk.py diff --git a/examples/boid_flockers/Flocker Test.ipynb b/examples/boid_flockers/Flocker Test.ipynb new file mode 100644 index 00000000000..664019e51fc --- /dev/null +++ b/examples/boid_flockers/Flocker Test.ipynb @@ -0,0 +1,114 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from boid_flockers.model import BoidFlockers\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\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.schedule.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\nJMl3JHkyyZtba1/ddJzABQCMwuCBa88vLHABACMxa26x0jwAQGcCFwBAZwIXAEBnAhcAQGcCFwBA\nZwIXAEBnAhcAQGcCFwBAZwIXAEBnAhcAQGcCFwBAZwIXAEBnAhcAQGcCFwBAZwIXAEBnAhcAQGcC\nFwBAZwIXQEdVdazq8JnJrY4N3R5gGNVaG+aFq1prrQZ5cYB9MAlYNz2aPHxwsmftmeTSva21x4Zt\nGbBbs+aWG+bZGAA2OnQiOXUwue/KjoPJ8RNJBC5YMYYUAQA608MF0M3Fk8nakSQbhxRPDtokYBBq\nuAA6mtRxHTox2bp4Uv0WjNOsuUXgYlR8eAEwBIGLlWHGFwBDMUuRFWLGFwDjZJYiAEBnergYETO+\nABgnNVyMiqJ5AIagaB4AoLNZc4saLgCAzgQuAIDOBC4AgM4ELgCAzgQuAIDOBC4AgM4ELgCAzgQu\nAIDOBC4AgM4ELgCAzgQuAIDOBC4AgM4ELpZCVR2rOnxmcqtjQ7cHADaq1towLzzjVbfhiknAuunR\n5OGDkz1rzySX7m2tPTZsywBYFrPmlhvm2RgYxqETyamDyX1XdhxMjp9IInABsBAMKbJkHkvy/iS5\nfTdDi4YkAejJkCKjtz6k+KMHk9NJfmb6yM6GFg1JAnAts+YWgYulMAlNh/5Zcurw+tDi6STHz7Z2\n4a6rH3v4THLqzt0eB8DqmDW3GFJkKUx7oz4xdDsAYCuK5lkiF08ma0eSbBwaPNnvOADYGUOKLJXp\n0OKJydbFkzutw9rrcQCsBjVcAACdqeECAFhwAhejYJ0sAMbMkCILzzpZAAzNpX1YAS7dA8C4GVIE\nAOhMDxcjYJ0sAMZNDRejYJ0sAIZkHS4AgM6swwUAsOAELgCAzgQuAIDOBC4AgM4ELgCAzgQuAIDO\nBC72jQtQA7CqrMPFvnABagDGzMWrGQkXoAZgdRlSBKA7JQWsupmHFKvq5iQ/m+S1SVqSH07y+ST/\nPMkrkzyZ5M2tta9uOs6Q4goxpAiry/ufZTD4tRSr6nSSX2utfaCqbkjyrUl+IsnvtdbeU1VvT/KS\n1toD82w44+MC1LCaqg6fSU7duV5ScDrJ8bOtXbhr8ri/DSy+QWu4qurbkvzl1tp9SdJa+0aS36+q\nNyW5Y/q000nOJXlgyx/Cypj+EfWHFHjOeu/XqSu9X0eqSu8XS2fWovnvTPJfquqDSV6X5LeT3J/k\nltba+elzzie5ZcbXAWC0Lp5M1o4k2TikeHJyf+8TavSMMSazBq4bktye5H9qrf1WVb03m3qyWmut\nqoZZewKAwbXWHquqe6dBKsmlmcORnjHGZtbA9VSSp1prvzXd/oUkDyZ5uqpuba09XVUvTfLlrQ6u\nqoc2bJ5rrZ2bsT0ALKDtSwqu1vt1NZaaoa+qOprk6Lx+3kyBaxqovlBVr26tfS7JG5I8Pr3dl+Td\n0//+0jbHPzTL6wMwbj16v2Aepp1A565sV9U7Z/l585il+LpMloV4UZL/mMmyENcneSTJd8SyEADM\nmaUm2G+DLwux5xcWuACYgaJ59pPABQDQ2ay5xaV9AAA6E7gAADoTuAAAOhO4AAA6E7gA6KKqjlUd\nPjO51bGh2wNDMksRgLmzThbLZtbcMuulfQBgCy69AxsZUmRQhhwAWAWGFBmMIQdYXt7fLBsrzTNa\nVYfPJKfuXB9yOJ3k+NnWLtw1ZLuA+XDpHZaJGi4AFtI0YAlZEIGLQV08mawdSbJxyOHkoE0CgA4M\nKTIoQw4AjIEaLgCAzmbNLZaFAFgBlmCBYenhAlhylmiA2enhAhiRYXqaDp2YhK37Mrk9fHC9dhLY\nD2YpAuyT9Z6mU1d6mo5UlZ4mWAECF8C+Ger6gpZggaEJXABLrrX2WFXdOw13SS5ZggX2maJ5gH2i\neB3GyzpcACNisd/F5d+GqxG4AJiJoKH3kWtz8WoA9szMySuGmtDAqrAOF0BWeSV2a3TBftDDBayU\nrYbP9PJg6Qx6U8MFrIzt6nSmw0l3rg8nnU5y/GxrF+4aqq37ZVFrl4aoK1PLxtWo4QLYsW3rdFbW\nIq7RNVSP4/TnC1l0IXABDDictAi9KosXNBSws3wELmCFbB2shurlUTsGq0MNF7BSFqFHab0th8+s\nau3Y1SxqXRmrTQ0XwC4s3vAZmy1iXRnMSg/Xklmkb+/A1d+TenJgPFzah+f44w2LZSfvSV+SnAPG\nQeDiOepBYLF4T16bL4qMhRouAEbMEhCsBoFrqbg0BctvXMNP3pPAhCHFJTOuDyPYnTEOP3lPXt0Y\n/01ZTWq4gJWhJmo5CaWMgRouAEbN2misAoELGBE1UcA4GVIERsXwEzAENVwAAJ3Nmluum2djAGZR\nVceqDp+Z3OrY0O0BmBc9XMBCsDwAsMjMUgSWhBXHgeVlSBEAoDNDisAgNs82nPzXkCKwmMxSBEZn\nu3qtyX1LPgCLRw0XMEJb12tNL9EjZAFLRw0XAEBneriAAbhED7Ba1HABg3CJHmBMFM0DAHTm0j4A\nAAtO4AIA6EzgAgDoTOACAOhM4GIwVXWs6vCZya2ODd0eAOjFLEUGsd2lXSwNAMAicmkfRmrrS7vE\nZV0AWEKGFAEAOtPDxUBc2mWVWFUeWHVquBiMD+HVoF4PWAYu7QMstKrDZ5JTd67X651Ocvxsaxfu\nGrJdALvh0j4AAAtODRfQmXo9AEOKQHfq9WDvvH8WgxouAFhSJp0sDgufAsDSskj0sphL0XxVXV9V\nv1NVvzzdPlRVZ6vqc1V1pqpunsfrAACM0bxmKf54kieSXBmffCDJ2dbaq5P8ynQbANiViycnw4in\nM7mtPTPZx9jMXMNVVa9I8k+T/C9JjrfW/npVfTbJHa2181V1a5JzrbXv3nScGi5YMIpzYfF4Xy6G\nwYvmq+pfJHlXkpuSvG0auL7SWnvJ9PFKcvHK9objBC5YIIpzAbY36MKnVfXXkny5tfY7SbZsRJsk\numGmQgK7cOjEJGzdl8nt4YPr36oBmMWssxT/UpI3VdUbk/yJJDdV1c8nOV9Vt7bWnq6qlyb58lYH\nV9VDGzbPtdbOzdgeYM+ePZy8P8lHk/yPQzcGYFBVdTTJ0bn9vHmtw1VVd2R9SPE9SS601t5dVQ8k\nubm19sCm5xtShAUxHU78SPLwgcmetyX5o2eTP7rHkCLA4q3DdSW9/XSSR6rq7yR5Msmb5/w6MEqL\nVPz6/LbcfDh574ENa/0kuf/x1r4mbAHMwdwCV2vt15L82vT+xSRvmNfPhmWwXpR+6kpR+pGqGqQo\n/YVtuf/yC5913YX9bRWsnkX6EkZfVpqHfbNIK0Zvbsunr0vWLue5iTQuMA29LdKXMPoTuIAk35vk\nm59Mjk97tS75pg3dLdKXMHoTuGDfXDyZrB1JsnGdqx31Is1/2GGrtnztHa39oT/0AB3MbZbirl/Y\nLEVW0F6CU68FSdWOwLAsNjwug680v+cXFrhgR6oOn0lO3bk+7HA6yfGzrV24a8h2AbPzxWc8Fm1Z\nCABgh6YBS8haAQIXDGhn3273XvsFwGIwpAgD2U39hmEHgGGp4YKRWoTaLEEOYGdmzS3XzbMxwHhs\nWHTxzsntpkcn+2C1VdWxqsNnJjfvCeZDDRcM5uq1Wf17nyy6CJtN3nc3fiR59fRC7p/6K1XlIu7M\nTOCCgbTWHquqe6chJxtXd3fJDxjKt74rOXgg+bHp9tsOJPWu+CLCjAQu2GfP77nKya1rtvaj98ns\nR3ihA69MfiYb3ntJjr9yqNawPAQu2EeL1HN1tR42WF2X/1OSw1vsg5mYpQj7aOuZifd/orWv/Pnn\nP29xL/lhZiPLbPre+0jy8LSGa+3Z5NI9k/t+71eZleZh/P5sVR3b+Ad8qN6na4WpReqhgx6m7717\nNr73Jv/1e89s9HDBPpoGlo8lD0+XZHl7krck+eAg10bcFLDOJTf95NV61RZh7TDYb37vSfRwwahM\nvj2/+JPJ+29PXpbJH+6nB2nLFr1Vr09+9DrLRLCKDJXTm8AF++5r70ieeDT5sYOTsDXU7MAXzIS8\nLnn/NY4xs5Hlc+2hcr/3zE7ggn222LMDP3s5OT0d7nzhh8pitx326urLsPi9Zx4ELhjA9I/1wH+w\nt/zW/lPJ8aOT7a0/VBaj7bC//N4zK0XzsMLUrcBiL8PC4pg1twhcsE+EG1hc3p9ci8AFI+AbNMC4\nWRYCRmE/ro0IwKK6bugGAAAsOz1csC+s4wOwytRwwT5RlAswXormAQA6mzW3qOECAOhM4AIA6Ezg\nAgDoTOACAOhM4AIA6EzgAgDoTOACAOhM4AIA6EzgAnatqo5VHT4zudWxvT4HYFVYaR7YlUl4uunR\n5OGN14W8d+OlinbyHIAxmTW3uHg1sEuHTiSnDib3XdlxMDl+Islju3sOwOowpAgA0JkeLmCXLp5M\n1o4k2ThceHL3zwFYHWq4gF2b1GgdOjHZunhyq9qsnTwHYCxmzS0CFwDANcyaW9RwAQB0JnABAHQm\ncAEAdCZwAQB0JnABAHQmcAEAdCZwAQB0JnABAHQmcAEAdCZwAQB0JnABAHQmcAEAdCZwAQB0JnAB\nAHQmcAEAdCZwAQB0JnABAHQmcAEAdCZwAQB0JnABAHQmcAEAdCZwAQB0JnABAHQmcAEAdCZwAQB0\nJnABAHQmcAEAdDZT4Kqq26rqV6vq8ar6D1W1Nt1/qKrOVtXnqupMVd08n+YCAIxPtdb2fnDVrUlu\nba19sqpenOS3k/xAkh9O8nuttfdU1duTvKS19sCmY1trrWZoOwDAvpg1t8zUw9Vae7q19snp/T9M\n8pkkL0/ypiSnp087nUkIAwBYSXOr4aqqVyX5c0l+M8ktrbXz04fOJ7llXq8DADA2N8zjh0yHE/9l\nkh9vrf1B1XqPW2utVdWW45ZV9dCGzXOttXPzaA8AwCyq6miSo3P7ebPUcCVJVX1Lkn+V5F+31t47\n3ffZJEdba09X1UuT/Gpr7bs3HaeGC1ZMVR1LDp2YbF082Vp7bNgWAezMoDVcNenK+rkkT1wJW1Mf\nTXLf9P59SX5pltcBxm8Stm56NDl15+R206OTfQDLb9ZZikeS/Lskn0py5Qc9mOTjSR5J8h1Jnkzy\n5tbaVzcdq4cLVkjV4TOToHXlu9jpJMfPtnbhriHbBbATs+aWmWq4Wmv/d7bvJXvDLD8bAGBZzKVo\nHuDaLp5M1o4kOTjZXnsmuXRyrz9NPRgwJjMXze/5hQ0pwsqZV0harwd7eGN4u1foAnqZNbcIXMDo\nbF0Pdv8nWvvKnx+yXcDyGnSWIsAC+bNmPQKLSg8XMDrTIcWPJQ9PvzS+PclbknzQrEegi0FnKQIM\nobX2WNWLP5m8//bkZZkMKT49dLMAtmVIERipr70jeeKZ5E2ZhK21ZyYzIQEWjyFFYLQsDQHsF7MU\nAQA6M0sRWFpVdazq8JnJzQxEYLz0cAGDuNZwoMVNgUViliJLQz3O6lgPU6euhKkjVbUpTB06MXn8\nyuKmOZgcP5HE7wUwOgIXC2FnH8Asj52EqcuHB2gYQBcCFwtCbwbrJgH8xtcmb9uwd+3ZWS52DTAk\ngQsYwMWTydqRJBvrszaEqUMnklMHkluT/OMkX0ryzcf1eAJjJXCxIK71AcwymawUX/dOezGTXNqm\nZu/Y9HY6yfEL+9hEgLkyS5GFoWieK8xQBBaNhU+BpSSAA4tE4AIA6MxK8wAAC07gAgDoTOACAOhM\n4KIrFx8GAEXzdGRqPwDLwsWrWWAu1wMAiSFF9shQIQDsnCFFdm2nQ4WGFAFYFhY+Zd9VHT6TnLpz\nfajwdJLjZ1u7cNcLn2u1cADGTw0XC20asK4ZsgQzAJaZHi52bd5DhYYeV4+ADYyNIUUGMc8PzN0M\nUTJ+AjYwRoYUGcROhwoTvRlsZrkQYPUIXMzFdqFqvTfj1JXejCNVtak34+LJZO1Iko09Hif3sfkA\n0JUhRXZlq2B1tSGinQ4X6gVbHYYUgTEypMi+2a63ah5DRLsZomTcpiH93unvSJJLAjaw9AQunnPt\nXqZtg9VVGC7khQRsYNUIXCTZaa3VdrYPVcvSm2HIE4BZqOEiyc6WZrh6rdbyBhI1RwCo4WLfXK23\narmHiCxjAMBsBC6mdlZrtdzBCgD6MKTIc5Z5WHAWhhQBcGkf2AfCKMBqE7joStAAAIGLjgylAcCE\nWYp0ZHYeAMzDdUM3gHGqqmNVh89MbnVs6PYAwCIzpMi2thtSnNw31AjA6lDDRVdbFc3vZFV6AFgm\nari4qllnGVroFABmp4drifWaZTj9uR9JHj4w/bnPJpfuMaQIy8nyMKCHi6vqOcvwG0nev+E+sIzW\nv7iduvLF7UhVqdmEXTJLkT04dCJ534HkNzK5ve/A+rdfYLkcOjHpJb8vk9vDB73fYff0cC21nV2Q\nGgDoSw3XkttJ7cVu6zOsQA+rw/sdJiwLwUz2+sdUES2sDu93ELiYkTW1AODaZs0tiuYBADpTNL/y\nFNYDQG+GFFGfAQDXoIYLAPaJL6irS+ACgH1giYzVpmgeYIVV1bGqw2eqXvLbVS/+7cn9OjZ0u5bT\n/q+6v/7v69917BTNA4zUC69z+LZMgsA/cb3DOXn+EOLlw/v/2q5juSwELoDResEF6pN8NJOel3ld\nqH7/LFp91AsDz1ufTdaeTXJgst17VvcL/n1H+e/KhMAFsIIWP9wsQm/OCwLPgeTvfiI5fmGyeWnw\n88Z4CFwAo7V5Hb0rQ4pX73kZSbhZ0N6cAxf270oc1klcJgLXyG31LXXRvrkCfUzf7/dOgsnlw8nX\nk3zwwrV7XsYSboY2bOB5/r9vokdt3ASuEdvmW+pPJTf95GJ9cwXmYasvU9P39hK8vxevN2cRAs/y\n/PtiHa4R2+bC0xeSU4ddjBqWyzzXgFrU9aT0zrPIZs0tergARmF+w4CL0HOzFb05LLNugauq7k7y\n3iTXJ/nZ1tq7e73W6tqyC/5UsvaTuUq3vG+RgHAD+6vLkGJVXZ/k/0vyhiRfTPJbSX6otfaZDc8x\npDgHuy2aX9ShBODqvHdhWAt5LcWq+otJ3tlau3u6/UCStNZ+esNzBK4BbFP3pcaLlTS23t6xtReW\nyaLWcL08yRc2bD+V5C90ei2AXVvMtaiuzjAgjFevwDXM1Ed2YPGmXsMwrEUF7J9egeuLSW7bsH1b\nJr1cz1NVD23YPNdaO9epPUwt6uwkAFgkVXU0ydG5/bxONVw3ZFI0//okX0ry8SiaBxaIInRgNxay\naD5JquqvZn1ZiJ9rrf3DTY8LXMCgFKEDO7WwgeuaLyxwAQAjMWtuuW6ejQEA4IUELgCAzgQuAIDO\nBC4AgM4ELgCAzgQuAIDOBC4AgM4ELgCAzgQuAIDOBC4AgM4ELgCAzgQuAIDOBC4AgM4ELgCAzgQu\nAIDOBC6KPY5xAAAGrUlEQVQAgM4ELgCAzgQuAIDOBC4AgM4ELgCAzgQuAIDOBC4AgM4ELgCAzgQu\nAIDOBC4AgM4ELgCAzgQuAIDOBC4AgM4ELgCAzgQuAIDOBC4AgM4ELgCAzgQuAIDOBC4AgM4ELgCA\nzgQuAIDOBC4AgM4ELgCAzgQuAIDOBC4AgM4ELgCAzgQuAIDOBC4AgM4ELgCAzgQuAIDOBC4AgM4E\nLgCAzgQuAIDOBC4AgM4ELgCAzgQuAIDOBC4AgM4ELgCAzgQuAIDOBC4AgM4ELgCAzgQuAIDOBC4A\ngM4ELgCAzgQuAIDOBC4AgM4ELgCAzgQuAIDOBC4AgM4ELgCAzgQuAIDOBC4AgM4ELgCAzgQuAIDO\nBC4AgM4ELgCAzvYcuKrqH1XVZ6rq31fVL1bVt2147MGq+nxVfbaq7ppPUwEAxmmWHq4zSV7bWntd\nks8leTBJquo1SX4wyWuS3J3kfVWlJ23Oquro0G0YM+dvNs7f3jl3s3H+ZuP8DWfPQai1dra1dnm6\n+ZtJXjG9f0+SD7fWvt5aezLJ7yb5vplayVaODt2AkTs6dANG7ujQDRixo0M3YOSODt2AkTs6dANW\n1bx6nv52ko9N778syVMbHnsqycvn9DoAAKNzw9UerKqzSW7d4qF3tNZ+efqcn0jyX1trH7rKj2p7\nbyIAwLhVa3vPQlX1t5L8aJLXt9b+eLrvgSRprf30dPv/SvLO1tpvbjpWCAMARqO1Vns9ds+Bq6ru\nTnIyyR2ttd/bsP81ST6USd3Wy5P8myR/us2S7AAARuyqQ4rX8L8neVGSs1WVJL/RWntra+2Jqnok\nyRNJvpHkrcIWALDKZhpSBADg2vZ9fSwLps6uqu6enqPPV9Xbh27PIquq26rqV6vq8ar6D1W1Nt1/\nqKrOVtXnqupMVd08dFsXWVVdX1W/U1VXJss4fztUVTdX1S9M/+49UVV/wfnbmelnwuNV9emq+lBV\nHXDutldVH6iq81X16Q37tj1fPnOfb5vzN7fMMsSCpBZMnUFVXZ/k/8jkHL0myQ9V1fcM26qF9vUk\n/3Nr7bVJvj/J352erweSnG2tvTrJr0y32d6PZ1ImcKVL3Pnbuf8tycdaa9+T5M8k+Wycv2uqqldl\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 new file mode 100644 index 00000000000..cb3292b4f68 --- /dev/null +++ b/examples/boid_flockers/Readme.md @@ -0,0 +1,34 @@ +# Flockers + +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. + +## How to Run + +Launch the model: +``` + $ python Flocker_Server.py +``` + +Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run. + +## Files + +* [flockers/model.py](flockers/model.py): Core model file; contains the BoidModel class. +* [flockers/boid.py](flockers/boid.py): The Boid agent class. +* [flockers/SimpleContinuousModule.py](flockers/SimpleContinuousModule.py): Defines ``SimpleCanvas``, the Python side of a custom visualization module for drawing agents with continuous positions. +* [flockers/simple_continuous_canvas.js](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. +* [flockers/server.py](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 + +======= +* Launch the visualization +``` +$ mesa runserver +``` +* Visit your browser: http://127.0.0.1:8521/ +* In your browser hit *run* diff --git a/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py b/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py new file mode 100644 index 00000000000..3f3da5dd01e --- /dev/null +++ b/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py @@ -0,0 +1,32 @@ +import mesa + + +class SimpleCanvas(mesa.visualization.VisualizationElement): + local_includes = ["boid_flockers/simple_continuous_canvas.js"] + portrayal_method = None + canvas_height = 500 + canvas_width = 500 + + def __init__(self, portrayal_method, 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 = "new Simple_Continuous_Module({}, {})".format( + self.canvas_width, self.canvas_height + ) + self.js_code = "elements.push(" + new_element + ");" + + def render(self, model): + space_state = [] + for obj in model.schedule.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/boid.py b/examples/boid_flockers/boid_flockers/boid.py new file mode 100644 index 00000000000..f427f9ddbbc --- /dev/null +++ b/examples/boid_flockers/boid_flockers/boid.py @@ -0,0 +1,104 @@ +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 velocity (a vector) + define their movement. Separation is their desired minimum distance from + any other Boid. + """ + + def __init__( + self, + unique_id, + model, + pos, + speed, + velocity, + vision, + separation, + cohere=0.025, + separate=0.25, + match=0.04, + ): + """ + Create a new Boid flocker agent. + + Args: + unique_id: Unique agent identifyer. + pos: Starting position + speed: Distance to move per step. + heading: 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__(unique_id, model) + self.pos = np.array(pos) + self.speed = speed + self.velocity = velocity + self.vision = vision + self.separation = separation + self.cohere_factor = cohere + self.separate_factor = separate + self.match_factor = match + + def cohere(self, neighbors): + """ + Return the vector toward the center of mass of the local neighbors. + """ + cohere = np.zeros(2) + if neighbors: + for neighbor in neighbors: + cohere += self.model.space.get_heading(self.pos, neighbor.pos) + cohere /= len(neighbors) + return cohere + + def separate(self, neighbors): + """ + Return a vector away from any neighbors closer than separation dist. + """ + me = self.pos + them = (n.pos for n in neighbors) + separation_vector = np.zeros(2) + for other in them: + if self.model.space.get_distance(me, other) < self.separation: + separation_vector -= self.model.space.get_heading(me, other) + return separation_vector + + def match_heading(self, neighbors): + """ + Return a vector of the neighbors' average heading. + """ + match_vector = np.zeros(2) + if neighbors: + for neighbor in neighbors: + match_vector += neighbor.velocity + match_vector /= len(neighbors) + return match_vector + + def step(self): + """ + Get the Boid's neighbors, compute the new vector, and move accordingly. + """ + + neighbors = self.model.space.get_neighbors(self.pos, self.vision, False) + self.velocity += ( + self.cohere(neighbors) * self.cohere_factor + + self.separate(neighbors) * self.separate_factor + + self.match_heading(neighbors) * self.match_factor + ) / 2 + self.velocity /= np.linalg.norm(self.velocity) + new_pos = self.pos + self.velocity * self.speed + self.model.space.move_agent(self, new_pos) diff --git a/examples/boid_flockers/boid_flockers/model.py b/examples/boid_flockers/boid_flockers/model.py new file mode 100644 index 00000000000..00a08d765d5 --- /dev/null +++ b/examples/boid_flockers/boid_flockers/model.py @@ -0,0 +1,76 @@ +""" +Flockers +============================================================= +A Mesa implementation of Craig Reynolds's Boids flocker model. +Uses numpy arrays to represent vectors. +""" + +import mesa +import numpy as np + +from .boid import Boid + + +class BoidFlockers(mesa.Model): + """ + Flocker model class. Handles agent creation, placement and scheduling. + """ + + def __init__( + self, + population=100, + width=100, + height=100, + speed=1, + vision=10, + separation=2, + cohere=0.025, + separate=0.25, + match=0.04, + ): + """ + 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.""" + self.population = population + self.vision = vision + self.speed = speed + self.separation = separation + self.schedule = mesa.time.RandomActivation(self) + self.space = mesa.space.ContinuousSpace(width, height, True) + self.factors = dict(cohere=cohere, separate=separate, match=match) + self.make_agents() + self.running = True + + def make_agents(self): + """ + Create self.population agents, with random positions and starting headings. + """ + for i 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)) + velocity = np.random.random(2) * 2 - 1 + boid = Boid( + i, + self, + pos, + self.speed, + velocity, + self.vision, + self.separation, + **self.factors + ) + self.space.place_agent(boid, pos) + self.schedule.add(boid) + + def step(self): + self.schedule.step() diff --git a/examples/boid_flockers/boid_flockers/server.py b/examples/boid_flockers/boid_flockers/server.py new file mode 100644 index 00000000000..4906df699c7 --- /dev/null +++ b/examples/boid_flockers/boid_flockers/server.py @@ -0,0 +1,23 @@ +import mesa + +from .model import BoidFlockers +from .SimpleContinuousModule import SimpleCanvas + + +def boid_draw(agent): + return {"Shape": "circle", "r": 2, "Filled": "true", "Color": "Red"} + + +boid_canvas = SimpleCanvas(boid_draw, 500, 500) +model_params = { + "population": 100, + "width": 100, + "height": 100, + "speed": 5, + "vision": 10, + "separation": 2, +} + +server = mesa.visualization.ModularServer( + BoidFlockers, [boid_canvas], "Boids", model_params +) diff --git a/examples/boid_flockers/boid_flockers/simple_continuous_canvas.js b/examples/boid_flockers/boid_flockers/simple_continuous_canvas.js new file mode 100644 index 00000000000..20c0ded8732 --- /dev/null +++ b/examples/boid_flockers/boid_flockers/simple_continuous_canvas.js @@ -0,0 +1,79 @@ +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 new file mode 100644 index 00000000000..bcbfbbe220b --- /dev/null +++ b/examples/boid_flockers/requirements.txt @@ -0,0 +1,3 @@ +jupyter +matplotlib +mesa diff --git a/examples/boid_flockers/run.py b/examples/boid_flockers/run.py new file mode 100644 index 00000000000..be0c1c75c58 --- /dev/null +++ b/examples/boid_flockers/run.py @@ -0,0 +1,3 @@ +from boid_flockers.server import server + +server.launch() diff --git a/examples/conways_game_of_life/Readme.md b/examples/conways_game_of_life/Readme.md new file mode 100644 index 00000000000..686afb4065a --- /dev/null +++ b/examples/conways_game_of_life/Readme.md @@ -0,0 +1,30 @@ +# 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 + +* ``game_of_life/cell.py``: Defines the behavior of an individual cell, which can be in two states: DEAD or ALIVE. +* ``game_of_life/model.py``: Defines the model itself, initialized with a random configuration of alive and dead cells. +* ``game_of_life/portrayal.py``: Describes for the front end how to render a cell. +* ``game_of_live/server.py``: Defines an interactive visualization. +* ``run.py``: Launches the visualization + +## 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/conways_game_of_life/cell.py b/examples/conways_game_of_life/conways_game_of_life/cell.py new file mode 100644 index 00000000000..8639288d4ca --- /dev/null +++ b/examples/conways_game_of_life/conways_game_of_life/cell.py @@ -0,0 +1,53 @@ +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__(pos, 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 step(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 advance(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 new file mode 100644 index 00000000000..635ccaa959d --- /dev/null +++ b/examples/conways_game_of_life/conways_game_of_life/model.py @@ -0,0 +1,43 @@ +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. + """ + + # Set up the grid and schedule. + + # Use SimultaneousActivation which simulates all the cells + # computing their next state simultaneously. This needs to + # be done because each cell's next state depends on the current + # state of all its neighbors -- before they've changed. + self.schedule = mesa.time.SimultaneousActivation(self) + + # Use a simple grid, where edges wrap around. + self.grid = mesa.space.Grid(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.schedule.add(cell) + + self.running = True + + def step(self): + """ + Have the scheduler advance each cell by one step + """ + self.schedule.step() 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 new file mode 100644 index 00000000000..4f68468d857 --- /dev/null +++ b/examples/conways_game_of_life/conways_game_of_life/portrayal.py @@ -0,0 +1,19 @@ +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 new file mode 100644 index 00000000000..4167b3d01bd --- /dev/null +++ b/examples/conways_game_of_life/conways_game_of_life/server.py @@ -0,0 +1,12 @@ +import mesa + +from .portrayal import portrayCell +from .model import ConwaysGameOfLife + + +# 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 new file mode 100644 index 00000000000..1ad1bbec7ab --- /dev/null +++ b/examples/conways_game_of_life/requirements.txt @@ -0,0 +1 @@ +mesa \ No newline at end of file diff --git a/examples/conways_game_of_life/run.py b/examples/conways_game_of_life/run.py new file mode 100644 index 00000000000..2854fdee59d --- /dev/null +++ b/examples/conways_game_of_life/run.py @@ -0,0 +1,3 @@ +from conways_game_of_life.server import server + +server.launch() diff --git a/examples/wolf_sheep/Readme.md b/examples/wolf_sheep/Readme.md new file mode 100644 index 00000000000..30794a6ee67 --- /dev/null +++ b/examples/wolf_sheep/Readme.md @@ -0,0 +1,57 @@ +# 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/requirements.txt b/examples/wolf_sheep/requirements.txt new file mode 100644 index 00000000000..da0b5b956fd --- /dev/null +++ b/examples/wolf_sheep/requirements.txt @@ -0,0 +1 @@ +mesa diff --git a/examples/wolf_sheep/run.py b/examples/wolf_sheep/run.py new file mode 100644 index 00000000000..dc5d367e89d --- /dev/null +++ b/examples/wolf_sheep/run.py @@ -0,0 +1,3 @@ +from wolf_sheep.server import server + +server.launch() diff --git a/examples/wolf_sheep/wolf_sheep/__init__.py b/examples/wolf_sheep/wolf_sheep/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/wolf_sheep/wolf_sheep/agents.py b/examples/wolf_sheep/wolf_sheep/agents.py new file mode 100644 index 00000000000..fe62192bf60 --- /dev/null +++ b/examples/wolf_sheep/wolf_sheep/agents.py @@ -0,0 +1,120 @@ +import mesa +from wolf_sheep.random_walk import RandomWalker + + +class Sheep(RandomWalker): + """ + A sheep that walks around, reproduces (asexually) and gets eaten. + + The init is the same as the RandomWalker. + """ + + energy = None + + def __init__(self, unique_id, pos, model, moore, energy=None): + super().__init__(unique_id, pos, model, moore=moore) + self.energy = energy + + def step(self): + """ + A model step. Move, then eat grass and reproduce. + """ + self.random_move() + living = True + + if self.model.grass: + # Reduce energy + self.energy -= 1 + + # If there is grass available, eat it + this_cell = self.model.grid.get_cell_list_contents([self.pos]) + grass_patch = [obj for obj in this_cell if isinstance(obj, GrassPatch)][0] + if grass_patch.fully_grown: + self.energy += self.model.sheep_gain_from_food + grass_patch.fully_grown = False + + # Death + if self.energy < 0: + self.model.grid.remove_agent(self) + self.model.schedule.remove(self) + living = False + + if living and self.random.random() < self.model.sheep_reproduce: + # Create a new sheep: + if self.model.grass: + self.energy /= 2 + lamb = Sheep( + self.model.next_id(), self.pos, self.model, self.moore, self.energy + ) + self.model.grid.place_agent(lamb, self.pos) + self.model.schedule.add(lamb) + + +class Wolf(RandomWalker): + """ + A wolf that walks around, reproduces (asexually) and eats sheep. + """ + + energy = None + + def __init__(self, unique_id, pos, model, moore, energy=None): + super().__init__(unique_id, pos, model, moore=moore) + self.energy = energy + + def step(self): + self.random_move() + self.energy -= 1 + + # If there are sheep present, eat one + x, y = self.pos + this_cell = self.model.grid.get_cell_list_contents([self.pos]) + sheep = [obj for obj in this_cell if isinstance(obj, Sheep)] + if len(sheep) > 0: + sheep_to_eat = self.random.choice(sheep) + self.energy += self.model.wolf_gain_from_food + + # Kill the sheep + self.model.grid.remove_agent(sheep_to_eat) + self.model.schedule.remove(sheep_to_eat) + + # Death or reproduction + if self.energy < 0: + self.model.grid.remove_agent(self) + self.model.schedule.remove(self) + else: + if self.random.random() < self.model.wolf_reproduce: + # Create a new wolf cub + self.energy /= 2 + cub = Wolf( + self.model.next_id(), self.pos, self.model, self.moore, self.energy + ) + self.model.grid.place_agent(cub, cub.pos) + self.model.schedule.add(cub) + + +class GrassPatch(mesa.Agent): + """ + A patch of grass that grows at a fixed rate and it is eaten by sheep + """ + + def __init__(self, unique_id, pos, 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__(unique_id, model) + self.fully_grown = fully_grown + self.countdown = countdown + self.pos = pos + + 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 new file mode 100644 index 00000000000..2b8fdbdeed1 --- /dev/null +++ b/examples/wolf_sheep/wolf_sheep/model.py @@ -0,0 +1,166 @@ +""" +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 wolf_sheep.scheduler import RandomActivationByTypeFiltered +from wolf_sheep.agents import Sheep, Wolf, GrassPatch + + +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 + + verbose = False # Print-monitoring + + 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, + ): + """ + 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__() + # Set parameters + self.width = width + self.height = height + self.initial_sheep = initial_sheep + self.initial_wolves = initial_wolves + self.sheep_reproduce = sheep_reproduce + self.wolf_reproduce = wolf_reproduce + self.wolf_gain_from_food = wolf_gain_from_food + self.grass = grass + self.grass_regrowth_time = grass_regrowth_time + self.sheep_gain_from_food = sheep_gain_from_food + + self.schedule = RandomActivationByTypeFiltered(self) + self.grid = mesa.space.MultiGrid(self.width, self.height, torus=True) + self.datacollector = mesa.DataCollector( + { + "Wolves": lambda m: m.schedule.get_type_count(Wolf), + "Sheep": lambda m: m.schedule.get_type_count(Sheep), + "Grass": lambda m: m.schedule.get_type_count( + GrassPatch, lambda x: x.fully_grown + ), + } + ) + + # 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 = Sheep(self.next_id(), (x, y), self, True, energy) + self.grid.place_agent(sheep, (x, y)) + self.schedule.add(sheep) + + # Create wolves + for i 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 = Wolf(self.next_id(), (x, y), self, True, energy) + self.grid.place_agent(wolf, (x, y)) + self.schedule.add(wolf) + + # Create grass patches + if self.grass: + for agent, x, y in self.grid.coord_iter(): + + 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.next_id(), (x, y), self, fully_grown, countdown) + self.grid.place_agent(patch, (x, y)) + self.schedule.add(patch) + + self.running = True + self.datacollector.collect(self) + + def step(self): + self.schedule.step() + # collect data + self.datacollector.collect(self) + if self.verbose: + print( + [ + self.schedule.time, + self.schedule.get_type_count(Wolf), + self.schedule.get_type_count(Sheep), + self.schedule.get_type_count(GrassPatch, lambda x: x.fully_grown), + ] + ) + + def run_model(self, step_count=200): + + if self.verbose: + print("Initial number wolves: ", self.schedule.get_type_count(Wolf)) + print("Initial number sheep: ", self.schedule.get_type_count(Sheep)) + print( + "Initial number grass: ", + self.schedule.get_type_count(GrassPatch, lambda x: x.fully_grown), + ) + + for i in range(step_count): + self.step() + + if self.verbose: + print("") + print("Final number wolves: ", self.schedule.get_type_count(Wolf)) + print("Final number sheep: ", self.schedule.get_type_count(Sheep)) + print( + "Final number grass: ", + self.schedule.get_type_count(GrassPatch, lambda x: x.fully_grown), + ) diff --git a/examples/wolf_sheep/wolf_sheep/random_walk.py b/examples/wolf_sheep/wolf_sheep/random_walk.py new file mode 100644 index 00000000000..49219fa7fff --- /dev/null +++ b/examples/wolf_sheep/wolf_sheep/random_walk.py @@ -0,0 +1,41 @@ +""" +Generalized behavior for random walking, one grid cell at a time. +""" + +import mesa + + +class RandomWalker(mesa.Agent): + """ + Class implementing random walker methods in a generalized manner. + + Not intended to be used on its own, but to inherit its methods to multiple + other agents. + """ + + grid = None + x = None + y = None + moore = True + + def __init__(self, unique_id, pos, model, moore=True): + """ + grid: The MultiGrid object in which the agent lives. + x: The agent's current x coordinate + y: The agent's current y coordinate + moore: If True, may move in all 8 directions. + Otherwise, only up, down, left, right. + """ + super().__init__(unique_id, model) + self.pos = pos + self.moore = moore + + def random_move(self): + """ + Step one cell in any allowable direction. + """ + # Pick the next cell from the adjacent cells. + next_moves = self.model.grid.get_neighborhood(self.pos, self.moore, True) + next_move = self.random.choice(next_moves) + # Now move: + self.model.grid.move_agent(self, next_move) diff --git a/examples/wolf_sheep/wolf_sheep/resources/sheep.png b/examples/wolf_sheep/wolf_sheep/resources/sheep.png new file mode 100644 index 0000000000000000000000000000000000000000..dfb81b0e5d73bb9f41e4b788934b212b1d544818 GIT binary patch literal 1322 zcmV+_1=aeAP)Nkl`0Z;)@0i*waLm3IY{CDgY_~DgY_~eJ8)u#Pa%QZ#O|v zEJc^Qwa4@3=gru=rz`@0O1dlQholdZ-lqSV@<`G{Nw?Gd z-)YXt(jMK#BcCKaC@6%ouO!`1KuS7)mh@N>a~BzNfxMA~H-1Wbk*vI*{^$FRq}?A$ z$CAFL?|6>yJjZ*0S!DD8AuMjC@Ij$(lD zuTu_`HX+W*kJ4~XkT<1)S6)iOv&=ELv$^3m1q8w502XC@OqL@jh<(O0N$+Qx5jn^& z4Zx9OL-{iW#1#P{4TfV7dWR$Or-1xQ?moys%yhtH z_~oGo7=YbLV924aF$LtWB;vJ1uU&$7m=wvNJA6w(NK{w}_jX%k2gOTEA(3|@#xxL= zhedK0S~Ii&z`oQYlM2Rzy2%}r0r|=xA0eG069NQRl6VNEz=+;{;)liholDE;q zog>x;HHUQUk&_6-yuM+9wY4tDC&5Yh?*1wjR2*;^fml#4Yb=d5o>4F=ml&-Bz6+ziS(l;k@Gj;#~V2z|T=sykLZozBanCwz#8NU zbO;TD9;@^es-Deja~xvq2a{rb{(LjTTApEWJhIU#Kaa$gA>VAt^9c!xfl9T?O)NP= zu5^vs3DE*T@A^`!y1uZ81*@b)JhB`@ET#n9B?PW zC%=3BkT*)9Weet1Y#=X$hJpJI6cOw){`k9=6aHp*v~ie@tEW!AE^AbW-|gIG`Y+2 zKt5NrH5P3a#oB8HD_hzqDn_V`0P%b)N8aF}*6DDTM_3e<%@f+Jgc5$$Q1gi2aB=d) zBfe;09|PIxuDRwBZ|;qv>mwNuhAodc!6R@KXI=c9eQ90;;$tKQ&F?KdKQb59yamLR zjnY(pDA{sX2NDw=04QJ}RB|V%6{&?*l;yi#=Et{ItA&gS^fkh`i^ahmbPn`}9b&iC@#=Ei!4*k_RMFTF0xMI;734eT27zp^XG?bg zSJY04k5Yij1FPBBv=%l3LhpL}c28v=?0qr{K6!l`y#J-rfUWqmscGCG?eF*6AJ&~f z`$iZbWWK z1U^5Sn%VQ0v1h?#L%s-*X)~%dLCeM49^f$s6<}QR!?izKHiSTV{xjq9aU+j3P89(< z)yF|1;3?F6EIt0lnEA@^ttIeO1ekW?fD)(|XVm>ZI9EZYjceHbMF0an&l5?+REIoZ z5%}gr)_#@Nd_<`+y`-5od#yHpQ;6Y2x+W1IYy0G^wL+^i?^I8j*;L;19|2n~lRLqx z2{=HHPMG-921_UvXu2uHtDpy?67*SR7|G81ReB2X*Z(cliwz}E zDPX#PDElGUmtwzwc`OXJ1fEE$WTMk6fqX$yA0S|qi*$@~*aXy-#=`^QA)eq@ah>#< zLNBN6${{e(xn(m8CZ^4FaM6}|JD&@?x4X?^&Tla=E1y0aS-ybZ0tg*$JeB~zT+_ql zKXkBQa-RxmBVHCHJuiMS)(?1Q-PhH=M+zMfBqZ} z-GGyLz#^b&E9?Y7ZV#-E8Kt{6RSF0dI6z^*dTd3>xivD%_P=ffZhd!HA7Y(}a!lH; z16EgU>KH=ES)LHa>> scheduler = RandomActivationByTypeFiltered(model) + >>> scheduler.get_type_count(AgentA, lambda agent: agent.some_attribute > 10) + """ + + def get_type_count( + self, + type_class: Type[mesa.Agent], + filter_func: Callable[[mesa.Agent], bool] = None, + ) -> int: + """ + Returns the current number of agents of certain type in the queue that satisfy the filter function. + """ + count = 0 + for agent in self.agents_by_type[type_class].values(): + if filter_func is None or filter_func(agent): + count += 1 + return count diff --git a/examples/wolf_sheep/wolf_sheep/server.py b/examples/wolf_sheep/wolf_sheep/server.py new file mode 100644 index 00000000000..bccf4ec849d --- /dev/null +++ b/examples/wolf_sheep/wolf_sheep/server.py @@ -0,0 +1,79 @@ +import mesa + +from wolf_sheep.agents import Wolf, Sheep, GrassPatch +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/examples/wolf_sheep/wolf_sheep/test_random_walk.py b/examples/wolf_sheep/wolf_sheep/test_random_walk.py new file mode 100644 index 00000000000..ab3b044ab1e --- /dev/null +++ b/examples/wolf_sheep/wolf_sheep/test_random_walk.py @@ -0,0 +1,82 @@ +""" +Testing the RandomWalker by having an ABM composed only of random walker +agents. +""" + +from mesa import Model +from mesa.space import MultiGrid +from mesa.time import RandomActivation +from mesa.visualization.TextVisualization import TextVisualization, TextGrid + +from wolf_sheep.random_walk import RandomWalker + + +class WalkerAgent(RandomWalker): + """ + Agent which only walks around. + """ + + def step(self): + self.random_move() + + +class WalkerWorld(Model): + """ + Random walker world. + """ + + height = 10 + width = 10 + + def __init__(self, width, height, agent_count): + """ + Create a new WalkerWorld. + + Args: + width, height: World size. + agent_count: How many agents to create. + """ + self.height = height + self.width = width + self.grid = MultiGrid(self.width, self.height, torus=True) + self.agent_count = agent_count + + self.schedule = RandomActivation(self) + # Create agents + for i in range(self.agent_count): + x = self.random.randrange(self.width) + y = self.random.randrange(self.height) + a = WalkerAgent(i, (x, y), self, True) + self.schedule.add(a) + self.grid.place_agent(a, (x, y)) + + def step(self): + self.schedule.step() + + +class WalkerWorldViz(TextVisualization): + """ + ASCII Visualization for a WalkerWorld agent. + Each cell is displayed as the number of agents currently in that cell. + """ + + def __init__(self, model): + """ + Create a new visualization for a WalkerWorld instance. + + args: + model: An instance of a WalkerWorld model. + """ + self.model = model + grid_viz = TextGrid(self.model.grid, None) + grid_viz.converter = lambda x: str(len(x)) + self.elements = [grid_viz] + + +if __name__ == "__main__": + print("Testing 10x10 world, with 50 random walkers, for 10 steps.") + model = WalkerWorld(10, 10, 50) + viz = WalkerWorldViz(model) + for i in range(10): + print("Step:", str(i)) + viz.step() From 293d113cfee85c1a57d827bb0efb776f61e78f8d Mon Sep 17 00:00:00 2001 From: rht Date: Mon, 5 Dec 2022 06:23:19 -0500 Subject: [PATCH 02/56] requirements: Pin Mesa version to 1.x --- examples/boid_flockers/requirements.txt | 2 +- examples/conways_game_of_life/requirements.txt | 2 +- examples/wolf_sheep/requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/boid_flockers/requirements.txt b/examples/boid_flockers/requirements.txt index bcbfbbe220b..19b805acb1f 100644 --- a/examples/boid_flockers/requirements.txt +++ b/examples/boid_flockers/requirements.txt @@ -1,3 +1,3 @@ jupyter matplotlib -mesa +mesa~=1.1 diff --git a/examples/conways_game_of_life/requirements.txt b/examples/conways_game_of_life/requirements.txt index 1ad1bbec7ab..0d2d0bc66aa 100644 --- a/examples/conways_game_of_life/requirements.txt +++ b/examples/conways_game_of_life/requirements.txt @@ -1 +1 @@ -mesa \ No newline at end of file +mesa~=1.1 \ No newline at end of file diff --git a/examples/wolf_sheep/requirements.txt b/examples/wolf_sheep/requirements.txt index da0b5b956fd..63b0d24e76d 100644 --- a/examples/wolf_sheep/requirements.txt +++ b/examples/wolf_sheep/requirements.txt @@ -1 +1 @@ -mesa +mesa~=1.1 From 4808fc3bccdf9f52db91a747019b1f38d6097c69 Mon Sep 17 00:00:00 2001 From: ItsQuinnMoore Date: Mon, 24 Apr 2023 11:13:30 -0600 Subject: [PATCH 03/56] Fixed consistent capitalization of files. --- examples/boltzmann_wealth_model/Readme.md | 39 ++++++++++ .../boltzmann_wealth_model/__init__.py | 0 .../boltzmann_wealth_model/model.py | 73 +++++++++++++++++++ .../boltzmann_wealth_model/server.py | 40 ++++++++++ .../boltzmann_wealth_model/requirements.txt | 4 + examples/boltzmann_wealth_model/run.py | 3 + 6 files changed, 159 insertions(+) create mode 100644 examples/boltzmann_wealth_model/Readme.md create mode 100644 examples/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py create mode 100644 examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py create mode 100644 examples/boltzmann_wealth_model/boltzmann_wealth_model/server.py create mode 100644 examples/boltzmann_wealth_model/requirements.txt create mode 100644 examples/boltzmann_wealth_model/run.py diff --git a/examples/boltzmann_wealth_model/Readme.md b/examples/boltzmann_wealth_model/Readme.md new file mode 100644 index 00000000000..785a0946a24 --- /dev/null +++ b/examples/boltzmann_wealth_model/Readme.md @@ -0,0 +1,39 @@ +# 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). + +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 examples, launch the Jupyter Notebook and run the code in ``Introduction to Mesa Tutorial Code.ipynb``. + +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: + +``` + $ python viz_money_model.py +``` + +If your browser doesn't open automatically, point it to [http://127.0.0.1:8521/](http://127.0.0.1:8521/). When the visualization loads, press Reset, then Run. + + +## Files + +* ``Introduction to Mesa Tutorial Code.ipynb``: Jupyter Notebook with all the steps as described in the tutorial. +* ``money_model.py``: Final version of the model. +* ``viz_money_model.py``: Creates and launches interactive visualization. + +## 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) +____ +You will need to open the file as a Jupyter (aka iPython) notebook with an iPython 3 kernel. Required dependencies are listed in the provided `requirements.txt` file which can be installed by running `pip install -r requirements.txt` diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py new file mode 100644 index 00000000000..76ebc516b36 --- /dev/null +++ b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -0,0 +1,73 @@ +import mesa + + +def compute_gini(model): + agent_wealths = [agent.wealth for agent in model.schedule.agents] + x = sorted(agent_wealths) + N = model.num_agents + B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x)) + return 1 + (1 / N) - 2 * B + + +class 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): + self.num_agents = N + self.grid = mesa.space.MultiGrid(width, height, True) + self.schedule = mesa.time.RandomActivation(self) + self.datacollector = mesa.DataCollector( + model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} + ) + # Create agents + for i in range(self.num_agents): + a = MoneyAgent(i, self) + self.schedule.add(a) + # Add the agent to a random grid cell + x = self.random.randrange(self.grid.width) + y = self.random.randrange(self.grid.height) + self.grid.place_agent(a, (x, y)) + + self.running = True + self.datacollector.collect(self) + + def step(self): + self.schedule.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, unique_id, model): + super().__init__(unique_id, model) + self.wealth = 1 + + def move(self): + possible_steps = self.model.grid.get_neighborhood( + self.pos, moore=True, include_center=False + ) + new_position = self.random.choice(possible_steps) + self.model.grid.move_agent(self, new_position) + + def give_money(self): + cellmates = self.model.grid.get_cell_list_contents([self.pos]) + if len(cellmates) > 1: + other = self.random.choice(cellmates) + other.wealth += 1 + self.wealth -= 1 + + def step(self): + self.move() + if self.wealth > 0: + self.give_money() diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/server.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/server.py new file mode 100644 index 00000000000..a49546ce741 --- /dev/null +++ b/examples/boltzmann_wealth_model/boltzmann_wealth_model/server.py @@ -0,0 +1,40 @@ +import mesa + +from .model import BoltzmannWealthModel + + +def agent_portrayal(agent): + portrayal = {"Shape": "circle", "Filled": "true", "r": 0.5} + + if agent.wealth > 0: + portrayal["Color"] = "red" + portrayal["Layer"] = 0 + else: + portrayal["Color"] = "grey" + portrayal["Layer"] = 1 + portrayal["r"] = 0.2 + return portrayal + + +grid = mesa.visualization.CanvasGrid(agent_portrayal, 10, 10, 500, 500) +chart = mesa.visualization.ChartModule( + [{"Label": "Gini", "Color": "#0000FF"}], data_collector_name="datacollector" +) + +model_params = { + "N": mesa.visualization.Slider( + "Number of agents", + 100, + 2, + 200, + 1, + description="Choose how many agents to include in the model", + ), + "width": 10, + "height": 10, +} + +server = mesa.visualization.ModularServer( + BoltzmannWealthModel, [grid, chart], "Money Model", model_params +) +server.port = 8521 diff --git a/examples/boltzmann_wealth_model/requirements.txt b/examples/boltzmann_wealth_model/requirements.txt new file mode 100644 index 00000000000..b93c188674f --- /dev/null +++ b/examples/boltzmann_wealth_model/requirements.txt @@ -0,0 +1,4 @@ +jupyter +matplotlib +mesa~=1.1 +numpy diff --git a/examples/boltzmann_wealth_model/run.py b/examples/boltzmann_wealth_model/run.py new file mode 100644 index 00000000000..ea57809eb0a --- /dev/null +++ b/examples/boltzmann_wealth_model/run.py @@ -0,0 +1,3 @@ +from boltzmann_wealth_model.server import server + +server.launch() From 7057338938d0d04165972507996f17017e0263a3 Mon Sep 17 00:00:00 2001 From: Houssam Kherraz Date: Mon, 24 Apr 2023 14:53:02 -0400 Subject: [PATCH 04/56] Update outdated readme to be more accurate + clean up requirements.txt of unnecessary dependencies --- examples/boltzmann_wealth_model/Readme.md | 22 ++++++++++++------- .../boltzmann_wealth_model/requirements.txt | 3 --- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/examples/boltzmann_wealth_model/Readme.md b/examples/boltzmann_wealth_model/Readme.md index 785a0946a24..84b6b28ad1f 100644 --- a/examples/boltzmann_wealth_model/Readme.md +++ b/examples/boltzmann_wealth_model/Readme.md @@ -2,18 +2,26 @@ ## 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). +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 examples, launch the Jupyter Notebook and run the code in ``Introduction to Mesa Tutorial Code.ipynb``. +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) 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: ``` - $ python viz_money_model.py + $ python server.py +``` + +Make sure to install the requirements first: + +``` + pip install -r requirements.txt ``` If your browser doesn't open automatically, point it to [http://127.0.0.1:8521/](http://127.0.0.1:8521/). When the visualization loads, press Reset, then Run. @@ -21,9 +29,9 @@ If your browser doesn't open automatically, point it to [http://127.0.0.1:8521/] ## Files -* ``Introduction to Mesa Tutorial Code.ipynb``: Jupyter Notebook with all the steps as described in the tutorial. -* ``money_model.py``: Final version of the model. -* ``viz_money_model.py``: Creates and launches interactive visualization. +* ``boltzmann_wealth_model/model.py``: Final version of the model. +* ``boltzmann_wealth_model/server.py``: Code for the interactive visualization. +* ``run.py``: Launches the server. ## Further Reading @@ -35,5 +43,3 @@ This model is drawn from econophysics and presents a statistical mechanics appro [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) -____ -You will need to open the file as a Jupyter (aka iPython) notebook with an iPython 3 kernel. Required dependencies are listed in the provided `requirements.txt` file which can be installed by running `pip install -r requirements.txt` diff --git a/examples/boltzmann_wealth_model/requirements.txt b/examples/boltzmann_wealth_model/requirements.txt index b93c188674f..63b0d24e76d 100644 --- a/examples/boltzmann_wealth_model/requirements.txt +++ b/examples/boltzmann_wealth_model/requirements.txt @@ -1,4 +1 @@ -jupyter -matplotlib mesa~=1.1 -numpy From c278053da5f5c197014a6803d556cfd791cd5223 Mon Sep 17 00:00:00 2001 From: Catherine Devlin Date: Tue, 25 Apr 2023 12:05:47 -0600 Subject: [PATCH 05/56] Use pre-commit (#27) * Use Pathlib Code by Phil Robare (versilimidude2) * Use pre-commit * Dropped unused imports * used pre-commit to run pyupgrade, trim whitespace * Remove pathlib changes These belong in a separate PR. * remove redundant black, move comment as per rht's suggestions --------- Co-authored-by: Catherine Devlin --- examples/conways_game_of_life/conways_game_of_life/model.py | 2 +- examples/wolf_sheep/wolf_sheep/model.py | 2 -- examples/wolf_sheep/wolf_sheep/scheduler.py | 3 ++- 3 files changed, 3 insertions(+), 4 deletions(-) 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 index 635ccaa959d..03fa52a07b1 100644 --- a/examples/conways_game_of_life/conways_game_of_life/model.py +++ b/examples/conways_game_of_life/conways_game_of_life/model.py @@ -27,7 +27,7 @@ def __init__(self, width=50, height=50): # Place a cell at each location, with some initialized to # ALIVE and some to DEAD. - for (contents, x, y) in self.grid.coord_iter(): + for contents, x, y in self.grid.coord_iter(): cell = Cell((x, y), self) if self.random.random() < 0.1: cell.state = cell.ALIVE diff --git a/examples/wolf_sheep/wolf_sheep/model.py b/examples/wolf_sheep/wolf_sheep/model.py index 2b8fdbdeed1..160a1e93e3c 100644 --- a/examples/wolf_sheep/wolf_sheep/model.py +++ b/examples/wolf_sheep/wolf_sheep/model.py @@ -114,7 +114,6 @@ def __init__( # Create grass patches if self.grass: for agent, x, y in self.grid.coord_iter(): - fully_grown = self.random.choice([True, False]) if fully_grown: @@ -144,7 +143,6 @@ def step(self): ) def run_model(self, step_count=200): - if self.verbose: print("Initial number wolves: ", self.schedule.get_type_count(Wolf)) print("Initial number sheep: ", self.schedule.get_type_count(Sheep)) diff --git a/examples/wolf_sheep/wolf_sheep/scheduler.py b/examples/wolf_sheep/wolf_sheep/scheduler.py index e2a59fc7c80..cd3dac61f9d 100644 --- a/examples/wolf_sheep/wolf_sheep/scheduler.py +++ b/examples/wolf_sheep/wolf_sheep/scheduler.py @@ -19,7 +19,8 @@ def get_type_count( filter_func: Callable[[mesa.Agent], bool] = None, ) -> int: """ - Returns the current number of agents of certain type in the queue that satisfy the filter function. + Returns the current number of agents of certain type in the queue + that satisfy the filter function. """ count = 0 for agent in self.agents_by_type[type_class].values(): From 19a2d3e605a5490f181c5d21630a4f764b304fa9 Mon Sep 17 00:00:00 2001 From: Jeremy Silver Date: Tue, 25 Apr 2023 12:38:25 -0600 Subject: [PATCH 06/56] Update examples to version 1.2 (using SingleGrid instead of Grid) (#30) Co-authored-by: Jeremy Silver --- examples/conways_game_of_life/conways_game_of_life/model.py | 2 +- examples/conways_game_of_life/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index 03fa52a07b1..581541c11d3 100644 --- a/examples/conways_game_of_life/conways_game_of_life/model.py +++ b/examples/conways_game_of_life/conways_game_of_life/model.py @@ -23,7 +23,7 @@ def __init__(self, width=50, height=50): self.schedule = mesa.time.SimultaneousActivation(self) # Use a simple grid, where edges wrap around. - self.grid = mesa.space.Grid(width, height, torus=True) + self.grid = mesa.space.SingleGrid(width, height, torus=True) # Place a cell at each location, with some initialized to # ALIVE and some to DEAD. diff --git a/examples/conways_game_of_life/requirements.txt b/examples/conways_game_of_life/requirements.txt index 0d2d0bc66aa..1a7fa12e2a4 100644 --- a/examples/conways_game_of_life/requirements.txt +++ b/examples/conways_game_of_life/requirements.txt @@ -1 +1 @@ -mesa~=1.1 \ No newline at end of file +mesa~=1.2 \ No newline at end of file From b20dc56668153e6870e6e0f81bd97cf2f310898b Mon Sep 17 00:00:00 2001 From: houssam7737 Date: Tue, 25 Apr 2023 17:35:07 -0400 Subject: [PATCH 07/56] Fix bug boltzman model agent giving money to itself (#28) * fix bug where agent gives money to itself in the boltzmann model example * [pre-commit.ci] auto fixes from pre-commit.com hooks --------- Co-authored-by: Houssam Kherraz Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../boltzmann_wealth_model/boltzmann_wealth_model/model.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py index 76ebc516b36..0f61b8838d6 100644 --- a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -62,7 +62,10 @@ def move(self): def give_money(self): cellmates = self.model.grid.get_cell_list_contents([self.pos]) - if len(cellmates) > 1: + 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 From 8522cacc27bae83d1521d9852033c7ae07bf2f06 Mon Sep 17 00:00:00 2001 From: Stephen Mubita Date: Mon, 24 Apr 2023 21:05:35 -0500 Subject: [PATCH 08/56] Add a 'headless' option to run.py to allow wolf_sheep to run headless --- examples/wolf_sheep/run.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/wolf_sheep/run.py b/examples/wolf_sheep/run.py index dc5d367e89d..0f51c273120 100644 --- a/examples/wolf_sheep/run.py +++ b/examples/wolf_sheep/run.py @@ -1,3 +1,9 @@ from wolf_sheep.server import server +import sys -server.launch() +open_browser: bool = True + +if sys.argv[1] == 'headless': + open_browser = False + +server.launch(open_browser=open_browser) From cf4e35794591ea7eff26eaeeab0db8e4c9eef4cf Mon Sep 17 00:00:00 2001 From: Stephen Mubita Date: Mon, 24 Apr 2023 21:22:50 -0500 Subject: [PATCH 09/56] check to make sure sys.argv actually has two or more items --- examples/wolf_sheep/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/wolf_sheep/run.py b/examples/wolf_sheep/run.py index 0f51c273120..8426a52dce9 100644 --- a/examples/wolf_sheep/run.py +++ b/examples/wolf_sheep/run.py @@ -3,7 +3,7 @@ open_browser: bool = True -if sys.argv[1] == 'headless': +if sys.argc >= 2 and sys.argv[1] == 'headless': open_browser = False server.launch(open_browser=open_browser) From c37ae79ffa6255f80b1c7d17e80bf76e82510301 Mon Sep 17 00:00:00 2001 From: Stephen Mubita Date: Tue, 25 Apr 2023 14:44:46 -0500 Subject: [PATCH 10/56] modified wolf_sheep edits to just include the one liine explicit true option; modified most other examples in the same way --- examples/boid_flockers/run.py | 2 +- examples/boltzmann_wealth_model/run.py | 2 +- examples/conways_game_of_life/run.py | 2 +- examples/wolf_sheep/run.py | 8 +------- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/examples/boid_flockers/run.py b/examples/boid_flockers/run.py index be0c1c75c58..0d9ca624248 100644 --- a/examples/boid_flockers/run.py +++ b/examples/boid_flockers/run.py @@ -1,3 +1,3 @@ from boid_flockers.server import server -server.launch() +server.launch(open_browser=True) diff --git a/examples/boltzmann_wealth_model/run.py b/examples/boltzmann_wealth_model/run.py index ea57809eb0a..f17675937cc 100644 --- a/examples/boltzmann_wealth_model/run.py +++ b/examples/boltzmann_wealth_model/run.py @@ -1,3 +1,3 @@ from boltzmann_wealth_model.server import server -server.launch() +server.launch(open_browser=True) diff --git a/examples/conways_game_of_life/run.py b/examples/conways_game_of_life/run.py index 2854fdee59d..7095816577c 100644 --- a/examples/conways_game_of_life/run.py +++ b/examples/conways_game_of_life/run.py @@ -1,3 +1,3 @@ from conways_game_of_life.server import server -server.launch() +server.launch(open_browser=True) diff --git a/examples/wolf_sheep/run.py b/examples/wolf_sheep/run.py index 8426a52dce9..89e3b5488df 100644 --- a/examples/wolf_sheep/run.py +++ b/examples/wolf_sheep/run.py @@ -1,9 +1,3 @@ from wolf_sheep.server import server -import sys -open_browser: bool = True - -if sys.argc >= 2 and sys.argv[1] == 'headless': - open_browser = False - -server.launch(open_browser=open_browser) +server.launch(open_browser=True) From 076de3e92be3d8f7f39b2b05d6927ab1303ca57d Mon Sep 17 00:00:00 2001 From: rht Date: Wed, 17 May 2023 06:09:12 -0400 Subject: [PATCH 11/56] Apply Black to boid_flockers --- examples/boid_flockers/boid_flockers/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/boid_flockers/boid_flockers/model.py b/examples/boid_flockers/boid_flockers/model.py index 00a08d765d5..d694265bb75 100644 --- a/examples/boid_flockers/boid_flockers/model.py +++ b/examples/boid_flockers/boid_flockers/model.py @@ -67,7 +67,7 @@ def make_agents(self): velocity, self.vision, self.separation, - **self.factors + **self.factors, ) self.space.place_agent(boid, pos) self.schedule.add(boid) From deae3c4a9efe63a3031ebd823d098779d027513c Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Sun, 11 Jun 2023 17:33:34 +0200 Subject: [PATCH 12/56] Implement Streamlit UI for Boltzmann wealth model (#36) * Implement Streamlit UI for Boltzmann wealth model * change slider text * change slider text * update readme --------- Co-authored-by: Ankit Kumar --- examples/boltzmann_wealth_model/Readme.md | 6 ++ examples/boltzmann_wealth_model/app.py | 117 ++++++++++++++++++++++ examples/conways_game_of_life/app.py | 80 +++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 examples/boltzmann_wealth_model/app.py create mode 100644 examples/conways_game_of_life/app.py diff --git a/examples/boltzmann_wealth_model/Readme.md b/examples/boltzmann_wealth_model/Readme.md index 84b6b28ad1f..4a6e21f142a 100644 --- a/examples/boltzmann_wealth_model/Readme.md +++ b/examples/boltzmann_wealth_model/Readme.md @@ -33,6 +33,12 @@ If your browser doesn't open automatically, point it to [http://127.0.0.1:8521/] * ``boltzmann_wealth_model/server.py``: Code for the interactive visualization. * ``run.py``: Launches the server. +## Optional + +* ``boltzmann_wealth_model/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 The full tutorial describing how the model is built can be found at: diff --git a/examples/boltzmann_wealth_model/app.py b/examples/boltzmann_wealth_model/app.py new file mode 100644 index 00000000000..8118cc70a76 --- /dev/null +++ b/examples/boltzmann_wealth_model/app.py @@ -0,0 +1,117 @@ +from boltzmann_wealth_model.model import BoltzmannWealthModel + +import streamlit as st + +import time + +import pandas as pd + +import altair as alt + + +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/app.py b/examples/conways_game_of_life/app.py new file mode 100644 index 00000000000..78a55539ee8 --- /dev/null +++ b/examples/conways_game_of_life/app.py @@ -0,0 +1,80 @@ +import mesa + +import streamlit as st + +import time + +import pandas as pd + +import altair as alt + + +import numpy as np +from conways_game_of_life.model import ConwaysGameOfLife + +import pandas as pd + + +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") From 88b143e6cdb90fcb8ea9d8e87a66b0a1c25a01cb Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Sun, 11 Jun 2023 19:02:46 +0200 Subject: [PATCH 13/56] Update Readme.md for Conway's Game of Life (#37) See PR #36 for the app.py implementation if it. * Initialize Game of Life example * set slider text * udpate readme * Implement Streamlit UI for Boltzmann wealth model * resolve merge conflicts --------- Co-authored-by: Ankit Kumar --- examples/conways_game_of_life/Readme.md | 15 +++++++++++---- examples/conways_game_of_life/app.py | 1 - 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/examples/conways_game_of_life/Readme.md b/examples/conways_game_of_life/Readme.md index 686afb4065a..85b591aa713 100644 --- a/examples/conways_game_of_life/Readme.md +++ b/examples/conways_game_of_life/Readme.md @@ -19,12 +19,19 @@ Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and p ## Files -* ``game_of_life/cell.py``: Defines the behavior of an individual cell, which can be in two states: DEAD or ALIVE. -* ``game_of_life/model.py``: Defines the model itself, initialized with a random configuration of alive and dead cells. -* ``game_of_life/portrayal.py``: Describes for the front end how to render a cell. -* ``game_of_live/server.py``: Defines an interactive visualization. +* ``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 index 78a55539ee8..be739caa2cc 100644 --- a/examples/conways_game_of_life/app.py +++ b/examples/conways_game_of_life/app.py @@ -1,5 +1,4 @@ import mesa - import streamlit as st import time From 7250c27163939e839fa9b13766e1404e8e0aa2f0 Mon Sep 17 00:00:00 2001 From: rht Date: Sat, 17 Jun 2023 03:52:59 -0400 Subject: [PATCH 14/56] Package wolf_sheep example --- examples/wolf_sheep/__init__.py | 0 examples/wolf_sheep/wolf_sheep/agents.py | 2 +- examples/wolf_sheep/wolf_sheep/model.py | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 examples/wolf_sheep/__init__.py diff --git a/examples/wolf_sheep/__init__.py b/examples/wolf_sheep/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/wolf_sheep/wolf_sheep/agents.py b/examples/wolf_sheep/wolf_sheep/agents.py index fe62192bf60..91a511fd636 100644 --- a/examples/wolf_sheep/wolf_sheep/agents.py +++ b/examples/wolf_sheep/wolf_sheep/agents.py @@ -1,5 +1,5 @@ import mesa -from wolf_sheep.random_walk import RandomWalker +from .random_walk import RandomWalker class Sheep(RandomWalker): diff --git a/examples/wolf_sheep/wolf_sheep/model.py b/examples/wolf_sheep/wolf_sheep/model.py index 160a1e93e3c..ec23c5a693f 100644 --- a/examples/wolf_sheep/wolf_sheep/model.py +++ b/examples/wolf_sheep/wolf_sheep/model.py @@ -11,8 +11,8 @@ import mesa -from wolf_sheep.scheduler import RandomActivationByTypeFiltered -from wolf_sheep.agents import Sheep, Wolf, GrassPatch +from .scheduler import RandomActivationByTypeFiltered +from .agents import Sheep, Wolf, GrassPatch class WolfSheep(mesa.Model): From 93cf3e12fe594b95a673a330992a6ceb0d8b193f Mon Sep 17 00:00:00 2001 From: rht Date: Sun, 18 Jun 2023 06:05:34 -0400 Subject: [PATCH 15/56] boltzmann wealth model: Apply isort to app.py --- examples/boltzmann_wealth_model/app.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/boltzmann_wealth_model/app.py b/examples/boltzmann_wealth_model/app.py index 8118cc70a76..4e279f26c23 100644 --- a/examples/boltzmann_wealth_model/app.py +++ b/examples/boltzmann_wealth_model/app.py @@ -1,13 +1,10 @@ -from boltzmann_wealth_model.model import BoltzmannWealthModel - -import streamlit as st - import time -import pandas as pd - import altair as alt +import pandas as pd +import streamlit as st +from boltzmann_wealth_model.model import BoltzmannWealthModel model = st.title("Boltzman Wealth Model") num_agents = st.slider( From c3a16bf6c609394dcab731b73e296434aacfc956 Mon Sep 17 00:00:00 2001 From: rht Date: Sun, 25 Jun 2023 08:52:48 -0400 Subject: [PATCH 16/56] Apply isort --- examples/boltzmann_wealth_model/app.py | 1 - examples/conways_game_of_life/app.py | 13 +++---------- .../conways_game_of_life/server.py | 3 +-- examples/wolf_sheep/wolf_sheep/agents.py | 1 + examples/wolf_sheep/wolf_sheep/model.py | 2 +- examples/wolf_sheep/wolf_sheep/scheduler.py | 2 +- examples/wolf_sheep/wolf_sheep/server.py | 3 +-- examples/wolf_sheep/wolf_sheep/test_random_walk.py | 3 +-- 8 files changed, 9 insertions(+), 19 deletions(-) diff --git a/examples/boltzmann_wealth_model/app.py b/examples/boltzmann_wealth_model/app.py index 4e279f26c23..f2dd6da91ab 100644 --- a/examples/boltzmann_wealth_model/app.py +++ b/examples/boltzmann_wealth_model/app.py @@ -3,7 +3,6 @@ import altair as alt import pandas as pd import streamlit as st - from boltzmann_wealth_model.model import BoltzmannWealthModel model = st.title("Boltzman Wealth Model") diff --git a/examples/conways_game_of_life/app.py b/examples/conways_game_of_life/app.py index be739caa2cc..c1623d359da 100644 --- a/examples/conways_game_of_life/app.py +++ b/examples/conways_game_of_life/app.py @@ -1,18 +1,11 @@ -import mesa -import streamlit as st - import time -import pandas as pd - import altair as alt - - +import mesa import numpy as np -from conways_game_of_life.model import ConwaysGameOfLife - 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) 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 index 4167b3d01bd..6da932f3ee6 100644 --- a/examples/conways_game_of_life/conways_game_of_life/server.py +++ b/examples/conways_game_of_life/conways_game_of_life/server.py @@ -1,8 +1,7 @@ import mesa -from .portrayal import portrayCell 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) diff --git a/examples/wolf_sheep/wolf_sheep/agents.py b/examples/wolf_sheep/wolf_sheep/agents.py index 91a511fd636..eef30d5475f 100644 --- a/examples/wolf_sheep/wolf_sheep/agents.py +++ b/examples/wolf_sheep/wolf_sheep/agents.py @@ -1,4 +1,5 @@ import mesa + from .random_walk import RandomWalker diff --git a/examples/wolf_sheep/wolf_sheep/model.py b/examples/wolf_sheep/wolf_sheep/model.py index ec23c5a693f..ac44beff42b 100644 --- a/examples/wolf_sheep/wolf_sheep/model.py +++ b/examples/wolf_sheep/wolf_sheep/model.py @@ -11,8 +11,8 @@ import mesa +from .agents import GrassPatch, Sheep, Wolf from .scheduler import RandomActivationByTypeFiltered -from .agents import Sheep, Wolf, GrassPatch class WolfSheep(mesa.Model): diff --git a/examples/wolf_sheep/wolf_sheep/scheduler.py b/examples/wolf_sheep/wolf_sheep/scheduler.py index cd3dac61f9d..2f5b1c9e05c 100644 --- a/examples/wolf_sheep/wolf_sheep/scheduler.py +++ b/examples/wolf_sheep/wolf_sheep/scheduler.py @@ -1,4 +1,4 @@ -from typing import Type, Callable +from typing import Callable, Type import mesa diff --git a/examples/wolf_sheep/wolf_sheep/server.py b/examples/wolf_sheep/wolf_sheep/server.py index bccf4ec849d..112c1a2dfda 100644 --- a/examples/wolf_sheep/wolf_sheep/server.py +++ b/examples/wolf_sheep/wolf_sheep/server.py @@ -1,6 +1,5 @@ import mesa - -from wolf_sheep.agents import Wolf, Sheep, GrassPatch +from wolf_sheep.agents import GrassPatch, Sheep, Wolf from wolf_sheep.model import WolfSheep diff --git a/examples/wolf_sheep/wolf_sheep/test_random_walk.py b/examples/wolf_sheep/wolf_sheep/test_random_walk.py index ab3b044ab1e..d2340fedba3 100644 --- a/examples/wolf_sheep/wolf_sheep/test_random_walk.py +++ b/examples/wolf_sheep/wolf_sheep/test_random_walk.py @@ -6,8 +6,7 @@ from mesa import Model from mesa.space import MultiGrid from mesa.time import RandomActivation -from mesa.visualization.TextVisualization import TextVisualization, TextGrid - +from mesa.visualization.TextVisualization import TextGrid, TextVisualization from wolf_sheep.random_walk import RandomWalker From 5910dfd9251cab38ddb421b1fa94aeea985b7ca0 Mon Sep 17 00:00:00 2001 From: rht Date: Sun, 25 Jun 2023 08:56:58 -0400 Subject: [PATCH 17/56] Apply ruff --fix --- examples/boid_flockers/boid_flockers/model.py | 2 +- examples/conways_game_of_life/app.py | 1 - examples/wolf_sheep/wolf_sheep/scheduler.py | 4 ++-- examples/wolf_sheep/wolf_sheep/server.py | 1 + examples/wolf_sheep/wolf_sheep/test_random_walk.py | 1 + 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/boid_flockers/boid_flockers/model.py b/examples/boid_flockers/boid_flockers/model.py index d694265bb75..3f7428f0924 100644 --- a/examples/boid_flockers/boid_flockers/model.py +++ b/examples/boid_flockers/boid_flockers/model.py @@ -46,7 +46,7 @@ def __init__( self.separation = separation self.schedule = mesa.time.RandomActivation(self) self.space = mesa.space.ContinuousSpace(width, height, True) - self.factors = dict(cohere=cohere, separate=separate, match=match) + self.factors = {"cohere": cohere, "separate": separate, "match": match} self.make_agents() self.running = True diff --git a/examples/conways_game_of_life/app.py b/examples/conways_game_of_life/app.py index c1623d359da..0977b6f3b43 100644 --- a/examples/conways_game_of_life/app.py +++ b/examples/conways_game_of_life/app.py @@ -1,7 +1,6 @@ import time import altair as alt -import mesa import numpy as np import pandas as pd import streamlit as st diff --git a/examples/wolf_sheep/wolf_sheep/scheduler.py b/examples/wolf_sheep/wolf_sheep/scheduler.py index 2f5b1c9e05c..4279de716d0 100644 --- a/examples/wolf_sheep/wolf_sheep/scheduler.py +++ b/examples/wolf_sheep/wolf_sheep/scheduler.py @@ -1,4 +1,4 @@ -from typing import Callable, Type +from typing import Callable, Optional, Type import mesa @@ -16,7 +16,7 @@ class RandomActivationByTypeFiltered(mesa.time.RandomActivationByType): def get_type_count( self, type_class: Type[mesa.Agent], - filter_func: Callable[[mesa.Agent], bool] = None, + filter_func: Optional[Callable[[mesa.Agent], bool]] = None, ) -> int: """ Returns the current number of agents of certain type in the queue diff --git a/examples/wolf_sheep/wolf_sheep/server.py b/examples/wolf_sheep/wolf_sheep/server.py index 112c1a2dfda..7b1b831a574 100644 --- a/examples/wolf_sheep/wolf_sheep/server.py +++ b/examples/wolf_sheep/wolf_sheep/server.py @@ -1,4 +1,5 @@ import mesa + from wolf_sheep.agents import GrassPatch, Sheep, Wolf from wolf_sheep.model import WolfSheep diff --git a/examples/wolf_sheep/wolf_sheep/test_random_walk.py b/examples/wolf_sheep/wolf_sheep/test_random_walk.py index d2340fedba3..0ba480ccdae 100644 --- a/examples/wolf_sheep/wolf_sheep/test_random_walk.py +++ b/examples/wolf_sheep/wolf_sheep/test_random_walk.py @@ -7,6 +7,7 @@ from mesa.space import MultiGrid from mesa.time import RandomActivation from mesa.visualization.TextVisualization import TextGrid, TextVisualization + from wolf_sheep.random_walk import RandomWalker From e4fcb1c711920835226ec7739cc9dd6073edb262 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 29 Jun 2023 01:45:50 -0400 Subject: [PATCH 18/56] boltzmann wealth model: Replace Streamlit viz with Solara --- examples/boltzmann_wealth_model/Readme.md | 6 +- examples/boltzmann_wealth_model/app.py | 126 +++------------------- 2 files changed, 19 insertions(+), 113 deletions(-) diff --git a/examples/boltzmann_wealth_model/Readme.md b/examples/boltzmann_wealth_model/Readme.md index 4a6e21f142a..9f952ef20f7 100644 --- a/examples/boltzmann_wealth_model/Readme.md +++ b/examples/boltzmann_wealth_model/Readme.md @@ -35,9 +35,9 @@ If your browser doesn't open automatically, point it to [http://127.0.0.1:8521/] ## Optional -* ``boltzmann_wealth_model/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`` +* ``boltzmann_wealth_model/app.py``: can be used to run the simulation via the Solara interface. +* For this, an additional packages ``solara`` needs to be installed. +* Once installed, the app can be opened in the browser after running ``solara run app.py`` ## Further Reading diff --git a/examples/boltzmann_wealth_model/app.py b/examples/boltzmann_wealth_model/app.py index f2dd6da91ab..c1b5aab3b5b 100644 --- a/examples/boltzmann_wealth_model/app.py +++ b/examples/boltzmann_wealth_model/app.py @@ -1,113 +1,19 @@ -import time - -import altair as alt -import pandas as pd -import streamlit as st +from mesa_models.experimental import JupyterViz from boltzmann_wealth_model.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 +model_params = { + "N": { + "type": "SliderInt", + "value": 50, + "label": "Number of agents:", + "min": 10, + "max": 100, + "step": 1, + }, + "width": 10, + "height": 10, +} + +page = JupyterViz( + BoltzmannWealthModel, model_params, measures=["Gini"], name="Money Model" ) -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']) From b0e26d4a1d1d7e4a08cd8a1458c5bee0a7f72bde Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 29 Jun 2023 01:48:53 -0400 Subject: [PATCH 19/56] Simplify boltzmann_wealth_model frontend setup --- examples/boltzmann_wealth_model/Readme.md | 21 ++++------ .../{boltzmann_wealth_model => }/__init__.py | 0 examples/boltzmann_wealth_model/app.py | 4 +- .../boltzmann_wealth_model/server.py | 40 ------------------- .../{boltzmann_wealth_model => }/model.py | 0 .../boltzmann_wealth_model/requirements.txt | 1 + examples/boltzmann_wealth_model/run.py | 3 -- 7 files changed, 11 insertions(+), 58 deletions(-) rename examples/boltzmann_wealth_model/{boltzmann_wealth_model => }/__init__.py (100%) delete mode 100644 examples/boltzmann_wealth_model/boltzmann_wealth_model/server.py rename examples/boltzmann_wealth_model/{boltzmann_wealth_model => }/model.py (100%) delete mode 100644 examples/boltzmann_wealth_model/run.py diff --git a/examples/boltzmann_wealth_model/Readme.md b/examples/boltzmann_wealth_model/Readme.md index 9f952ef20f7..fc27fdb2c28 100644 --- a/examples/boltzmann_wealth_model/Readme.md +++ b/examples/boltzmann_wealth_model/Readme.md @@ -12,32 +12,25 @@ As the model runs, the distribution of wealth among agents goes from being perfe 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) -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: +Make sure to install the requirements first: ``` - $ python server.py + pip install -r requirements.txt ``` -Make sure to install the requirements first: +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: ``` - pip install -r requirements.txt + $ solara run app.py ``` -If your browser doesn't open automatically, point it to [http://127.0.0.1:8521/](http://127.0.0.1:8521/). When the visualization loads, press Reset, then Run. +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 -* ``boltzmann_wealth_model/model.py``: Final version of the model. -* ``boltzmann_wealth_model/server.py``: Code for the interactive visualization. -* ``run.py``: Launches the server. - -## Optional - -* ``boltzmann_wealth_model/app.py``: can be used to run the simulation via the Solara interface. -* For this, an additional packages ``solara`` needs to be installed. -* Once installed, the app can be opened in the browser after running ``solara run app.py`` +* ``model.py``: Final version of the model. +* ``app.py``: Code for the interactive visualization. ## Further Reading diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py b/examples/boltzmann_wealth_model/__init__.py similarity index 100% rename from examples/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py rename to examples/boltzmann_wealth_model/__init__.py diff --git a/examples/boltzmann_wealth_model/app.py b/examples/boltzmann_wealth_model/app.py index c1b5aab3b5b..b87da8fef98 100644 --- a/examples/boltzmann_wealth_model/app.py +++ b/examples/boltzmann_wealth_model/app.py @@ -1,5 +1,6 @@ from mesa_models.experimental import JupyterViz -from boltzmann_wealth_model.model import BoltzmannWealthModel + +from model import BoltzmannWealthModel model_params = { "N": { @@ -17,3 +18,4 @@ page = JupyterViz( BoltzmannWealthModel, model_params, measures=["Gini"], name="Money Model" ) +page diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/server.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/server.py deleted file mode 100644 index a49546ce741..00000000000 --- a/examples/boltzmann_wealth_model/boltzmann_wealth_model/server.py +++ /dev/null @@ -1,40 +0,0 @@ -import mesa - -from .model import BoltzmannWealthModel - - -def agent_portrayal(agent): - portrayal = {"Shape": "circle", "Filled": "true", "r": 0.5} - - if agent.wealth > 0: - portrayal["Color"] = "red" - portrayal["Layer"] = 0 - else: - portrayal["Color"] = "grey" - portrayal["Layer"] = 1 - portrayal["r"] = 0.2 - return portrayal - - -grid = mesa.visualization.CanvasGrid(agent_portrayal, 10, 10, 500, 500) -chart = mesa.visualization.ChartModule( - [{"Label": "Gini", "Color": "#0000FF"}], data_collector_name="datacollector" -) - -model_params = { - "N": mesa.visualization.Slider( - "Number of agents", - 100, - 2, - 200, - 1, - description="Choose how many agents to include in the model", - ), - "width": 10, - "height": 10, -} - -server = mesa.visualization.ModularServer( - BoltzmannWealthModel, [grid, chart], "Money Model", model_params -) -server.port = 8521 diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/boltzmann_wealth_model/model.py similarity index 100% rename from examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py rename to examples/boltzmann_wealth_model/model.py diff --git a/examples/boltzmann_wealth_model/requirements.txt b/examples/boltzmann_wealth_model/requirements.txt index 63b0d24e76d..4d93614c9d4 100644 --- a/examples/boltzmann_wealth_model/requirements.txt +++ b/examples/boltzmann_wealth_model/requirements.txt @@ -1 +1,2 @@ mesa~=1.1 +solara diff --git a/examples/boltzmann_wealth_model/run.py b/examples/boltzmann_wealth_model/run.py deleted file mode 100644 index f17675937cc..00000000000 --- a/examples/boltzmann_wealth_model/run.py +++ /dev/null @@ -1,3 +0,0 @@ -from boltzmann_wealth_model.server import server - -server.launch(open_browser=True) From 8bd395b9d02eb65976943720e8f413b954219057 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 29 Jun 2023 03:13:43 -0400 Subject: [PATCH 20/56] fix: Move agent_portrayal to example-specific file --- examples/boltzmann_wealth_model/app.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/examples/boltzmann_wealth_model/app.py b/examples/boltzmann_wealth_model/app.py index b87da8fef98..04f0b52ca47 100644 --- a/examples/boltzmann_wealth_model/app.py +++ b/examples/boltzmann_wealth_model/app.py @@ -2,6 +2,13 @@ from model import BoltzmannWealthModel + +def agent_portrayal(agent): + if agent.wealth > 0: + return 50 + return 10 + + model_params = { "N": { "type": "SliderInt", @@ -16,6 +23,10 @@ } page = JupyterViz( - BoltzmannWealthModel, model_params, measures=["Gini"], name="Money Model" + BoltzmannWealthModel, + model_params, + measures=["Gini"], + name="Money Model", + agent_portrayal=agent_portrayal, ) page From d8d0e4ef4ad6e698fcc5f30054b63832110755e8 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 29 Jun 2023 03:21:15 -0400 Subject: [PATCH 21/56] feat: Support color in Jupyter viz --- examples/boltzmann_wealth_model/app.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/boltzmann_wealth_model/app.py b/examples/boltzmann_wealth_model/app.py index 04f0b52ca47..0e263c927b7 100644 --- a/examples/boltzmann_wealth_model/app.py +++ b/examples/boltzmann_wealth_model/app.py @@ -4,9 +4,12 @@ def agent_portrayal(agent): + size = 10 + color = "tab:red" if agent.wealth > 0: - return 50 - return 10 + size = 50 + color = "tab:blue" + return {"size": size, "color": color} model_params = { From f6077f9222112f95f3ab0fe0be1e6aa8650e038d Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 29 Jun 2023 06:17:03 -0400 Subject: [PATCH 22/56] Fix Ruff errors --- examples/boltzmann_wealth_model/app.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/boltzmann_wealth_model/app.py b/examples/boltzmann_wealth_model/app.py index 0e263c927b7..0fea75775a8 100644 --- a/examples/boltzmann_wealth_model/app.py +++ b/examples/boltzmann_wealth_model/app.py @@ -1,5 +1,4 @@ from mesa_models.experimental import JupyterViz - from model import BoltzmannWealthModel @@ -32,4 +31,4 @@ def agent_portrayal(agent): name="Money Model", agent_portrayal=agent_portrayal, ) -page +page # noqa From b2d6ff53574f373ecc437c3cba96f38dad7d40e6 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 29 Jun 2023 06:37:43 -0400 Subject: [PATCH 23/56] Add mesa_models to requirements.txt --- examples/boltzmann_wealth_model/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/boltzmann_wealth_model/requirements.txt b/examples/boltzmann_wealth_model/requirements.txt index 4d93614c9d4..cd191a90de6 100644 --- a/examples/boltzmann_wealth_model/requirements.txt +++ b/examples/boltzmann_wealth_model/requirements.txt @@ -1,2 +1,3 @@ mesa~=1.1 solara +git+https://github.com/projectmesa/mesa-examples From fb3fd53207bca786bb2bbf26c9bd399e54b15a86 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 29 Jun 2023 06:42:05 -0400 Subject: [PATCH 24/56] Make separate copies for experimental --- examples/boltzmann_wealth_model/Readme.md | 44 ----------- examples/boltzmann_wealth_model/__init__.py | 0 examples/boltzmann_wealth_model/app.py | 34 --------- examples/boltzmann_wealth_model/model.py | 76 ------------------- .../boltzmann_wealth_model/requirements.txt | 3 - 5 files changed, 157 deletions(-) delete mode 100644 examples/boltzmann_wealth_model/Readme.md delete mode 100644 examples/boltzmann_wealth_model/__init__.py delete mode 100644 examples/boltzmann_wealth_model/app.py delete mode 100644 examples/boltzmann_wealth_model/model.py delete mode 100644 examples/boltzmann_wealth_model/requirements.txt diff --git a/examples/boltzmann_wealth_model/Readme.md b/examples/boltzmann_wealth_model/Readme.md deleted file mode 100644 index fc27fdb2c28..00000000000 --- a/examples/boltzmann_wealth_model/Readme.md +++ /dev/null @@ -1,44 +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. - -## 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/__init__.py b/examples/boltzmann_wealth_model/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/examples/boltzmann_wealth_model/app.py b/examples/boltzmann_wealth_model/app.py deleted file mode 100644 index 0fea75775a8..00000000000 --- a/examples/boltzmann_wealth_model/app.py +++ /dev/null @@ -1,34 +0,0 @@ -from mesa_models.experimental import JupyterViz -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, -} - -page = JupyterViz( - BoltzmannWealthModel, - model_params, - measures=["Gini"], - name="Money Model", - agent_portrayal=agent_portrayal, -) -page # noqa diff --git a/examples/boltzmann_wealth_model/model.py b/examples/boltzmann_wealth_model/model.py deleted file mode 100644 index 0f61b8838d6..00000000000 --- a/examples/boltzmann_wealth_model/model.py +++ /dev/null @@ -1,76 +0,0 @@ -import mesa - - -def compute_gini(model): - agent_wealths = [agent.wealth for agent in model.schedule.agents] - x = sorted(agent_wealths) - N = model.num_agents - B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x)) - return 1 + (1 / N) - 2 * B - - -class 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): - self.num_agents = N - self.grid = mesa.space.MultiGrid(width, height, True) - self.schedule = mesa.time.RandomActivation(self) - self.datacollector = mesa.DataCollector( - model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} - ) - # Create agents - for i in range(self.num_agents): - a = MoneyAgent(i, self) - self.schedule.add(a) - # Add the agent to a random grid cell - x = self.random.randrange(self.grid.width) - y = self.random.randrange(self.grid.height) - self.grid.place_agent(a, (x, y)) - - self.running = True - self.datacollector.collect(self) - - def step(self): - self.schedule.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, unique_id, model): - super().__init__(unique_id, model) - self.wealth = 1 - - def move(self): - possible_steps = self.model.grid.get_neighborhood( - self.pos, moore=True, include_center=False - ) - new_position = self.random.choice(possible_steps) - self.model.grid.move_agent(self, new_position) - - def give_money(self): - cellmates = self.model.grid.get_cell_list_contents([self.pos]) - 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 cd191a90de6..00000000000 --- a/examples/boltzmann_wealth_model/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -mesa~=1.1 -solara -git+https://github.com/projectmesa/mesa-examples From 2d41a8fe7c142a8bb425dc8cf56160495d55609b Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 29 Jun 2023 06:43:15 -0400 Subject: [PATCH 25/56] Add back original boltzmann and schelling --- examples/boltzmann_wealth_model/Readme.md | 51 ++++++++ examples/boltzmann_wealth_model/app.py | 113 ++++++++++++++++++ .../boltzmann_wealth_model/__init__.py | 0 .../boltzmann_wealth_model/model.py | 76 ++++++++++++ .../boltzmann_wealth_model/server.py | 40 +++++++ .../boltzmann_wealth_model/requirements.txt | 1 + examples/boltzmann_wealth_model/run.py | 3 + 7 files changed, 284 insertions(+) create mode 100644 examples/boltzmann_wealth_model/Readme.md create mode 100644 examples/boltzmann_wealth_model/app.py create mode 100644 examples/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py create mode 100644 examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py create mode 100644 examples/boltzmann_wealth_model/boltzmann_wealth_model/server.py create mode 100644 examples/boltzmann_wealth_model/requirements.txt create mode 100644 examples/boltzmann_wealth_model/run.py diff --git a/examples/boltzmann_wealth_model/Readme.md b/examples/boltzmann_wealth_model/Readme.md new file mode 100644 index 00000000000..4a6e21f142a --- /dev/null +++ b/examples/boltzmann_wealth_model/Readme.md @@ -0,0 +1,51 @@ +# 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) + +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: + +``` + $ python server.py +``` + +Make sure to install the requirements first: + +``` + pip install -r requirements.txt +``` + +If your browser doesn't open automatically, point it to [http://127.0.0.1:8521/](http://127.0.0.1:8521/). When the visualization loads, press Reset, then Run. + + +## Files + +* ``boltzmann_wealth_model/model.py``: Final version of the model. +* ``boltzmann_wealth_model/server.py``: Code for the interactive visualization. +* ``run.py``: Launches the server. + +## Optional + +* ``boltzmann_wealth_model/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 + +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 new file mode 100644 index 00000000000..f2dd6da91ab --- /dev/null +++ b/examples/boltzmann_wealth_model/app.py @@ -0,0 +1,113 @@ +import time + +import altair as alt +import pandas as pd +import streamlit as st +from boltzmann_wealth_model.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/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py new file mode 100644 index 00000000000..0f61b8838d6 --- /dev/null +++ b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -0,0 +1,76 @@ +import mesa + + +def compute_gini(model): + agent_wealths = [agent.wealth for agent in model.schedule.agents] + x = sorted(agent_wealths) + N = model.num_agents + B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x)) + return 1 + (1 / N) - 2 * B + + +class 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): + self.num_agents = N + self.grid = mesa.space.MultiGrid(width, height, True) + self.schedule = mesa.time.RandomActivation(self) + self.datacollector = mesa.DataCollector( + model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} + ) + # Create agents + for i in range(self.num_agents): + a = MoneyAgent(i, self) + self.schedule.add(a) + # Add the agent to a random grid cell + x = self.random.randrange(self.grid.width) + y = self.random.randrange(self.grid.height) + self.grid.place_agent(a, (x, y)) + + self.running = True + self.datacollector.collect(self) + + def step(self): + self.schedule.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, unique_id, model): + super().__init__(unique_id, model) + self.wealth = 1 + + def move(self): + possible_steps = self.model.grid.get_neighborhood( + self.pos, moore=True, include_center=False + ) + new_position = self.random.choice(possible_steps) + self.model.grid.move_agent(self, new_position) + + def give_money(self): + cellmates = self.model.grid.get_cell_list_contents([self.pos]) + 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/boltzmann_wealth_model/server.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/server.py new file mode 100644 index 00000000000..a49546ce741 --- /dev/null +++ b/examples/boltzmann_wealth_model/boltzmann_wealth_model/server.py @@ -0,0 +1,40 @@ +import mesa + +from .model import BoltzmannWealthModel + + +def agent_portrayal(agent): + portrayal = {"Shape": "circle", "Filled": "true", "r": 0.5} + + if agent.wealth > 0: + portrayal["Color"] = "red" + portrayal["Layer"] = 0 + else: + portrayal["Color"] = "grey" + portrayal["Layer"] = 1 + portrayal["r"] = 0.2 + return portrayal + + +grid = mesa.visualization.CanvasGrid(agent_portrayal, 10, 10, 500, 500) +chart = mesa.visualization.ChartModule( + [{"Label": "Gini", "Color": "#0000FF"}], data_collector_name="datacollector" +) + +model_params = { + "N": mesa.visualization.Slider( + "Number of agents", + 100, + 2, + 200, + 1, + description="Choose how many agents to include in the model", + ), + "width": 10, + "height": 10, +} + +server = mesa.visualization.ModularServer( + BoltzmannWealthModel, [grid, chart], "Money Model", model_params +) +server.port = 8521 diff --git a/examples/boltzmann_wealth_model/requirements.txt b/examples/boltzmann_wealth_model/requirements.txt new file mode 100644 index 00000000000..63b0d24e76d --- /dev/null +++ b/examples/boltzmann_wealth_model/requirements.txt @@ -0,0 +1 @@ +mesa~=1.1 diff --git a/examples/boltzmann_wealth_model/run.py b/examples/boltzmann_wealth_model/run.py new file mode 100644 index 00000000000..f17675937cc --- /dev/null +++ b/examples/boltzmann_wealth_model/run.py @@ -0,0 +1,3 @@ +from boltzmann_wealth_model.server import server + +server.launch(open_browser=True) From ea9f576edb13ce609030894e06482774ff52fd2c Mon Sep 17 00:00:00 2001 From: rht Date: Sun, 16 Jul 2023 00:04:28 -0400 Subject: [PATCH 26/56] Bump to Mesa 2.0 (#40) * Bump to Mesa 2.0 * Update to Mesa 2.0 API * Update coord_iter to Mesa 2.0 API --------- Co-authored-by: Tom Pike --- examples/boid_flockers/requirements.txt | 2 +- examples/boltzmann_wealth_model/app.py | 2 +- examples/boltzmann_wealth_model/requirements.txt | 2 +- examples/conways_game_of_life/app.py | 2 +- examples/conways_game_of_life/conways_game_of_life/model.py | 2 +- examples/wolf_sheep/requirements.txt | 2 +- examples/wolf_sheep/wolf_sheep/model.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/boid_flockers/requirements.txt b/examples/boid_flockers/requirements.txt index 19b805acb1f..da2b9972efd 100644 --- a/examples/boid_flockers/requirements.txt +++ b/examples/boid_flockers/requirements.txt @@ -1,3 +1,3 @@ jupyter matplotlib -mesa~=1.1 +mesa~=2.0 diff --git a/examples/boltzmann_wealth_model/app.py b/examples/boltzmann_wealth_model/app.py index f2dd6da91ab..c4c92ea837e 100644 --- a/examples/boltzmann_wealth_model/app.py +++ b/examples/boltzmann_wealth_model/app.py @@ -68,7 +68,7 @@ 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 + 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[ diff --git a/examples/boltzmann_wealth_model/requirements.txt b/examples/boltzmann_wealth_model/requirements.txt index 63b0d24e76d..25d263f4e84 100644 --- a/examples/boltzmann_wealth_model/requirements.txt +++ b/examples/boltzmann_wealth_model/requirements.txt @@ -1 +1 @@ -mesa~=1.1 +mesa~=2.0 diff --git a/examples/conways_game_of_life/app.py b/examples/conways_game_of_life/app.py index 0977b6f3b43..884ec523921 100644 --- a/examples/conways_game_of_life/app.py +++ b/examples/conways_game_of_life/app.py @@ -49,7 +49,7 @@ 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(): + 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[ 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 index 581541c11d3..bf2204e0251 100644 --- a/examples/conways_game_of_life/conways_game_of_life/model.py +++ b/examples/conways_game_of_life/conways_game_of_life/model.py @@ -27,7 +27,7 @@ def __init__(self, width=50, height=50): # Place a cell at each location, with some initialized to # ALIVE and some to DEAD. - for contents, x, y in self.grid.coord_iter(): + for contents, (x, y) in self.grid.coord_iter(): cell = Cell((x, y), self) if self.random.random() < 0.1: cell.state = cell.ALIVE diff --git a/examples/wolf_sheep/requirements.txt b/examples/wolf_sheep/requirements.txt index 63b0d24e76d..25d263f4e84 100644 --- a/examples/wolf_sheep/requirements.txt +++ b/examples/wolf_sheep/requirements.txt @@ -1 +1 @@ -mesa~=1.1 +mesa~=2.0 diff --git a/examples/wolf_sheep/wolf_sheep/model.py b/examples/wolf_sheep/wolf_sheep/model.py index ac44beff42b..2626f9581c5 100644 --- a/examples/wolf_sheep/wolf_sheep/model.py +++ b/examples/wolf_sheep/wolf_sheep/model.py @@ -113,7 +113,7 @@ def __init__( # Create grass patches if self.grass: - for agent, x, y in self.grid.coord_iter(): + for agent, (x, y) in self.grid.coord_iter(): fully_grown = self.random.choice([True, False]) if fully_grown: From 43d1ce4fca661102f2c98260cdd4407eb8ad3023 Mon Sep 17 00:00:00 2001 From: Jackie Kazil Date: Tue, 26 Dec 2023 21:46:29 -0600 Subject: [PATCH 27/56] Fix conways_game reqs txt. --- examples/conways_game_of_life/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/conways_game_of_life/requirements.txt b/examples/conways_game_of_life/requirements.txt index 1a7fa12e2a4..ecd07eafe6f 100644 --- a/examples/conways_game_of_life/requirements.txt +++ b/examples/conways_game_of_life/requirements.txt @@ -1 +1 @@ -mesa~=1.2 \ No newline at end of file +mesa~=2.0 \ No newline at end of file From 420f87ec9e00c711f5a58b621baa503287b0629a Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 11 Jan 2024 02:12:32 -0500 Subject: [PATCH 28/56] Apply ruff --fix . --- examples/wolf_sheep/wolf_sheep/server.py | 1 - examples/wolf_sheep/wolf_sheep/test_random_walk.py | 1 - 2 files changed, 2 deletions(-) diff --git a/examples/wolf_sheep/wolf_sheep/server.py b/examples/wolf_sheep/wolf_sheep/server.py index 7b1b831a574..112c1a2dfda 100644 --- a/examples/wolf_sheep/wolf_sheep/server.py +++ b/examples/wolf_sheep/wolf_sheep/server.py @@ -1,5 +1,4 @@ import mesa - from wolf_sheep.agents import GrassPatch, Sheep, Wolf from wolf_sheep.model import WolfSheep diff --git a/examples/wolf_sheep/wolf_sheep/test_random_walk.py b/examples/wolf_sheep/wolf_sheep/test_random_walk.py index 0ba480ccdae..d2340fedba3 100644 --- a/examples/wolf_sheep/wolf_sheep/test_random_walk.py +++ b/examples/wolf_sheep/wolf_sheep/test_random_walk.py @@ -7,7 +7,6 @@ from mesa.space import MultiGrid from mesa.time import RandomActivation from mesa.visualization.TextVisualization import TextGrid, TextVisualization - from wolf_sheep.random_walk import RandomWalker From 2a381e5985d502444ba85774bac6d3251159f47a Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 11 Jan 2024 02:23:17 -0500 Subject: [PATCH 29/56] Apply lint fixes --- examples/boid_flockers/Flocker Test.ipynb | 3 +-- examples/wolf_sheep/wolf_sheep/agents.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/boid_flockers/Flocker Test.ipynb b/examples/boid_flockers/Flocker Test.ipynb index 664019e51fc..82ecc47b99f 100644 --- a/examples/boid_flockers/Flocker Test.ipynb +++ b/examples/boid_flockers/Flocker Test.ipynb @@ -8,9 +8,8 @@ }, "outputs": [], "source": [ - "from boid_flockers.model import BoidFlockers\n", - "import numpy as np\n", "import matplotlib.pyplot as plt\n", + "from boid_flockers.model import BoidFlockers\n", "\n", "%matplotlib inline" ] diff --git a/examples/wolf_sheep/wolf_sheep/agents.py b/examples/wolf_sheep/wolf_sheep/agents.py index eef30d5475f..460c4abb131 100644 --- a/examples/wolf_sheep/wolf_sheep/agents.py +++ b/examples/wolf_sheep/wolf_sheep/agents.py @@ -29,7 +29,7 @@ def step(self): # If there is grass available, eat it this_cell = self.model.grid.get_cell_list_contents([self.pos]) - grass_patch = [obj for obj in this_cell if isinstance(obj, GrassPatch)][0] + grass_patch = next(obj for obj in this_cell if isinstance(obj, GrassPatch)) if grass_patch.fully_grown: self.energy += self.model.sheep_gain_from_food grass_patch.fully_grown = False From a0335e97a397b4b744fd66ddffe28da75da26236 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 11 Jan 2024 17:49:58 -0500 Subject: [PATCH 30/56] fix: Use new API to loop over agents_by_type --- examples/wolf_sheep/wolf_sheep/scheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/wolf_sheep/wolf_sheep/scheduler.py b/examples/wolf_sheep/wolf_sheep/scheduler.py index 4279de716d0..9a7c7d7b27d 100644 --- a/examples/wolf_sheep/wolf_sheep/scheduler.py +++ b/examples/wolf_sheep/wolf_sheep/scheduler.py @@ -23,7 +23,7 @@ def get_type_count( that satisfy the filter function. """ count = 0 - for agent in self.agents_by_type[type_class].values(): + for agent in self.agents_by_type[type_class]: if filter_func is None or filter_func(agent): count += 1 return count From e33bf4e059babcaa7ca5b4d55fb7c3776756cb48 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 11 Jan 2024 18:00:43 -0500 Subject: [PATCH 31/56] Add super().__init__() in model.__init__() --- examples/boid_flockers/boid_flockers/model.py | 1 + examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py | 1 + examples/conways_game_of_life/conways_game_of_life/model.py | 1 + 3 files changed, 3 insertions(+) diff --git a/examples/boid_flockers/boid_flockers/model.py b/examples/boid_flockers/boid_flockers/model.py index 3f7428f0924..22e9dce6711 100644 --- a/examples/boid_flockers/boid_flockers/model.py +++ b/examples/boid_flockers/boid_flockers/model.py @@ -40,6 +40,7 @@ def __init__( keep from any other cohere, separate, match: factors for the relative importance of the three drives.""" + super().__init__() self.population = population self.vision = vision self.speed = speed diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py index 0f61b8838d6..11a3e95878a 100644 --- a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -18,6 +18,7 @@ class BoltzmannWealthModel(mesa.Model): """ def __init__(self, N=100, width=10, height=10): + super().__init__() self.num_agents = N self.grid = mesa.space.MultiGrid(width, height, True) self.schedule = mesa.time.RandomActivation(self) 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 index bf2204e0251..f6c9637a67e 100644 --- a/examples/conways_game_of_life/conways_game_of_life/model.py +++ b/examples/conways_game_of_life/conways_game_of_life/model.py @@ -13,6 +13,7 @@ def __init__(self, width=50, height=50): """ Create a new playing area of (width, height) cells. """ + super().__init__() # Set up the grid and schedule. From 39138a094b97f29fd1e164dd619683015e35ac52 Mon Sep 17 00:00:00 2001 From: rht Date: Thu, 11 Jan 2024 18:54:52 -0500 Subject: [PATCH 32/56] wolf_sheep: Handle the case when type_class not in agents_by_type --- examples/wolf_sheep/wolf_sheep/scheduler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/wolf_sheep/wolf_sheep/scheduler.py b/examples/wolf_sheep/wolf_sheep/scheduler.py index 9a7c7d7b27d..a29a5a770eb 100644 --- a/examples/wolf_sheep/wolf_sheep/scheduler.py +++ b/examples/wolf_sheep/wolf_sheep/scheduler.py @@ -22,6 +22,8 @@ def get_type_count( Returns the current number of agents of certain type in the queue that satisfy the filter function. """ + if type_class not in self.agents_by_type: + return 0 count = 0 for agent in self.agents_by_type[type_class]: if filter_func is None or filter_func(agent): From afc9d429788da1b6202edd2aaa29a0a860ebf3ea Mon Sep 17 00:00:00 2001 From: rht Date: Mon, 8 Jan 2024 19:03:01 -0500 Subject: [PATCH 33/56] Boid flockers: Add Solara viz file --- examples/boid_flockers/app.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 examples/boid_flockers/app.py diff --git a/examples/boid_flockers/app.py b/examples/boid_flockers/app.py new file mode 100644 index 00000000000..30b9fa28ff6 --- /dev/null +++ b/examples/boid_flockers/app.py @@ -0,0 +1,25 @@ +from boid_flockers.model import BoidFlockers +from mesa.experimental import JupyterViz + + +def boid_draw(agent): + return {"color": "tab:red"} + + +model_params = { + "population": 100, + "width": 100, + "height": 100, + "speed": 5, + "vision": 10, + "separation": 2, +} + +page = JupyterViz( + BoidFlockers, + model_params, + measures=[], + name="BoidFlockers", + agent_portrayal=boid_draw, +) +page # noqa From ad8e80fad47db89fe76ba791f8b46dd42dc21f94 Mon Sep 17 00:00:00 2001 From: rht Date: Wed, 24 Jan 2024 04:20:44 -0500 Subject: [PATCH 34/56] Revert "fix: Use new API to loop over agents_by_type" This reverts commit a0335e97a397b4b744fd66ddffe28da75da26236. --- examples/wolf_sheep/wolf_sheep/scheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/wolf_sheep/wolf_sheep/scheduler.py b/examples/wolf_sheep/wolf_sheep/scheduler.py index a29a5a770eb..97424a553a8 100644 --- a/examples/wolf_sheep/wolf_sheep/scheduler.py +++ b/examples/wolf_sheep/wolf_sheep/scheduler.py @@ -25,7 +25,7 @@ def get_type_count( if type_class not in self.agents_by_type: return 0 count = 0 - for agent in self.agents_by_type[type_class]: + for agent in self.agents_by_type[type_class].values(): if filter_func is None or filter_func(agent): count += 1 return count From 7c39970ecf827e77a802beb94aea679a4a7e6de6 Mon Sep 17 00:00:00 2001 From: Achal Jain Date: Sat, 24 Feb 2024 22:50:45 +0530 Subject: [PATCH 35/56] Improve boid flocker model and documentation (#101) * Improve boid flocker model * Update Readme.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update model.py * Update server.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- examples/boid_flockers/Readme.md | 47 ++++--- examples/boid_flockers/app.py | 4 +- .../boid_flockers/SimpleContinuousModule.py | 5 +- examples/boid_flockers/boid_flockers/boid.py | 104 --------------- examples/boid_flockers/boid_flockers/model.py | 123 ++++++++++++++++-- .../boid_flockers/boid_flockers/server.py | 55 +++++++- .../boid_flockers/simple_continuous_canvas.js | 1 - 7 files changed, 193 insertions(+), 146 deletions(-) delete mode 100644 examples/boid_flockers/boid_flockers/boid.py diff --git a/examples/boid_flockers/Readme.md b/examples/boid_flockers/Readme.md index cb3292b4f68..d1f4a987399 100644 --- a/examples/boid_flockers/Readme.md +++ b/examples/boid_flockers/Readme.md @@ -1,34 +1,47 @@ -# Flockers +# 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 -Launch the model: +* 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 Flocker_Server.py + $ 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. +* Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run. ## Files -* [flockers/model.py](flockers/model.py): Core model file; contains the BoidModel class. -* [flockers/boid.py](flockers/boid.py): The Boid agent class. -* [flockers/SimpleContinuousModule.py](flockers/SimpleContinuousModule.py): Defines ``SimpleCanvas``, the Python side of a custom visualization module for drawing agents with continuous positions. -* [flockers/simple_continuous_canvas.js](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. -* [flockers/server.py](flockers/server.py): Sets up the visualization; uses the SimpleCanvas element defined above +* [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. +* [Flocker_Test.ipynb](Flocker_Test.ipynb): Tests the model in a Jupyter notebook. ## Further Reading -======= -* Launch the visualization -``` -$ mesa runserver -``` -* Visit your browser: http://127.0.0.1:8521/ -* In your browser hit *run* +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 index 30b9fa28ff6..5de317feff1 100644 --- a/examples/boid_flockers/app.py +++ b/examples/boid_flockers/app.py @@ -16,8 +16,8 @@ def boid_draw(agent): } page = JupyterViz( - BoidFlockers, - model_params, + model_class=BoidFlockers, + model_params=model_params, measures=[], name="BoidFlockers", agent_portrayal=boid_draw, diff --git a/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py b/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py index 3f3da5dd01e..eabf077c8f6 100644 --- a/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py +++ b/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py @@ -3,11 +3,8 @@ class SimpleCanvas(mesa.visualization.VisualizationElement): local_includes = ["boid_flockers/simple_continuous_canvas.js"] - portrayal_method = None - canvas_height = 500 - canvas_width = 500 - def __init__(self, portrayal_method, canvas_height=500, canvas_width=500): + def __init__(self, portrayal_method=None, canvas_height=500, canvas_width=500): """ Instantiate a new SimpleCanvas """ diff --git a/examples/boid_flockers/boid_flockers/boid.py b/examples/boid_flockers/boid_flockers/boid.py deleted file mode 100644 index f427f9ddbbc..00000000000 --- a/examples/boid_flockers/boid_flockers/boid.py +++ /dev/null @@ -1,104 +0,0 @@ -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 velocity (a vector) - define their movement. Separation is their desired minimum distance from - any other Boid. - """ - - def __init__( - self, - unique_id, - model, - pos, - speed, - velocity, - vision, - separation, - cohere=0.025, - separate=0.25, - match=0.04, - ): - """ - Create a new Boid flocker agent. - - Args: - unique_id: Unique agent identifyer. - pos: Starting position - speed: Distance to move per step. - heading: 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__(unique_id, model) - self.pos = np.array(pos) - self.speed = speed - self.velocity = velocity - self.vision = vision - self.separation = separation - self.cohere_factor = cohere - self.separate_factor = separate - self.match_factor = match - - def cohere(self, neighbors): - """ - Return the vector toward the center of mass of the local neighbors. - """ - cohere = np.zeros(2) - if neighbors: - for neighbor in neighbors: - cohere += self.model.space.get_heading(self.pos, neighbor.pos) - cohere /= len(neighbors) - return cohere - - def separate(self, neighbors): - """ - Return a vector away from any neighbors closer than separation dist. - """ - me = self.pos - them = (n.pos for n in neighbors) - separation_vector = np.zeros(2) - for other in them: - if self.model.space.get_distance(me, other) < self.separation: - separation_vector -= self.model.space.get_heading(me, other) - return separation_vector - - def match_heading(self, neighbors): - """ - Return a vector of the neighbors' average heading. - """ - match_vector = np.zeros(2) - if neighbors: - for neighbor in neighbors: - match_vector += neighbor.velocity - match_vector /= len(neighbors) - return match_vector - - def step(self): - """ - Get the Boid's neighbors, compute the new vector, and move accordingly. - """ - - neighbors = self.model.space.get_neighbors(self.pos, self.vision, False) - self.velocity += ( - self.cohere(neighbors) * self.cohere_factor - + self.separate(neighbors) * self.separate_factor - + self.match_heading(neighbors) * self.match_factor - ) / 2 - self.velocity /= np.linalg.norm(self.velocity) - new_pos = self.pos + self.velocity * self.speed - self.model.space.move_agent(self, new_pos) diff --git a/examples/boid_flockers/boid_flockers/model.py b/examples/boid_flockers/boid_flockers/model.py index 22e9dce6711..ff443b52c7b 100644 --- a/examples/boid_flockers/boid_flockers/model.py +++ b/examples/boid_flockers/boid_flockers/model.py @@ -8,7 +8,108 @@ import mesa import numpy as np -from .boid import Boid + +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, + unique_id, + model, + pos, + speed, + direction, + vision, + separation, + cohere=0.025, + separate=0.25, + match=0.04, + ): + """ + Create a new Boid flocker agent. + + Args: + unique_id: Unique agent identifyer. + pos: Starting position + 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__(unique_id, model) + self.pos = np.array(pos) + 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 cohere(self): + """ + Return the vector toward the center of mass of the local neighbors. + """ + cohere = np.zeros(2) + if self.neighbors: + for neighbor in self.neighbors: + cohere += self.model.space.get_heading(self.pos, neighbor.pos) + cohere /= len(self.neighbors) + return cohere + + def separate(self): + """ + Return a vector away from any neighbors closer than separation dist. + """ + me = self.pos + them = (n.pos for n in self.neighbors) + separation_vector = np.zeros(2) + for other in them: + if self.model.space.get_distance(me, other) < self.separation: + separation_vector -= self.model.space.get_heading(me, other) + return separation_vector + + def match_heading(self): + """ + Return a vector of the neighbors' average heading. + """ + match_vector = np.zeros(2) + if self.neighbors: + for neighbor in self.neighbors: + match_vector += neighbor.direction + match_vector /= len(self.neighbors) + return match_vector + + 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) + self.direction += ( + self.cohere() * self.cohere_factor + + self.separate() * self.separate_factor + + self.match_heading() * self.match_factor + ) / 2 + 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): @@ -39,7 +140,8 @@ def __init__( 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.""" + the three drives. + """ super().__init__() self.population = population self.vision = vision @@ -49,7 +151,6 @@ def __init__( self.space = mesa.space.ContinuousSpace(width, height, True) self.factors = {"cohere": cohere, "separate": separate, "match": match} self.make_agents() - self.running = True def make_agents(self): """ @@ -59,15 +160,15 @@ def make_agents(self): x = self.random.random() * self.space.x_max y = self.random.random() * self.space.y_max pos = np.array((x, y)) - velocity = np.random.random(2) * 2 - 1 + direction = np.random.random(2) * 2 - 1 boid = Boid( - i, - self, - pos, - self.speed, - velocity, - self.vision, - self.separation, + unique_id=i, + model=self, + pos=pos, + speed=self.speed, + direction=direction, + vision=self.vision, + separation=self.separation, **self.factors, ) self.space.place_agent(boid, pos) diff --git a/examples/boid_flockers/boid_flockers/server.py b/examples/boid_flockers/boid_flockers/server.py index 4906df699c7..190c6533abb 100644 --- a/examples/boid_flockers/boid_flockers/server.py +++ b/examples/boid_flockers/boid_flockers/server.py @@ -5,19 +5,60 @@ def boid_draw(agent): - return {"Shape": "circle", "r": 2, "Filled": "true", "Color": "Red"} + 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(boid_draw, 500, 500) + +boid_canvas = SimpleCanvas( + portrayal_method=boid_draw, canvas_height=500, canvas_width=500 +) model_params = { - "population": 100, + "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": 5, - "vision": 10, - "separation": 2, + "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( - BoidFlockers, [boid_canvas], "Boids", model_params + 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 index 20c0ded8732..812cadced8b 100644 --- a/examples/boid_flockers/boid_flockers/simple_continuous_canvas.js +++ b/examples/boid_flockers/boid_flockers/simple_continuous_canvas.js @@ -6,7 +6,6 @@ const ContinuousVisualization = function(width, height, context) { if (p.Shape == "circle") this.drawCircle(p.x, p.y, p.r, p.Color, p.Filled); }; - }; this.drawCircle = function(x, y, radius, color, fill) { From 116ff17f749118e53b73a5c5bb5b762dccbf490a Mon Sep 17 00:00:00 2001 From: Achal Jain Date: Mon, 26 Feb 2024 03:39:28 +0530 Subject: [PATCH 36/56] Improve model to benchmark (#104) --- examples/boid_flockers/boid_flockers/model.py | 74 +++++++------------ 1 file changed, 25 insertions(+), 49 deletions(-) diff --git a/examples/boid_flockers/boid_flockers/model.py b/examples/boid_flockers/boid_flockers/model.py index ff443b52c7b..6ebbf8aa8fa 100644 --- a/examples/boid_flockers/boid_flockers/model.py +++ b/examples/boid_flockers/boid_flockers/model.py @@ -33,15 +33,15 @@ def __init__( direction, vision, separation, - cohere=0.025, - separate=0.25, - match=0.04, + cohere=0.03, + separate=0.015, + match=0.05, ): """ Create a new Boid flocker agent. Args: - unique_id: Unique agent identifyer. + unique_id: Unique agent identifier. pos: Starting position speed: Distance to move per step. direction: numpy vector for the Boid's direction of movement. @@ -62,51 +62,26 @@ def __init__( self.match_factor = match self.neighbors = None - def cohere(self): - """ - Return the vector toward the center of mass of the local neighbors. - """ - cohere = np.zeros(2) - if self.neighbors: - for neighbor in self.neighbors: - cohere += self.model.space.get_heading(self.pos, neighbor.pos) - cohere /= len(self.neighbors) - return cohere - - def separate(self): - """ - Return a vector away from any neighbors closer than separation dist. - """ - me = self.pos - them = (n.pos for n in self.neighbors) - separation_vector = np.zeros(2) - for other in them: - if self.model.space.get_distance(me, other) < self.separation: - separation_vector -= self.model.space.get_heading(me, other) - return separation_vector - - def match_heading(self): - """ - Return a vector of the neighbors' average heading. - """ - match_vector = np.zeros(2) - if self.neighbors: - for neighbor in self.neighbors: - match_vector += neighbor.direction - match_vector /= len(self.neighbors) - return match_vector - 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) - self.direction += ( - self.cohere() * self.cohere_factor - + self.separate() * self.separate_factor - + self.match_heading() * self.match_factor - ) / 2 + 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) @@ -119,15 +94,16 @@ class BoidFlockers(mesa.Model): def __init__( self, + seed=None, population=100, width=100, height=100, - speed=1, vision=10, - separation=2, - cohere=0.025, - separate=0.25, - match=0.04, + speed=1, + separation=1, + cohere=0.03, + separate=0.015, + match=0.05, ): """ Create a new Flockers model. @@ -142,7 +118,7 @@ def __init__( cohere, separate, match: factors for the relative importance of the three drives. """ - super().__init__() + super().__init__(seed=seed) self.population = population self.vision = vision self.speed = speed From e0ab716c3e2c1f3cc986bf8a9aa9a9933a398fe6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 23:16:33 -0400 Subject: [PATCH 37/56] [pre-commit.ci] pre-commit autoupdate (#116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.2.0 → v0.4.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.0...v0.4.3) - [github.com/asottile/pyupgrade: v3.15.0 → v3.15.2](https://github.com/asottile/pyupgrade/compare/v3.15.0...v3.15.2) - [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- examples/boltzmann_wealth_model/app.py | 6 +++--- examples/conways_game_of_life/app.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/boltzmann_wealth_model/app.py b/examples/boltzmann_wealth_model/app.py index c4c92ea837e..97f0f20c0fd 100644 --- a/examples/boltzmann_wealth_model/app.py +++ b/examples/boltzmann_wealth_model/app.py @@ -71,9 +71,9 @@ 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_grid.loc[selected_row.index, "agent_count"] = ( + agent_count # random.choice([1,2]) + ) df_gini = pd.concat( [ diff --git a/examples/conways_game_of_life/app.py b/examples/conways_game_of_life/app.py index 884ec523921..5be8327a35b 100644 --- a/examples/conways_game_of_life/app.py +++ b/examples/conways_game_of_life/app.py @@ -52,9 +52,9 @@ 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]) + df_grid.loc[selected_row.index, "state"] = ( + contents.state + ) # random.choice([1,2]) heatmap = ( alt.Chart(df_grid) From e3b58f5fce1fc12b11369c0209ee37993f67c049 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Thu, 4 Jul 2024 16:15:46 +0200 Subject: [PATCH 38/56] Fix pre-commit Make pre-commit fully green again --- .../boid_flockers/boid_flockers/SimpleContinuousModule.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py b/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py index eabf077c8f6..42b3e9dd76f 100644 --- a/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py +++ b/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py @@ -11,8 +11,8 @@ def __init__(self, portrayal_method=None, canvas_height=500, canvas_width=500): self.portrayal_method = portrayal_method self.canvas_height = canvas_height self.canvas_width = canvas_width - new_element = "new Simple_Continuous_Module({}, {})".format( - self.canvas_width, self.canvas_height + new_element = ( + f"new Simple_Continuous_Module({self.canvas_width}, {self.canvas_height})" ) self.js_code = "elements.push(" + new_element + ");" From 03fe965298579389e4b125c4e59c96a3a793e8a1 Mon Sep 17 00:00:00 2001 From: rht Date: Sat, 10 Aug 2024 07:34:02 -0400 Subject: [PATCH 39/56] Address duplicate position warning (#122) --- examples/boid_flockers/boid_flockers/model.py | 4 ---- examples/wolf_sheep/wolf_sheep/agents.py | 21 +++++++------------ examples/wolf_sheep/wolf_sheep/model.py | 6 +++--- examples/wolf_sheep/wolf_sheep/random_walk.py | 3 +-- 4 files changed, 12 insertions(+), 22 deletions(-) diff --git a/examples/boid_flockers/boid_flockers/model.py b/examples/boid_flockers/boid_flockers/model.py index 6ebbf8aa8fa..8ddfc11a2f4 100644 --- a/examples/boid_flockers/boid_flockers/model.py +++ b/examples/boid_flockers/boid_flockers/model.py @@ -28,7 +28,6 @@ def __init__( self, unique_id, model, - pos, speed, direction, vision, @@ -42,7 +41,6 @@ def __init__( Args: unique_id: Unique agent identifier. - pos: Starting position speed: Distance to move per step. direction: numpy vector for the Boid's direction of movement. vision: Radius to look around for nearby Boids. @@ -52,7 +50,6 @@ def __init__( match: the relative importance of matching neighbors' headings """ super().__init__(unique_id, model) - self.pos = np.array(pos) self.speed = speed self.direction = direction self.vision = vision @@ -140,7 +137,6 @@ def make_agents(self): boid = Boid( unique_id=i, model=self, - pos=pos, speed=self.speed, direction=direction, vision=self.vision, diff --git a/examples/wolf_sheep/wolf_sheep/agents.py b/examples/wolf_sheep/wolf_sheep/agents.py index 460c4abb131..6d73e113a9f 100644 --- a/examples/wolf_sheep/wolf_sheep/agents.py +++ b/examples/wolf_sheep/wolf_sheep/agents.py @@ -12,8 +12,8 @@ class Sheep(RandomWalker): energy = None - def __init__(self, unique_id, pos, model, moore, energy=None): - super().__init__(unique_id, pos, model, moore=moore) + def __init__(self, unique_id, model, moore, energy=None): + super().__init__(unique_id, model, moore=moore) self.energy = energy def step(self): @@ -44,9 +44,7 @@ def step(self): # Create a new sheep: if self.model.grass: self.energy /= 2 - lamb = Sheep( - self.model.next_id(), self.pos, self.model, self.moore, self.energy - ) + lamb = Sheep(self.model.next_id(), self.model, self.moore, self.energy) self.model.grid.place_agent(lamb, self.pos) self.model.schedule.add(lamb) @@ -58,8 +56,8 @@ class Wolf(RandomWalker): energy = None - def __init__(self, unique_id, pos, model, moore, energy=None): - super().__init__(unique_id, pos, model, moore=moore) + def __init__(self, unique_id, model, moore, energy=None): + super().__init__(unique_id, model, moore=moore) self.energy = energy def step(self): @@ -86,10 +84,8 @@ def step(self): if self.random.random() < self.model.wolf_reproduce: # Create a new wolf cub self.energy /= 2 - cub = Wolf( - self.model.next_id(), self.pos, self.model, self.moore, self.energy - ) - self.model.grid.place_agent(cub, cub.pos) + cub = Wolf(self.model.next_id(), self.model, self.moore, self.energy) + self.model.grid.place_agent(cub, self.pos) self.model.schedule.add(cub) @@ -98,7 +94,7 @@ class GrassPatch(mesa.Agent): A patch of grass that grows at a fixed rate and it is eaten by sheep """ - def __init__(self, unique_id, pos, model, fully_grown, countdown): + def __init__(self, unique_id, model, fully_grown, countdown): """ Creates a new patch of grass @@ -109,7 +105,6 @@ def __init__(self, unique_id, pos, model, fully_grown, countdown): super().__init__(unique_id, model) self.fully_grown = fully_grown self.countdown = countdown - self.pos = pos def step(self): if not self.fully_grown: diff --git a/examples/wolf_sheep/wolf_sheep/model.py b/examples/wolf_sheep/wolf_sheep/model.py index 2626f9581c5..48128d00a9f 100644 --- a/examples/wolf_sheep/wolf_sheep/model.py +++ b/examples/wolf_sheep/wolf_sheep/model.py @@ -98,7 +98,7 @@ def __init__( x = self.random.randrange(self.width) y = self.random.randrange(self.height) energy = self.random.randrange(2 * self.sheep_gain_from_food) - sheep = Sheep(self.next_id(), (x, y), self, True, energy) + sheep = Sheep(self.next_id(), self, True, energy) self.grid.place_agent(sheep, (x, y)) self.schedule.add(sheep) @@ -107,7 +107,7 @@ def __init__( x = self.random.randrange(self.width) y = self.random.randrange(self.height) energy = self.random.randrange(2 * self.wolf_gain_from_food) - wolf = Wolf(self.next_id(), (x, y), self, True, energy) + wolf = Wolf(self.next_id(), self, True, energy) self.grid.place_agent(wolf, (x, y)) self.schedule.add(wolf) @@ -121,7 +121,7 @@ def __init__( else: countdown = self.random.randrange(self.grass_regrowth_time) - patch = GrassPatch(self.next_id(), (x, y), self, fully_grown, countdown) + patch = GrassPatch(self.next_id(), self, fully_grown, countdown) self.grid.place_agent(patch, (x, y)) self.schedule.add(patch) diff --git a/examples/wolf_sheep/wolf_sheep/random_walk.py b/examples/wolf_sheep/wolf_sheep/random_walk.py index 49219fa7fff..920ae08ca1d 100644 --- a/examples/wolf_sheep/wolf_sheep/random_walk.py +++ b/examples/wolf_sheep/wolf_sheep/random_walk.py @@ -18,7 +18,7 @@ class RandomWalker(mesa.Agent): y = None moore = True - def __init__(self, unique_id, pos, model, moore=True): + def __init__(self, unique_id, model, moore=True): """ grid: The MultiGrid object in which the agent lives. x: The agent's current x coordinate @@ -27,7 +27,6 @@ def __init__(self, unique_id, pos, model, moore=True): Otherwise, only up, down, left, right. """ super().__init__(unique_id, model) - self.pos = pos self.moore = moore def random_move(self): From 7913feb250cb00fdab628d9810d126b9a20b9b6a Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sun, 11 Aug 2024 12:22:01 +0200 Subject: [PATCH 40/56] Use SolaraViz instead of JupyterViz in examples This updates all the examples to use the renamed and stabilized SolaraViz. --- examples/boid_flockers/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/boid_flockers/app.py b/examples/boid_flockers/app.py index 5de317feff1..c8dd76bcd2c 100644 --- a/examples/boid_flockers/app.py +++ b/examples/boid_flockers/app.py @@ -1,5 +1,5 @@ from boid_flockers.model import BoidFlockers -from mesa.experimental import JupyterViz +from mesa.visualization import SolaraViz def boid_draw(agent): @@ -15,7 +15,7 @@ def boid_draw(agent): "separation": 2, } -page = JupyterViz( +page = SolaraViz( model_class=BoidFlockers, model_params=model_params, measures=[], From 567926407d58e2e9f56b4517d81dd5e6a36ff25b Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 10 Aug 2024 22:10:09 +0200 Subject: [PATCH 41/56] wolf_sheep: Replace custom scheduler with AgentSet functionality This resolves all warnings outputted by this model. For the model step, the behavior of the old RandomActivationByType scheduler when using step(shuffle_types=True, shuffle_agents=True) is replicated. Conceptually, it can be argued that this should be modelled differently. The verbose prints are also removed. --- examples/wolf_sheep/wolf_sheep/agents.py | 8 ++-- examples/wolf_sheep/wolf_sheep/model.py | 49 +++++---------------- examples/wolf_sheep/wolf_sheep/scheduler.py | 31 ------------- 3 files changed, 14 insertions(+), 74 deletions(-) delete mode 100644 examples/wolf_sheep/wolf_sheep/scheduler.py diff --git a/examples/wolf_sheep/wolf_sheep/agents.py b/examples/wolf_sheep/wolf_sheep/agents.py index 6d73e113a9f..688f25096f4 100644 --- a/examples/wolf_sheep/wolf_sheep/agents.py +++ b/examples/wolf_sheep/wolf_sheep/agents.py @@ -37,7 +37,7 @@ def step(self): # Death if self.energy < 0: self.model.grid.remove_agent(self) - self.model.schedule.remove(self) + self.remove() living = False if living and self.random.random() < self.model.sheep_reproduce: @@ -46,7 +46,6 @@ def step(self): self.energy /= 2 lamb = Sheep(self.model.next_id(), self.model, self.moore, self.energy) self.model.grid.place_agent(lamb, self.pos) - self.model.schedule.add(lamb) class Wolf(RandomWalker): @@ -74,19 +73,18 @@ def step(self): # Kill the sheep self.model.grid.remove_agent(sheep_to_eat) - self.model.schedule.remove(sheep_to_eat) + sheep_to_eat.remove() # Death or reproduction if self.energy < 0: self.model.grid.remove_agent(self) - self.model.schedule.remove(self) + self.remove() else: if self.random.random() < self.model.wolf_reproduce: # Create a new wolf cub self.energy /= 2 cub = Wolf(self.model.next_id(), self.model, self.moore, self.energy) self.model.grid.place_agent(cub, self.pos) - self.model.schedule.add(cub) class GrassPatch(mesa.Agent): diff --git a/examples/wolf_sheep/wolf_sheep/model.py b/examples/wolf_sheep/wolf_sheep/model.py index 48128d00a9f..59f1835c0be 100644 --- a/examples/wolf_sheep/wolf_sheep/model.py +++ b/examples/wolf_sheep/wolf_sheep/model.py @@ -12,7 +12,6 @@ import mesa from .agents import GrassPatch, Sheep, Wolf -from .scheduler import RandomActivationByTypeFiltered class WolfSheep(mesa.Model): @@ -35,8 +34,6 @@ class WolfSheep(mesa.Model): grass_regrowth_time = 30 sheep_gain_from_food = 4 - verbose = False # Print-monitoring - description = ( "A model for simulating wolf and sheep (predator-prey) ecosystem modelling." ) @@ -81,14 +78,13 @@ def __init__( self.grass_regrowth_time = grass_regrowth_time self.sheep_gain_from_food = sheep_gain_from_food - self.schedule = RandomActivationByTypeFiltered(self) self.grid = mesa.space.MultiGrid(self.width, self.height, torus=True) self.datacollector = mesa.DataCollector( { - "Wolves": lambda m: m.schedule.get_type_count(Wolf), - "Sheep": lambda m: m.schedule.get_type_count(Sheep), - "Grass": lambda m: m.schedule.get_type_count( - GrassPatch, lambda x: x.fully_grown + "Wolves": lambda m: len(m.get_agents_of_type(Wolf)), + "Sheep": lambda m: len(m.get_agents_of_type(Sheep)), + "Grass": lambda m: len( + m.get_agents_of_type(GrassPatch).select(lambda a: a.fully_grown) ), } ) @@ -100,7 +96,6 @@ def __init__( energy = self.random.randrange(2 * self.sheep_gain_from_food) sheep = Sheep(self.next_id(), self, True, energy) self.grid.place_agent(sheep, (x, y)) - self.schedule.add(sheep) # Create wolves for i in range(self.initial_wolves): @@ -109,7 +104,6 @@ def __init__( energy = self.random.randrange(2 * self.wolf_gain_from_food) wolf = Wolf(self.next_id(), self, True, energy) self.grid.place_agent(wolf, (x, y)) - self.schedule.add(wolf) # Create grass patches if self.grass: @@ -123,42 +117,21 @@ def __init__( patch = GrassPatch(self.next_id(), self, fully_grown, countdown) self.grid.place_agent(patch, (x, y)) - self.schedule.add(patch) self.running = True self.datacollector.collect(self) def step(self): - self.schedule.step() + # 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.get_agents_of_type(agent_type).do("step") + # collect data self.datacollector.collect(self) - if self.verbose: - print( - [ - self.schedule.time, - self.schedule.get_type_count(Wolf), - self.schedule.get_type_count(Sheep), - self.schedule.get_type_count(GrassPatch, lambda x: x.fully_grown), - ] - ) def run_model(self, step_count=200): - if self.verbose: - print("Initial number wolves: ", self.schedule.get_type_count(Wolf)) - print("Initial number sheep: ", self.schedule.get_type_count(Sheep)) - print( - "Initial number grass: ", - self.schedule.get_type_count(GrassPatch, lambda x: x.fully_grown), - ) - for i in range(step_count): self.step() - - if self.verbose: - print("") - print("Final number wolves: ", self.schedule.get_type_count(Wolf)) - print("Final number sheep: ", self.schedule.get_type_count(Sheep)) - print( - "Final number grass: ", - self.schedule.get_type_count(GrassPatch, lambda x: x.fully_grown), - ) diff --git a/examples/wolf_sheep/wolf_sheep/scheduler.py b/examples/wolf_sheep/wolf_sheep/scheduler.py deleted file mode 100644 index 97424a553a8..00000000000 --- a/examples/wolf_sheep/wolf_sheep/scheduler.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Callable, Optional, Type - -import mesa - - -class RandomActivationByTypeFiltered(mesa.time.RandomActivationByType): - """ - A scheduler that overrides the get_type_count method to allow for filtering - of agents by a function before counting. - - Example: - >>> scheduler = RandomActivationByTypeFiltered(model) - >>> scheduler.get_type_count(AgentA, lambda agent: agent.some_attribute > 10) - """ - - def get_type_count( - self, - type_class: Type[mesa.Agent], - filter_func: Optional[Callable[[mesa.Agent], bool]] = None, - ) -> int: - """ - Returns the current number of agents of certain type in the queue - that satisfy the filter function. - """ - if type_class not in self.agents_by_type: - return 0 - count = 0 - for agent in self.agents_by_type[type_class].values(): - if filter_func is None or filter_func(agent): - count += 1 - return count From 4700d276326b5fd97e9acae283d50bfbc8356ce2 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 17 Aug 2024 21:33:56 +0200 Subject: [PATCH 42/56] Replace model.schedule.agents with model.agents AgentSet Replace all usages of model.schedule.agents with the use of the model.agents AgentSet in all examples. --- examples/boid_flockers/Flocker Test.ipynb | 2 +- examples/boid_flockers/boid_flockers/SimpleContinuousModule.py | 2 +- examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/boid_flockers/Flocker Test.ipynb b/examples/boid_flockers/Flocker Test.ipynb index 82ecc47b99f..c757f3a88ed 100644 --- a/examples/boid_flockers/Flocker Test.ipynb +++ b/examples/boid_flockers/Flocker Test.ipynb @@ -25,7 +25,7 @@ "def draw_boids(model):\n", " x_vals = []\n", " y_vals = []\n", - " for boid in model.schedule.agents:\n", + " for boid in model.agents:\n", " x, y = boid.pos\n", " x_vals.append(x)\n", " y_vals.append(y)\n", diff --git a/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py b/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py index 42b3e9dd76f..ec670d7af1c 100644 --- a/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py +++ b/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py @@ -18,7 +18,7 @@ def __init__(self, portrayal_method=None, canvas_height=500, canvas_width=500): def render(self, model): space_state = [] - for obj in model.schedule.agents: + 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) diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py index 11a3e95878a..1e1f3973d04 100644 --- a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -2,7 +2,7 @@ def compute_gini(model): - agent_wealths = [agent.wealth for agent in model.schedule.agents] + 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)) From f857216098619a5e0be3b6f70861185f9459ba90 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 17 Aug 2024 21:47:16 +0200 Subject: [PATCH 43/56] Replace RandomActivation scheduler with AgentSet Replace the old RandomActivation scheduler with AgentSet functionality. self.agents.shuffle.do("step") is equivalent and self.schedule.step() with an RandomActivation scheduler, and model behavior should not change. --- examples/boid_flockers/boid_flockers/model.py | 4 ++-- .../boltzmann_wealth_model/boltzmann_wealth_model/model.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/boid_flockers/boid_flockers/model.py b/examples/boid_flockers/boid_flockers/model.py index 8ddfc11a2f4..f9cd39344fe 100644 --- a/examples/boid_flockers/boid_flockers/model.py +++ b/examples/boid_flockers/boid_flockers/model.py @@ -120,7 +120,7 @@ def __init__( self.vision = vision self.speed = speed self.separation = separation - self.schedule = mesa.time.RandomActivation(self) + self.space = mesa.space.ContinuousSpace(width, height, True) self.factors = {"cohere": cohere, "separate": separate, "match": match} self.make_agents() @@ -147,4 +147,4 @@ def make_agents(self): self.schedule.add(boid) def step(self): - self.schedule.step() + self.agents.shuffle().do("step") diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py index 1e1f3973d04..9a16a69e90b 100644 --- a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -21,7 +21,7 @@ def __init__(self, N=100, width=10, height=10): super().__init__() self.num_agents = N self.grid = mesa.space.MultiGrid(width, height, True) - self.schedule = mesa.time.RandomActivation(self) + self.datacollector = mesa.DataCollector( model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} ) @@ -38,7 +38,7 @@ def __init__(self, N=100, width=10, height=10): self.datacollector.collect(self) def step(self): - self.schedule.step() + self.agents.shuffle().do("step") # collect data self.datacollector.collect(self) From c0a2b2998fe2586b1ee2af1f44c66f892d501917 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 17 Aug 2024 22:04:10 +0200 Subject: [PATCH 44/56] Replace SimultaneousActivation scheduler by AgentSet The SimultaneousActivation scheduler used a "step" and "advanced" function in the Agent. These have been renamed for each model to be more descriptive (like "determine_state" and "assume_state"), and then the model step was replace by something like: self.agents.do("determine_state") self.agents.do("assume_state") Docstring was also updated accordingly. --- .../conways_game_of_life/cell.py | 4 ++-- .../conways_game_of_life/model.py | 16 +++++----------- 2 files changed, 7 insertions(+), 13 deletions(-) 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 index 8639288d4ca..d9e0e7ba076 100644 --- a/examples/conways_game_of_life/conways_game_of_life/cell.py +++ b/examples/conways_game_of_life/conways_game_of_life/cell.py @@ -24,7 +24,7 @@ def isAlive(self): def neighbors(self): return self.model.grid.iter_neighbors((self.x, self.y), True) - def step(self): + 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 @@ -46,7 +46,7 @@ def step(self): if live_neighbors == 3: self._nextState = self.ALIVE - def advance(self): + def assume_state(self): """ Set the state to the new computed state -- computed in step(). """ 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 index f6c9637a67e..387de9ec9b5 100644 --- a/examples/conways_game_of_life/conways_game_of_life/model.py +++ b/examples/conways_game_of_life/conways_game_of_life/model.py @@ -14,15 +14,6 @@ def __init__(self, width=50, height=50): Create a new playing area of (width, height) cells. """ super().__init__() - - # Set up the grid and schedule. - - # Use SimultaneousActivation which simulates all the cells - # computing their next state simultaneously. This needs to - # be done because each cell's next state depends on the current - # state of all its neighbors -- before they've changed. - self.schedule = mesa.time.SimultaneousActivation(self) - # Use a simple grid, where edges wrap around. self.grid = mesa.space.SingleGrid(width, height, torus=True) @@ -39,6 +30,9 @@ def __init__(self, width=50, height=50): def step(self): """ - Have the scheduler advance each cell by one step + 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.schedule.step() + self.agents.do("determine_state") + self.agents.do("assume_state") From ad9a13af88e11cb6ec59a67c4d63290b8af6b3aa Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 17 Aug 2024 22:10:19 +0200 Subject: [PATCH 45/56] Remove adding agents to schedulers Schedulers don't exist anymore, so we don't need to add agents anymore. Agents are automatically added to model.agents on Agent initialisation. --- examples/boid_flockers/boid_flockers/model.py | 1 - examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py | 2 +- examples/conways_game_of_life/conways_game_of_life/model.py | 1 - examples/wolf_sheep/wolf_sheep/test_random_walk.py | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/boid_flockers/boid_flockers/model.py b/examples/boid_flockers/boid_flockers/model.py index f9cd39344fe..6b032c33592 100644 --- a/examples/boid_flockers/boid_flockers/model.py +++ b/examples/boid_flockers/boid_flockers/model.py @@ -144,7 +144,6 @@ def make_agents(self): **self.factors, ) self.space.place_agent(boid, pos) - self.schedule.add(boid) def step(self): self.agents.shuffle().do("step") diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py index 9a16a69e90b..c34d0937dd3 100644 --- a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -28,7 +28,7 @@ def __init__(self, N=100, width=10, height=10): # Create agents for i in range(self.num_agents): a = MoneyAgent(i, self) - self.schedule.add(a) + # Add the agent to a random grid cell x = self.random.randrange(self.grid.width) y = self.random.randrange(self.grid.height) 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 index 387de9ec9b5..76d9ca9fef4 100644 --- a/examples/conways_game_of_life/conways_game_of_life/model.py +++ b/examples/conways_game_of_life/conways_game_of_life/model.py @@ -24,7 +24,6 @@ def __init__(self, width=50, height=50): if self.random.random() < 0.1: cell.state = cell.ALIVE self.grid.place_agent(cell, (x, y)) - self.schedule.add(cell) self.running = True diff --git a/examples/wolf_sheep/wolf_sheep/test_random_walk.py b/examples/wolf_sheep/wolf_sheep/test_random_walk.py index d2340fedba3..92bfdcd38f5 100644 --- a/examples/wolf_sheep/wolf_sheep/test_random_walk.py +++ b/examples/wolf_sheep/wolf_sheep/test_random_walk.py @@ -46,7 +46,7 @@ def __init__(self, width, height, agent_count): x = self.random.randrange(self.width) y = self.random.randrange(self.height) a = WalkerAgent(i, (x, y), self, True) - self.schedule.add(a) + self.grid.place_agent(a, (x, y)) def step(self): From 49e392bd9bc061806d83317b1afb277a2fae63aa Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 17 Aug 2024 22:25:58 +0200 Subject: [PATCH 46/56] Restore Sugerscape and Wolf-sheep schedulers These models use complex schedulers which warrant an separate PR. --- examples/wolf_sheep/wolf_sheep/test_random_walk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/wolf_sheep/wolf_sheep/test_random_walk.py b/examples/wolf_sheep/wolf_sheep/test_random_walk.py index 92bfdcd38f5..d2340fedba3 100644 --- a/examples/wolf_sheep/wolf_sheep/test_random_walk.py +++ b/examples/wolf_sheep/wolf_sheep/test_random_walk.py @@ -46,7 +46,7 @@ def __init__(self, width, height, agent_count): x = self.random.randrange(self.width) y = self.random.randrange(self.height) a = WalkerAgent(i, (x, y), self, True) - + self.schedule.add(a) self.grid.place_agent(a, (x, y)) def step(self): From 36b6a9d2cb407264ad173f4404ed533ac339f3d0 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Thu, 22 Aug 2024 15:13:51 +0200 Subject: [PATCH 47/56] Revert PR #161: Replace schedulers with AgentSet functionality (#170) This commit reverts PR #161 https://github.com/projectmesa/mesa-examples/pull/161 That PR assumed that time advancement would be done automatically, like proposed in https://github.com/projectmesa/mesa/pull/2223 We encountered some underlying issues with time, which we couldn't resolve in time. --- examples/boid_flockers/Flocker Test.ipynb | 2 +- .../boid_flockers/SimpleContinuousModule.py | 2 +- examples/boid_flockers/boid_flockers/model.py | 5 +++-- .../boltzmann_wealth_model/model.py | 8 ++++---- .../conways_game_of_life/cell.py | 4 ++-- .../conways_game_of_life/model.py | 17 ++++++++++++----- 6 files changed, 23 insertions(+), 15 deletions(-) diff --git a/examples/boid_flockers/Flocker Test.ipynb b/examples/boid_flockers/Flocker Test.ipynb index c757f3a88ed..82ecc47b99f 100644 --- a/examples/boid_flockers/Flocker Test.ipynb +++ b/examples/boid_flockers/Flocker Test.ipynb @@ -25,7 +25,7 @@ "def draw_boids(model):\n", " x_vals = []\n", " y_vals = []\n", - " for boid in model.agents:\n", + " for boid in model.schedule.agents:\n", " x, y = boid.pos\n", " x_vals.append(x)\n", " y_vals.append(y)\n", diff --git a/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py b/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py index ec670d7af1c..42b3e9dd76f 100644 --- a/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py +++ b/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py @@ -18,7 +18,7 @@ def __init__(self, portrayal_method=None, canvas_height=500, canvas_width=500): def render(self, model): space_state = [] - for obj in model.agents: + for obj in model.schedule.agents: portrayal = self.portrayal_method(obj) x, y = obj.pos x = (x - model.space.x_min) / (model.space.x_max - model.space.x_min) diff --git a/examples/boid_flockers/boid_flockers/model.py b/examples/boid_flockers/boid_flockers/model.py index 6b032c33592..8ddfc11a2f4 100644 --- a/examples/boid_flockers/boid_flockers/model.py +++ b/examples/boid_flockers/boid_flockers/model.py @@ -120,7 +120,7 @@ def __init__( self.vision = vision self.speed = speed self.separation = separation - + self.schedule = mesa.time.RandomActivation(self) self.space = mesa.space.ContinuousSpace(width, height, True) self.factors = {"cohere": cohere, "separate": separate, "match": match} self.make_agents() @@ -144,6 +144,7 @@ def make_agents(self): **self.factors, ) self.space.place_agent(boid, pos) + self.schedule.add(boid) def step(self): - self.agents.shuffle().do("step") + self.schedule.step() diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py index c34d0937dd3..11a3e95878a 100644 --- a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -2,7 +2,7 @@ def compute_gini(model): - agent_wealths = [agent.wealth for agent in model.agents] + agent_wealths = [agent.wealth for agent in model.schedule.agents] x = sorted(agent_wealths) N = model.num_agents B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x)) @@ -21,14 +21,14 @@ def __init__(self, N=100, width=10, height=10): super().__init__() self.num_agents = N self.grid = mesa.space.MultiGrid(width, height, True) - + self.schedule = mesa.time.RandomActivation(self) self.datacollector = mesa.DataCollector( model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} ) # Create agents for i in range(self.num_agents): a = MoneyAgent(i, self) - + self.schedule.add(a) # Add the agent to a random grid cell x = self.random.randrange(self.grid.width) y = self.random.randrange(self.grid.height) @@ -38,7 +38,7 @@ def __init__(self, N=100, width=10, height=10): self.datacollector.collect(self) def step(self): - self.agents.shuffle().do("step") + self.schedule.step() # collect data self.datacollector.collect(self) 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 index d9e0e7ba076..8639288d4ca 100644 --- a/examples/conways_game_of_life/conways_game_of_life/cell.py +++ b/examples/conways_game_of_life/conways_game_of_life/cell.py @@ -24,7 +24,7 @@ def isAlive(self): def neighbors(self): return self.model.grid.iter_neighbors((self.x, self.y), True) - def determine_state(self): + def step(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 @@ -46,7 +46,7 @@ def determine_state(self): if live_neighbors == 3: self._nextState = self.ALIVE - def assume_state(self): + def advance(self): """ Set the state to the new computed state -- computed in step(). """ 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 index 76d9ca9fef4..f6c9637a67e 100644 --- a/examples/conways_game_of_life/conways_game_of_life/model.py +++ b/examples/conways_game_of_life/conways_game_of_life/model.py @@ -14,6 +14,15 @@ def __init__(self, width=50, height=50): Create a new playing area of (width, height) cells. """ super().__init__() + + # Set up the grid and schedule. + + # Use SimultaneousActivation which simulates all the cells + # computing their next state simultaneously. This needs to + # be done because each cell's next state depends on the current + # state of all its neighbors -- before they've changed. + self.schedule = mesa.time.SimultaneousActivation(self) + # Use a simple grid, where edges wrap around. self.grid = mesa.space.SingleGrid(width, height, torus=True) @@ -24,14 +33,12 @@ def __init__(self, width=50, height=50): if self.random.random() < 0.1: cell.state = cell.ALIVE self.grid.place_agent(cell, (x, y)) + self.schedule.add(cell) 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 + Have the scheduler advance each cell by one step """ - self.agents.do("determine_state") - self.agents.do("assume_state") + self.schedule.step() From 915c20890f6c4f132720709025de318505fe08e8 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Wed, 28 Aug 2024 09:49:20 +0200 Subject: [PATCH 48/56] Ensure grasspatches info is only collected if gras is true (#181) the datacollector allways includes grasspatches even if grass is False. This is breaking (the proposed changes to agent storage in Model. The underlying problem is that Model.get_agents_of_type now raises a KeyError if type does not exist. I think this is desirable behavior becuase errrors should not be passed over in silence. But this requires fixing the wolf sheep example as done here. --- examples/wolf_sheep/wolf_sheep/model.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/wolf_sheep/wolf_sheep/model.py b/examples/wolf_sheep/wolf_sheep/model.py index 59f1835c0be..12db261a86a 100644 --- a/examples/wolf_sheep/wolf_sheep/model.py +++ b/examples/wolf_sheep/wolf_sheep/model.py @@ -79,15 +79,16 @@ def __init__( self.sheep_gain_from_food = sheep_gain_from_food self.grid = mesa.space.MultiGrid(self.width, self.height, torus=True) - self.datacollector = mesa.DataCollector( - { - "Wolves": lambda m: len(m.get_agents_of_type(Wolf)), - "Sheep": lambda m: len(m.get_agents_of_type(Sheep)), - "Grass": lambda m: len( - m.get_agents_of_type(GrassPatch).select(lambda a: a.fully_grown) - ), - } - ) + + collectors = { + "Wolves": lambda m: len(m.get_agents_of_type(Wolf)), + "Sheep": lambda m: len(m.get_agents_of_type(Sheep)), + } + + if grass: + collectors["Grass"] = lambda m: len(m.get_agents_of_type(GrassPatch)) + + self.datacollector = mesa.DataCollector(collectors) # Create sheep: for i in range(self.initial_sheep): From a8df6c6b033caab4f898ca93aceb16821218a1a0 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Fri, 30 Aug 2024 10:30:22 +0200 Subject: [PATCH 49/56] Reinstate PR #161: Replace schedulers with AgentSet functionality This reinstates PR #161 after the previous revert. --- examples/boid_flockers/Flocker Test.ipynb | 2 +- .../boid_flockers/SimpleContinuousModule.py | 2 +- examples/boid_flockers/boid_flockers/model.py | 5 ++--- .../boltzmann_wealth_model/model.py | 8 ++++---- .../conways_game_of_life/cell.py | 4 ++-- .../conways_game_of_life/model.py | 17 +++++------------ 6 files changed, 15 insertions(+), 23 deletions(-) diff --git a/examples/boid_flockers/Flocker Test.ipynb b/examples/boid_flockers/Flocker Test.ipynb index 82ecc47b99f..c757f3a88ed 100644 --- a/examples/boid_flockers/Flocker Test.ipynb +++ b/examples/boid_flockers/Flocker Test.ipynb @@ -25,7 +25,7 @@ "def draw_boids(model):\n", " x_vals = []\n", " y_vals = []\n", - " for boid in model.schedule.agents:\n", + " for boid in model.agents:\n", " x, y = boid.pos\n", " x_vals.append(x)\n", " y_vals.append(y)\n", diff --git a/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py b/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py index 42b3e9dd76f..ec670d7af1c 100644 --- a/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py +++ b/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py @@ -18,7 +18,7 @@ def __init__(self, portrayal_method=None, canvas_height=500, canvas_width=500): def render(self, model): space_state = [] - for obj in model.schedule.agents: + 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) diff --git a/examples/boid_flockers/boid_flockers/model.py b/examples/boid_flockers/boid_flockers/model.py index 8ddfc11a2f4..6b032c33592 100644 --- a/examples/boid_flockers/boid_flockers/model.py +++ b/examples/boid_flockers/boid_flockers/model.py @@ -120,7 +120,7 @@ def __init__( self.vision = vision self.speed = speed self.separation = separation - self.schedule = mesa.time.RandomActivation(self) + self.space = mesa.space.ContinuousSpace(width, height, True) self.factors = {"cohere": cohere, "separate": separate, "match": match} self.make_agents() @@ -144,7 +144,6 @@ def make_agents(self): **self.factors, ) self.space.place_agent(boid, pos) - self.schedule.add(boid) def step(self): - self.schedule.step() + self.agents.shuffle().do("step") diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py index 11a3e95878a..c34d0937dd3 100644 --- a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -2,7 +2,7 @@ def compute_gini(model): - agent_wealths = [agent.wealth for agent in model.schedule.agents] + 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)) @@ -21,14 +21,14 @@ def __init__(self, N=100, width=10, height=10): super().__init__() self.num_agents = N self.grid = mesa.space.MultiGrid(width, height, True) - self.schedule = mesa.time.RandomActivation(self) + self.datacollector = mesa.DataCollector( model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} ) # Create agents for i in range(self.num_agents): a = MoneyAgent(i, self) - self.schedule.add(a) + # Add the agent to a random grid cell x = self.random.randrange(self.grid.width) y = self.random.randrange(self.grid.height) @@ -38,7 +38,7 @@ def __init__(self, N=100, width=10, height=10): self.datacollector.collect(self) def step(self): - self.schedule.step() + self.agents.shuffle().do("step") # collect data self.datacollector.collect(self) 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 index 8639288d4ca..d9e0e7ba076 100644 --- a/examples/conways_game_of_life/conways_game_of_life/cell.py +++ b/examples/conways_game_of_life/conways_game_of_life/cell.py @@ -24,7 +24,7 @@ def isAlive(self): def neighbors(self): return self.model.grid.iter_neighbors((self.x, self.y), True) - def step(self): + 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 @@ -46,7 +46,7 @@ def step(self): if live_neighbors == 3: self._nextState = self.ALIVE - def advance(self): + def assume_state(self): """ Set the state to the new computed state -- computed in step(). """ 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 index f6c9637a67e..76d9ca9fef4 100644 --- a/examples/conways_game_of_life/conways_game_of_life/model.py +++ b/examples/conways_game_of_life/conways_game_of_life/model.py @@ -14,15 +14,6 @@ def __init__(self, width=50, height=50): Create a new playing area of (width, height) cells. """ super().__init__() - - # Set up the grid and schedule. - - # Use SimultaneousActivation which simulates all the cells - # computing their next state simultaneously. This needs to - # be done because each cell's next state depends on the current - # state of all its neighbors -- before they've changed. - self.schedule = mesa.time.SimultaneousActivation(self) - # Use a simple grid, where edges wrap around. self.grid = mesa.space.SingleGrid(width, height, torus=True) @@ -33,12 +24,14 @@ def __init__(self, width=50, height=50): if self.random.random() < 0.1: cell.state = cell.ALIVE self.grid.place_agent(cell, (x, y)) - self.schedule.add(cell) self.running = True def step(self): """ - Have the scheduler advance each cell by one step + 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.schedule.step() + self.agents.do("determine_state") + self.agents.do("assume_state") From a5d16bfbcdcfafd1c2f1f35adf6c078364c97ddf Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Tue, 3 Sep 2024 18:49:47 +0200 Subject: [PATCH 50/56] Replace `get_agents_of_type` method with `agents_by_type` property (#190) The Model method `get_agents_of_type()` is replaced by the `agents_by_type` property, which directly returns the dict. Instead of using: ```Python model.get_agents_of_type(Wolf) ``` You should now use: ```Python model.agents_by_type[Wolf] ``` --- examples/wolf_sheep/wolf_sheep/model.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/wolf_sheep/wolf_sheep/model.py b/examples/wolf_sheep/wolf_sheep/model.py index 12db261a86a..1dc1e1a53e7 100644 --- a/examples/wolf_sheep/wolf_sheep/model.py +++ b/examples/wolf_sheep/wolf_sheep/model.py @@ -81,12 +81,12 @@ def __init__( self.grid = mesa.space.MultiGrid(self.width, self.height, torus=True) collectors = { - "Wolves": lambda m: len(m.get_agents_of_type(Wolf)), - "Sheep": lambda m: len(m.get_agents_of_type(Sheep)), + "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.get_agents_of_type(GrassPatch)) + collectors["Grass"] = lambda m: len(m.agents_by_type[GrassPatch]) self.datacollector = mesa.DataCollector(collectors) @@ -128,7 +128,7 @@ def step(self): # 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.get_agents_of_type(agent_type).do("step") + self.agents_by_type[agent_type].do("step") # collect data self.datacollector.collect(self) From 2f0427ef48206a44a31ac844623081061f7b866c Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Thu, 5 Sep 2024 11:37:12 +0200 Subject: [PATCH 51/56] Remove unique_id and model.next_id (#194) All examples can be updated to no longer pass a unique id, nor use model.next_id. This only fixes the examples, not the gis-examples or rl examples. Co-authored-by: Ewout ter Hoeven --- examples/boid_flockers/boid_flockers/model.py | 7 ++----- .../boltzmann_wealth_model/model.py | 8 ++++---- .../conways_game_of_life/cell.py | 2 +- examples/wolf_sheep/wolf_sheep/agents.py | 16 ++++++++-------- examples/wolf_sheep/wolf_sheep/model.py | 8 ++++---- examples/wolf_sheep/wolf_sheep/random_walk.py | 4 ++-- 6 files changed, 21 insertions(+), 24 deletions(-) diff --git a/examples/boid_flockers/boid_flockers/model.py b/examples/boid_flockers/boid_flockers/model.py index 6b032c33592..312986bd207 100644 --- a/examples/boid_flockers/boid_flockers/model.py +++ b/examples/boid_flockers/boid_flockers/model.py @@ -26,7 +26,6 @@ class Boid(mesa.Agent): def __init__( self, - unique_id, model, speed, direction, @@ -40,7 +39,6 @@ def __init__( Create a new Boid flocker agent. Args: - unique_id: Unique agent identifier. speed: Distance to move per step. direction: numpy vector for the Boid's direction of movement. vision: Radius to look around for nearby Boids. @@ -49,7 +47,7 @@ def __init__( separate: the relative importance of avoiding close neighbors match: the relative importance of matching neighbors' headings """ - super().__init__(unique_id, model) + super().__init__(model) self.speed = speed self.direction = direction self.vision = vision @@ -129,13 +127,12 @@ def make_agents(self): """ Create self.population agents, with random positions and starting headings. """ - for i in range(self.population): + 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( - unique_id=i, model=self, speed=self.speed, direction=direction, diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py index c34d0937dd3..5c6d1f88d7e 100644 --- a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -26,8 +26,8 @@ def __init__(self, N=100, width=10, height=10): model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} ) # Create agents - for i in range(self.num_agents): - a = MoneyAgent(i, self) + for _ in range(self.num_agents): + a = MoneyAgent(self) # Add the agent to a random grid cell x = self.random.randrange(self.grid.width) @@ -50,8 +50,8 @@ def run_model(self, n): class MoneyAgent(mesa.Agent): """An agent with fixed initial wealth.""" - def __init__(self, unique_id, model): - super().__init__(unique_id, model) + def __init__(self, model): + super().__init__(model) self.wealth = 1 def move(self): 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 index d9e0e7ba076..35c8d3f2791 100644 --- a/examples/conways_game_of_life/conways_game_of_life/cell.py +++ b/examples/conways_game_of_life/conways_game_of_life/cell.py @@ -11,7 +11,7 @@ def __init__(self, pos, model, init_state=DEAD): """ Create a cell, in the given state, at the given x, y position. """ - super().__init__(pos, model) + super().__init__(model) self.x, self.y = pos self.state = init_state self._nextState = None diff --git a/examples/wolf_sheep/wolf_sheep/agents.py b/examples/wolf_sheep/wolf_sheep/agents.py index 688f25096f4..c0b06f3a509 100644 --- a/examples/wolf_sheep/wolf_sheep/agents.py +++ b/examples/wolf_sheep/wolf_sheep/agents.py @@ -12,8 +12,8 @@ class Sheep(RandomWalker): energy = None - def __init__(self, unique_id, model, moore, energy=None): - super().__init__(unique_id, model, moore=moore) + def __init__(self, model, moore, energy=None): + super().__init__(model, moore=moore) self.energy = energy def step(self): @@ -44,7 +44,7 @@ def step(self): # Create a new sheep: if self.model.grass: self.energy /= 2 - lamb = Sheep(self.model.next_id(), self.model, self.moore, self.energy) + lamb = Sheep(self.model, self.moore, self.energy) self.model.grid.place_agent(lamb, self.pos) @@ -55,8 +55,8 @@ class Wolf(RandomWalker): energy = None - def __init__(self, unique_id, model, moore, energy=None): - super().__init__(unique_id, model, moore=moore) + def __init__(self, model, moore, energy=None): + super().__init__(model, moore=moore) self.energy = energy def step(self): @@ -83,7 +83,7 @@ def step(self): if self.random.random() < self.model.wolf_reproduce: # Create a new wolf cub self.energy /= 2 - cub = Wolf(self.model.next_id(), self.model, self.moore, self.energy) + cub = Wolf(self.model, self.moore, self.energy) self.model.grid.place_agent(cub, self.pos) @@ -92,7 +92,7 @@ class GrassPatch(mesa.Agent): A patch of grass that grows at a fixed rate and it is eaten by sheep """ - def __init__(self, unique_id, model, fully_grown, countdown): + def __init__(self, model, fully_grown, countdown): """ Creates a new patch of grass @@ -100,7 +100,7 @@ def __init__(self, unique_id, model, fully_grown, countdown): 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__(unique_id, model) + super().__init__(model) self.fully_grown = fully_grown self.countdown = countdown diff --git a/examples/wolf_sheep/wolf_sheep/model.py b/examples/wolf_sheep/wolf_sheep/model.py index 1dc1e1a53e7..85b60b73104 100644 --- a/examples/wolf_sheep/wolf_sheep/model.py +++ b/examples/wolf_sheep/wolf_sheep/model.py @@ -95,15 +95,15 @@ def __init__( x = self.random.randrange(self.width) y = self.random.randrange(self.height) energy = self.random.randrange(2 * self.sheep_gain_from_food) - sheep = Sheep(self.next_id(), self, True, energy) + sheep = Sheep(self, True, energy) self.grid.place_agent(sheep, (x, y)) # Create wolves - for i in range(self.initial_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 = Wolf(self.next_id(), self, True, energy) + wolf = Wolf(self, True, energy) self.grid.place_agent(wolf, (x, y)) # Create grass patches @@ -116,7 +116,7 @@ def __init__( else: countdown = self.random.randrange(self.grass_regrowth_time) - patch = GrassPatch(self.next_id(), self, fully_grown, countdown) + patch = GrassPatch(self, fully_grown, countdown) self.grid.place_agent(patch, (x, y)) self.running = True diff --git a/examples/wolf_sheep/wolf_sheep/random_walk.py b/examples/wolf_sheep/wolf_sheep/random_walk.py index 920ae08ca1d..a204f9cc414 100644 --- a/examples/wolf_sheep/wolf_sheep/random_walk.py +++ b/examples/wolf_sheep/wolf_sheep/random_walk.py @@ -18,7 +18,7 @@ class RandomWalker(mesa.Agent): y = None moore = True - def __init__(self, unique_id, model, moore=True): + def __init__(self, model, moore=True): """ grid: The MultiGrid object in which the agent lives. x: The agent's current x coordinate @@ -26,7 +26,7 @@ def __init__(self, unique_id, model, moore=True): moore: If True, may move in all 8 directions. Otherwise, only up, down, left, right. """ - super().__init__(unique_id, model) + super().__init__(model) self.moore = moore def random_move(self): From db9415baf3636d686dd401cc866ad05f0bef18f8 Mon Sep 17 00:00:00 2001 From: Corvince <13568919+Corvince@users.noreply.github.com> Date: Tue, 17 Sep 2024 08:59:28 +0200 Subject: [PATCH 52/56] update examples to use new SolaraViz API (#193) --- examples/boid_flockers/app.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/boid_flockers/app.py b/examples/boid_flockers/app.py index c8dd76bcd2c..205cb21858c 100644 --- a/examples/boid_flockers/app.py +++ b/examples/boid_flockers/app.py @@ -1,5 +1,5 @@ from boid_flockers.model import BoidFlockers -from mesa.visualization import SolaraViz +from mesa.visualization import SolaraViz, make_space_matplotlib def boid_draw(agent): @@ -15,11 +15,12 @@ def boid_draw(agent): "separation": 2, } +model = BoidFlockers(100, 100, 100, 5, 10, 2) + page = SolaraViz( - model_class=BoidFlockers, + model, + [make_space_matplotlib(agent_portrayal=boid_draw)], model_params=model_params, - measures=[], name="BoidFlockers", - agent_portrayal=boid_draw, ) page # noqa From 5e90bf54c08d57220cd4dffef2e043a91023ba92 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 21 Sep 2024 10:43:22 +0200 Subject: [PATCH 53/56] Use performance optimized shuffle_do() method (#201) Replace shuffle().do() in 18 models with the performance optimized shuffle_do() method. --- examples/boid_flockers/boid_flockers/model.py | 2 +- examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/boid_flockers/boid_flockers/model.py b/examples/boid_flockers/boid_flockers/model.py index 312986bd207..ae3099f3549 100644 --- a/examples/boid_flockers/boid_flockers/model.py +++ b/examples/boid_flockers/boid_flockers/model.py @@ -143,4 +143,4 @@ def make_agents(self): self.space.place_agent(boid, pos) def step(self): - self.agents.shuffle().do("step") + self.agents.shuffle_do("step") diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py index 5c6d1f88d7e..ac091a6ce34 100644 --- a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py +++ b/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py @@ -38,7 +38,7 @@ def __init__(self, N=100, width=10, height=10): self.datacollector.collect(self) def step(self): - self.agents.shuffle().do("step") + self.agents.shuffle_do("step") # collect data self.datacollector.collect(self) From 438bff13ac2391121d9dc8de32cfc9ff00094900 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Sat, 21 Sep 2024 15:58:25 +0200 Subject: [PATCH 54/56] Replace the remaining schedulers with AgentSet functionality (#202) This PR completes the migration from schedulers to AgentSet functionality across the mesa-examples repository for all regular (non-`gis`/-`rl`) examples. Key changes include: - Replaced `RandomActivation`, `SimultaneousActivation`, and `RandomActivationByType` schedulers with appropriate AgentSet methods - Updated `Model.step()` implementations to use AgentSet activation - Removed references to `schedule.steps`, `schedule.agents`, and `schedule.agents_by_type` - Updated agent addition/removal logic to work with AgentSets - Adjusted data collection and visualization code to use `Model.steps` and `Model.agents` For more details on migrating from schedulers to AgentSets, see the migration guide: https://mesa.readthedocs.io/en/latest/migration_guide.html#time-and-schedulers --- examples/wolf_sheep/wolf_sheep/test_random_walk.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/wolf_sheep/wolf_sheep/test_random_walk.py b/examples/wolf_sheep/wolf_sheep/test_random_walk.py index d2340fedba3..393a46b18c4 100644 --- a/examples/wolf_sheep/wolf_sheep/test_random_walk.py +++ b/examples/wolf_sheep/wolf_sheep/test_random_walk.py @@ -5,7 +5,6 @@ from mesa import Model from mesa.space import MultiGrid -from mesa.time import RandomActivation from mesa.visualization.TextVisualization import TextGrid, TextVisualization from wolf_sheep.random_walk import RandomWalker @@ -40,17 +39,15 @@ def __init__(self, width, height, agent_count): self.grid = MultiGrid(self.width, self.height, torus=True) self.agent_count = agent_count - self.schedule = RandomActivation(self) # Create agents for i in range(self.agent_count): x = self.random.randrange(self.width) y = self.random.randrange(self.height) a = WalkerAgent(i, (x, y), self, True) - self.schedule.add(a) self.grid.place_agent(a, (x, y)) def step(self): - self.schedule.step() + self.agents.shuffle_do("step") class WalkerWorldViz(TextVisualization): From 3ec87cf12857c4d2ab433f79be0e9594856de6a3 Mon Sep 17 00:00:00 2001 From: Wang Boyu Date: Sat, 12 Oct 2024 09:14:42 -0400 Subject: [PATCH 55/56] merge experimental boltzmann wealth into boltzmann wealth example --- examples/boltzmann_wealth_model/Readme.md | 31 ++-- examples/boltzmann_wealth_model/app.py | 156 ++++++------------ .../boltzmann_wealth_model/__init__.py | 0 .../boltzmann_wealth_model/server.py | 40 ----- .../{boltzmann_wealth_model => }/model.py | 0 .../boltzmann_wealth_model/requirements.txt | 2 +- examples/boltzmann_wealth_model/run.py | 3 - examples/boltzmann_wealth_model/st_app.py | 113 +++++++++++++ 8 files changed, 188 insertions(+), 157 deletions(-) delete mode 100644 examples/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py delete mode 100644 examples/boltzmann_wealth_model/boltzmann_wealth_model/server.py rename examples/boltzmann_wealth_model/{boltzmann_wealth_model => }/model.py (100%) delete mode 100644 examples/boltzmann_wealth_model/run.py create mode 100644 examples/boltzmann_wealth_model/st_app.py diff --git a/examples/boltzmann_wealth_model/Readme.md b/examples/boltzmann_wealth_model/Readme.md index 4a6e21f142a..8f7f7c817c2 100644 --- a/examples/boltzmann_wealth_model/Readme.md +++ b/examples/boltzmann_wealth_model/Readme.md @@ -12,32 +12,41 @@ As the model runs, the distribution of wealth among agents goes from being perfe 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) -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: +Make sure to install the requirements first: ``` - $ python server.py + $ pip install -r requirements.txt ``` -Make sure to install the requirements first: +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: ``` - pip install -r requirements.txt + $ solara run app.py ``` -If your browser doesn't open automatically, point it to [http://127.0.0.1:8521/](http://127.0.0.1:8521/). When the visualization loads, press Reset, then Run. +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 -* ``boltzmann_wealth_model/model.py``: Final version of the model. -* ``boltzmann_wealth_model/server.py``: Code for the interactive visualization. -* ``run.py``: Launches the server. +* ``model.py``: Final version of the model. +* ``app.py``: Code for the interactive visualization. ## Optional -* ``boltzmann_wealth_model/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`` +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 diff --git a/examples/boltzmann_wealth_model/app.py b/examples/boltzmann_wealth_model/app.py index 97f0f20c0fd..199b3a1a50e 100644 --- a/examples/boltzmann_wealth_model/app.py +++ b/examples/boltzmann_wealth_model/app.py @@ -1,113 +1,65 @@ -import time - -import altair as alt -import pandas as pd -import streamlit as st -from boltzmann_wealth_model.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, +from mesa.visualization import ( + SolaraViz, + make_plot_measure, + make_space_matplotlib, ) -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) - +from model import BoltzmannWealthModel -status_text = st.empty() -run = st.button("Run Simulation") +def agent_portrayal(agent): + size = 10 + color = "tab:red" + if agent.wealth > 0: + size = 50 + color = "tab:blue" + return {"size": size, "color": color} -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) - ) +model_params = { + "N": { + "type": "SliderInt", + "value": 50, + "label": "Number of agents:", + "min": 10, + "max": 100, + "step": 1, + }, + "width": 10, + "height": 10, +} - line = ( - alt.Chart(df_gini) - .mark_line(point=True) - .encode(x="step", y="gini") - .properties(width=800, height=600) - ) +# Create initial model instance +model1 = BoltzmannWealthModel(50, 10, 10) - # 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) +# 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") - 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) +# 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 - time.sleep(0.01) - tock = time.time() - st.success(f"Simulation completed in {tock - tick:.2f} secs") +# In a notebook environment, we can also display the visualization elements directly +# SpaceGraph(model1) +# GiniPlot(model1) - # 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']) +# 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/boltzmann_wealth_model/__init__.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/server.py b/examples/boltzmann_wealth_model/boltzmann_wealth_model/server.py deleted file mode 100644 index a49546ce741..00000000000 --- a/examples/boltzmann_wealth_model/boltzmann_wealth_model/server.py +++ /dev/null @@ -1,40 +0,0 @@ -import mesa - -from .model import BoltzmannWealthModel - - -def agent_portrayal(agent): - portrayal = {"Shape": "circle", "Filled": "true", "r": 0.5} - - if agent.wealth > 0: - portrayal["Color"] = "red" - portrayal["Layer"] = 0 - else: - portrayal["Color"] = "grey" - portrayal["Layer"] = 1 - portrayal["r"] = 0.2 - return portrayal - - -grid = mesa.visualization.CanvasGrid(agent_portrayal, 10, 10, 500, 500) -chart = mesa.visualization.ChartModule( - [{"Label": "Gini", "Color": "#0000FF"}], data_collector_name="datacollector" -) - -model_params = { - "N": mesa.visualization.Slider( - "Number of agents", - 100, - 2, - 200, - 1, - description="Choose how many agents to include in the model", - ), - "width": 10, - "height": 10, -} - -server = mesa.visualization.ModularServer( - BoltzmannWealthModel, [grid, chart], "Money Model", model_params -) -server.port = 8521 diff --git a/examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py b/examples/boltzmann_wealth_model/model.py similarity index 100% rename from examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py rename to examples/boltzmann_wealth_model/model.py diff --git a/examples/boltzmann_wealth_model/requirements.txt b/examples/boltzmann_wealth_model/requirements.txt index 25d263f4e84..95044bedf78 100644 --- a/examples/boltzmann_wealth_model/requirements.txt +++ b/examples/boltzmann_wealth_model/requirements.txt @@ -1 +1 @@ -mesa~=2.0 +mesa[viz]>=3.0.0b0 diff --git a/examples/boltzmann_wealth_model/run.py b/examples/boltzmann_wealth_model/run.py deleted file mode 100644 index f17675937cc..00000000000 --- a/examples/boltzmann_wealth_model/run.py +++ /dev/null @@ -1,3 +0,0 @@ -from boltzmann_wealth_model.server import server - -server.launch(open_browser=True) diff --git a/examples/boltzmann_wealth_model/st_app.py b/examples/boltzmann_wealth_model/st_app.py new file mode 100644 index 00000000000..665f8067a6a --- /dev/null +++ b/examples/boltzmann_wealth_model/st_app.py @@ -0,0 +1,113 @@ +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']) From 460983ef3d74dbf2b5b66b7d8fd571bcead56dd0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 12 Oct 2024 18:28:07 +0000 Subject: [PATCH 56/56] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/boid_flockers/app.py | 1 + .../boid_flockers/SimpleContinuousModule.py | 4 +-- examples/boid_flockers/boid_flockers/model.py | 28 ++++++------------- examples/boltzmann_wealth_model/app.py | 3 +- .../conways_game_of_life/cell.py | 12 ++------ .../conways_game_of_life/model.py | 10 ++----- .../conways_game_of_life/portrayal.py | 3 +- examples/wolf_sheep/wolf_sheep/agents.py | 18 ++++-------- examples/wolf_sheep/wolf_sheep/model.py | 10 ++----- examples/wolf_sheep/wolf_sheep/random_walk.py | 14 +++------- .../wolf_sheep/wolf_sheep/test_random_walk.py | 22 +++++---------- 11 files changed, 39 insertions(+), 86 deletions(-) diff --git a/examples/boid_flockers/app.py b/examples/boid_flockers/app.py index 205cb21858c..ade2deb2616 100644 --- a/examples/boid_flockers/app.py +++ b/examples/boid_flockers/app.py @@ -1,4 +1,5 @@ from boid_flockers.model import BoidFlockers + from mesa.visualization import SolaraViz, make_space_matplotlib diff --git a/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py b/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py index ec670d7af1c..ef26ddf7b80 100644 --- a/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py +++ b/examples/boid_flockers/boid_flockers/SimpleContinuousModule.py @@ -5,9 +5,7 @@ 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 - """ + """Instantiate a new SimpleCanvas""" self.portrayal_method = portrayal_method self.canvas_height = canvas_height self.canvas_width = canvas_width diff --git a/examples/boid_flockers/boid_flockers/model.py b/examples/boid_flockers/boid_flockers/model.py index ae3099f3549..d293a6bdc5f 100644 --- a/examples/boid_flockers/boid_flockers/model.py +++ b/examples/boid_flockers/boid_flockers/model.py @@ -1,17 +1,16 @@ -""" -Flockers +"""Flockers ============================================================= A Mesa implementation of Craig Reynolds's Boids flocker model. Uses numpy arrays to represent vectors. """ -import mesa import numpy as np +import mesa + class Boid(mesa.Agent): - """ - A Boid-style flocker agent. + """A Boid-style flocker agent. The agent follows three behaviors to flock: - Cohesion: steering towards neighboring agents. @@ -35,8 +34,7 @@ def __init__( separate=0.015, match=0.05, ): - """ - Create a new Boid flocker agent. + """Create a new Boid flocker agent. Args: speed: Distance to move per step. @@ -58,10 +56,7 @@ def __init__( self.neighbors = None def step(self): - """ - Get the Boid's neighbors, compute the new vector, and move accordingly. - """ - + """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)) @@ -83,9 +78,7 @@ def step(self): class BoidFlockers(mesa.Model): - """ - Flocker model class. Handles agent creation, placement and scheduling. - """ + """Flocker model class. Handles agent creation, placement and scheduling.""" def __init__( self, @@ -100,8 +93,7 @@ def __init__( separate=0.015, match=0.05, ): - """ - Create a new Flockers model. + """Create a new Flockers model. Args: population: Number of Boids @@ -124,9 +116,7 @@ def __init__( self.make_agents() def make_agents(self): - """ - Create self.population agents, with random positions and starting headings. - """ + """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 diff --git a/examples/boltzmann_wealth_model/app.py b/examples/boltzmann_wealth_model/app.py index 199b3a1a50e..0c38c742500 100644 --- a/examples/boltzmann_wealth_model/app.py +++ b/examples/boltzmann_wealth_model/app.py @@ -1,9 +1,10 @@ +from model import BoltzmannWealthModel + from mesa.visualization import ( SolaraViz, make_plot_measure, make_space_matplotlib, ) -from model import BoltzmannWealthModel def agent_portrayal(agent): 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 index 35c8d3f2791..9d286d925cd 100644 --- a/examples/conways_game_of_life/conways_game_of_life/cell.py +++ b/examples/conways_game_of_life/conways_game_of_life/cell.py @@ -8,9 +8,7 @@ class Cell(mesa.Agent): ALIVE = 1 def __init__(self, pos, model, init_state=DEAD): - """ - Create a cell, in the given state, at the given x, y position. - """ + """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 @@ -25,14 +23,12 @@ 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 + """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) @@ -47,7 +43,5 @@ def determine_state(self): self._nextState = self.ALIVE def assume_state(self): - """ - Set the state to the new computed state -- computed in step(). - """ + """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 index 76d9ca9fef4..d00261c90c7 100644 --- a/examples/conways_game_of_life/conways_game_of_life/model.py +++ b/examples/conways_game_of_life/conways_game_of_life/model.py @@ -4,15 +4,12 @@ class ConwaysGameOfLife(mesa.Model): - """ - Represents the 2-dimensional array of cells in Conway's + """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. - """ + """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) @@ -28,8 +25,7 @@ def __init__(self, width=50, height=50): self.running = True def step(self): - """ - Perform the model step in two stages: + """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 """ 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 index 4f68468d857..1c8d6d0fadd 100644 --- a/examples/conways_game_of_life/conways_game_of_life/portrayal.py +++ b/examples/conways_game_of_life/conways_game_of_life/portrayal.py @@ -1,6 +1,5 @@ def portrayCell(cell): - """ - This function is registered with the visualization server to be called + """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. diff --git a/examples/wolf_sheep/wolf_sheep/agents.py b/examples/wolf_sheep/wolf_sheep/agents.py index c0b06f3a509..fd6a8561bf9 100644 --- a/examples/wolf_sheep/wolf_sheep/agents.py +++ b/examples/wolf_sheep/wolf_sheep/agents.py @@ -4,8 +4,7 @@ class Sheep(RandomWalker): - """ - A sheep that walks around, reproduces (asexually) and gets eaten. + """A sheep that walks around, reproduces (asexually) and gets eaten. The init is the same as the RandomWalker. """ @@ -17,9 +16,7 @@ def __init__(self, model, moore, energy=None): self.energy = energy def step(self): - """ - A model step. Move, then eat grass and reproduce. - """ + """A model step. Move, then eat grass and reproduce.""" self.random_move() living = True @@ -49,9 +46,7 @@ def step(self): class Wolf(RandomWalker): - """ - A wolf that walks around, reproduces (asexually) and eats sheep. - """ + """A wolf that walks around, reproduces (asexually) and eats sheep.""" energy = None @@ -88,13 +83,10 @@ def step(self): class GrassPatch(mesa.Agent): - """ - A patch of grass that grows at a fixed rate and it is eaten by sheep - """ + """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 + """Creates a new patch of grass Args: grown: (boolean) Whether the patch of grass is fully grown or not diff --git a/examples/wolf_sheep/wolf_sheep/model.py b/examples/wolf_sheep/wolf_sheep/model.py index 85b60b73104..e97775c7746 100644 --- a/examples/wolf_sheep/wolf_sheep/model.py +++ b/examples/wolf_sheep/wolf_sheep/model.py @@ -1,5 +1,4 @@ -""" -Wolf-Sheep Predation Model +"""Wolf-Sheep Predation Model ================================ Replication of the model found in NetLogo: @@ -15,9 +14,7 @@ class WolfSheep(mesa.Model): - """ - Wolf-Sheep Predation Model - """ + """Wolf-Sheep Predation Model""" height = 20 width = 20 @@ -51,8 +48,7 @@ def __init__( grass_regrowth_time=30, sheep_gain_from_food=4, ): - """ - Create a new Wolf-Sheep model with the given parameters. + """Create a new Wolf-Sheep model with the given parameters. Args: initial_sheep: Number of sheep to start with diff --git a/examples/wolf_sheep/wolf_sheep/random_walk.py b/examples/wolf_sheep/wolf_sheep/random_walk.py index a204f9cc414..51b0a72b8f2 100644 --- a/examples/wolf_sheep/wolf_sheep/random_walk.py +++ b/examples/wolf_sheep/wolf_sheep/random_walk.py @@ -1,13 +1,10 @@ -""" -Generalized behavior for random walking, one grid cell at a time. -""" +"""Generalized behavior for random walking, one grid cell at a time.""" import mesa class RandomWalker(mesa.Agent): - """ - Class implementing random walker methods in a generalized manner. + """Class implementing random walker methods in a generalized manner. Not intended to be used on its own, but to inherit its methods to multiple other agents. @@ -19,8 +16,7 @@ class RandomWalker(mesa.Agent): moore = True def __init__(self, model, moore=True): - """ - grid: The MultiGrid object in which the agent lives. + """grid: The MultiGrid object in which the agent lives. x: The agent's current x coordinate y: The agent's current y coordinate moore: If True, may move in all 8 directions. @@ -30,9 +26,7 @@ def __init__(self, model, moore=True): self.moore = moore def random_move(self): - """ - Step one cell in any allowable direction. - """ + """Step one cell in any allowable direction.""" # Pick the next cell from the adjacent cells. next_moves = self.model.grid.get_neighborhood(self.pos, self.moore, True) next_move = self.random.choice(next_moves) diff --git a/examples/wolf_sheep/wolf_sheep/test_random_walk.py b/examples/wolf_sheep/wolf_sheep/test_random_walk.py index 393a46b18c4..14284f98300 100644 --- a/examples/wolf_sheep/wolf_sheep/test_random_walk.py +++ b/examples/wolf_sheep/wolf_sheep/test_random_walk.py @@ -1,5 +1,4 @@ -""" -Testing the RandomWalker by having an ABM composed only of random walker +"""Testing the RandomWalker by having an ABM composed only of random walker agents. """ @@ -10,25 +9,20 @@ class WalkerAgent(RandomWalker): - """ - Agent which only walks around. - """ + """Agent which only walks around.""" def step(self): self.random_move() class WalkerWorld(Model): - """ - Random walker world. - """ + """Random walker world.""" height = 10 width = 10 def __init__(self, width, height, agent_count): - """ - Create a new WalkerWorld. + """Create a new WalkerWorld. Args: width, height: World size. @@ -51,16 +45,14 @@ def step(self): class WalkerWorldViz(TextVisualization): - """ - ASCII Visualization for a WalkerWorld agent. + """ASCII Visualization for a WalkerWorld agent. Each cell is displayed as the number of agents currently in that cell. """ def __init__(self, model): - """ - Create a new visualization for a WalkerWorld instance. + """Create a new visualization for a WalkerWorld instance. - args: + Args: model: An instance of a WalkerWorld model. """ self.model = model