{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "/* \n", "* @Author: paul\n", "* @Date: 2016-02-25 17:35:32\n", "* @Last Modified by: paul\n", "* @Last Modified time: 2016-02-25 17:45:54\n", "*/\n", "\n", "\n", "\n", "\n", "\n", "\n", "" ], "text/plain": [ "" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from IPython.core.display import HTML\n", "def css_styling():\n", " sheet = '../css/custom.css'\n", " styles = open(sheet, \"r\").read() \n", " return HTML(styles)\n", "css_styling()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# OOP Predator Prey Agent Based Modelling in Python" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Use what you have learned through this intermediate Python course to produce a *'predator-prey'* agent based modelling simulation. Your task is to program the classes which describe the eating and moving behaviour of a predator and its prey - we'll use rabbits and foxes in this example.\n", "\n", "This exercise will test your knowledge of inheritance, and is specifically designed to demonstrate polymorphism in action. The main iterative algorithm and many functions are provided; your tasks is to generate the underlying classes.\n", "\n", "|Class: Predator | Class: Prey |\n", "|-----------------------------|----------------------------|\n", "|![The_Predator](data/fox.png)|![The_prey](data/rabbit.png)|\n", "\n", "**Note: do not get confused between the current task and the \"predator prey problem\", which models the interactions between predator and prey through differential equations**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Implementation\n", "\n", "We'll assume the rabbits move only in a random direction, simulating a grazing behaviour, and that foxes move towards their nearest prey. Detailed breakdown of the algorithm is provided under [Task breakdown](#Task-Breakdown).\n", "\n", "## What you have been given\n", "\n", "Constants:\n", "\n", ">* $N_r$ - number of rabbits\n", ">* $N_f$ - number of foxes\n", ">* $age_f$ - maximum \"age\" at which foxes die\n", ">* $age_r$ - maximum \"age\" at which foxes die\n", ">* $speed_f$ - fox move speed\n", ">* $speed_r$ - rabbit move speed\n", "\n", "You have been given the `Agent` class from which your Predator and Prey classes will inherit. This contains:\n", "\n", "* `__init__(self, age, location, age_death, image)` initialiser. Stores the attributes:\n", " - age: the age to give this Agent on construction (int)\n", " - location : array of (x, y)\n", " - age_death : age (iteration) at which this agent will 'die'\n", " - isalive : bool\n", " - image : image file name to be used for plotting (images seen in the table above)\n", " \n", "* `update_ periodic_boundaries` - checks if self.location is within a box from 0,0 to 1,1, otherwise resets the location to appear on the other side of the box\n", "\n", "* `move`, method - does nothing: **MUST BE REDEFINED IN DERIVED CLASSES**\n", " - *Note that this class inherits from the metaclass ABCMeta using the Python2 - Python3 cross compatibility `six` package. This helps achieve polymorphism by enforcing us to redefine the move method in all derived classes (otherwise an error will be raised), ensuring the API remains complete and consistent*\n", "\n", "* **All other functions should be self explanatory**\n", "\n", "Some helper functions are also provided, which should be self explanatory and are already called for you in an iterative fashion. You do not need to know what these do." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Task Breakdown\n", "\n", "The classes you are required to implement are:\n", "\n", "* **`Prey`** class inherited from `Agent` with the following attributes and methods:\n", " - class attributes: **Prey.age_death** and **Prey.speed**\n", " - `__init__` : has the same arguments as `Agent.__init__`, and simply calls the parent class `__init__` with\n", " > super().__init__(age=age,\n", " > location=location,\n", " > age_death=Prey.age_death,\n", " > image='rabbit.png')\n", " \n", " - Try to understand this line : ask the demonstrators if you need help\n", " \n", " - A `move` method, to override the base `Agent` class\n", " - The algorithm should be (where each line can be calculated either simply or by a function in the base class):\n", " > if [I am] alive\n", " > vector = get random vector\n", " > get distance (vector)\n", " >\n", " > scale vector to unit length using distance\n", " >\n", " > update location by (unit vector * Prey.speed)\n", " >\n", " > update_periodic_boundaries \n", " >\n", " > increment_age\n", " > else\n", " > do nothing\n", "\n", "* **`Predator`** class inhertied from `Agent` class with the following attributes and methods:\n", " - class attributes: **Predator.age_death** and **Predator.speed**\n", "\n", " - `__init__` : same args as Agent, but also includes preylist (the list of prey)\n", " - pass all arguments to parent `__init__` with super as you did in the Prey class, but with `image=fox.png`\n", " - add preylist as an attribute with `self`\n", " \n", " - `find_prey(self)` : to save time we provide the code for this method\n", " > def find_prey(self):\n", " > \"\"\"Searches through the location of prey items in\n", " > preylist and updates self.nearest prey\"\"\"\n", " > max_distance = 10 # No prey this far away\n", " > for prey in self.preylist:\n", " > vec = self.get_vector(prey.location)\n", " > distance = self.get_distance(vec)\n", " > if distance < max_distance and prey.isalive:\n", " > max_distance = distance\n", " > self.nearest_prey = prey\n", " \n", " - A **`move`** method, to override the base `Agent` class abstract `move` method\n", " - The algorithm should be (where each line can be calculated either simply or by a function in the base class):\n", " \n", " if [I am] alive\n", " find nearest prey\n", "\n", " if nearest prey is alive:\n", " get vector to nearest preys location\n", " get distance (vector)\n", "\n", " if distance is less than Predator.speed\n", " kill the prey\n", "\n", " else move:\n", "\n", " scale vector to unit length\n", "\n", " update location by (unit vector * Predator.speed)\n", "\n", " else if prey is dead:\n", " remove prey from preylist\n", "\n", " increment_age\n", "\n", " else if [I am] dead\n", " do nothing\n", " " ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pylab as plt\n", "from matplotlib import image\n", "import os\n", "plt.rcParams['figure.figsize'] = (18.0, 10.0)\n", "from abc import ABCMeta, abstractmethod\n", "import six\n", "%matplotlib inline\n" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Constants:\n", "Nr = 15\n", "Nf = 3\n", "age_r = 200 # death age of rabbits\n", "age_f = 50 # death age of foxes\n", "# factor which scales move vector length per iteration:\n", "speed_r = 0.02\n", "speed_f = 0.05" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Classes:\n", "class Agent(six.with_metaclass(ABCMeta)):\n", " def __init__(self, age, location, age_death, image):\n", " \"\"\"\n", " Parameters\n", " ----------\n", " age : int\n", " location : array length 2 of scalar\n", " (x, y) location\n", " age_death : age at which this instance dies\n", " image : string\n", " the name of the image\n", " \"\"\"\n", " self._age = age # We don't want anything else to overwrite age: \n", " # Could use encapsulation for self.age\n", " self.location = np.array(location)\n", " self.isalive = True\n", " self.age_death = age_death\n", " self.add_image(image)\n", " \n", " def increment_age(self):\n", " self._age += 1\n", " if self._age >= self.age_death:\n", " self.die()\n", " \n", " @abstractmethod\n", " def move(self):\n", " pass\n", " \n", " def die(self):\n", " self.isalive = False\n", " self.add_image('dead.png')\n", " \n", " def get_vector(self, loc):\n", " \"\"\"Returns a tuple of distance and unit vector from self.location to input loc\"\"\"\n", " assert(len(loc) == 2), \"Location should contain an x and y coordinate\"\n", " vec = loc - self.location\n", " return vec\n", " \n", " def get_distance(self, vec):\n", " return np.linalg.norm(vec)\n", "\n", " def get_random_vector(self):\n", " \"\"\"return a random vector\"\"\"\n", " return np.random.uniform(-1, 1, 2)\n", "\n", " def display(self, fig):\n", " \"\"\"Displays an image of this instance depending on life status\"\"\"\n", " aximg = fig.add_axes([self.location[0], self.location[1], 0.05, 0.05])\n", " aximg.axis('off')\n", " aximg.imshow(self.image)\n", " \n", " def add_image(self, img, path='data'):\n", " \"\"\"updates the image used by display method using imread\"\"\"\n", " self.image = image.imread(os.path.join(path, img))\n", " \n", " def update_periodic_boundaries(self):\n", " if self.location[0] > 1:\n", " self.location[0] -= 1\n", " elif self.location[0] < 0:\n", " self.location[0] += 1\n", "\n", " if self.location[1] > 1:\n", " self.location[1] -= 1\n", " elif self.location[1] < 0:\n", " self.location[1] += 1 " ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Your classes should be implemented here ...\n", "# class ...\n" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# =================================================================\n", "# Misc helper functions\n", "# =================================================================\n", "def generate_predator_prey(Npredator, Nprey):\n", " \"\"\"Generate Npredator predators and Nprey prey objects with randomly\n", " distribute locations and ages\"\"\"\n", "\n", " prey_locs = np.random.rand(Nr, 2)\n", " prey_ages = np.random.randint(0, Prey.age_death, Nr)\n", " preys = [Prey(age=prey_ages[i], location=prey_locs[i]) for i in range(Nr)]\n", "\n", " predator_locs = np.random.rand(Nf, 2)\n", " predator_ages = np.random.randint(0, Predator.age_death, Nf)\n", " predators = [Predator(age=predator_ages[i], location=predator_locs[i],\n", " preylist=preys) for i in range(Nf)]\n", " return predators, preys\n", "\n", "\n", "def update_predator_prey(predators, preys, fig):\n", " \"\"\"Updates the locations of all predators and preys in the inputs and\n", " displays them in input fig using the Agent.display method\"\"\"\n", " # Clear the figure first\n", " fig.clf() \n", " for predator in predators:\n", " predator.move()\n", " predator.display(fig)\n", " \n", " for prey in preys:\n", " prey.move()\n", " prey.display(fig)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Test case\n", "The code below should test if your classes work as expected" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "ename": "NameError", "evalue": "name 'Prey' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mfoxes\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrabbits\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mgenerate_predator_prey\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mNr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mNf\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0mfig\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfigure\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfigsize\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m16\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0max\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfig\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0madd_subplot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m111\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0max\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0maxis\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'off'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m\u001b[0m in \u001b[0;36mgenerate_predator_prey\u001b[0;34m(Npredator, Nprey)\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0mprey_locs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrandom\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrand\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mNr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 9\u001b[0;31m \u001b[0mprey_ages\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrandom\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrandint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mPrey\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mage_death\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mNr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 10\u001b[0m \u001b[0mpreys\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mPrey\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mage\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mprey_ages\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlocation\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mprey_locs\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mNr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mNameError\u001b[0m: name 'Prey' is not defined" ] } ], "source": [ "foxes, rabbits = generate_predator_prey(Nr, Nf)\n", "\n", "fig = plt.figure(figsize=(16,10))\n", "ax = fig.add_subplot(111)\n", "ax.axis('off')\n", "\n", "Nit = 1 # Iterate 1 more than the death of all foxes\n", "outputpath = os.path.abspath('frames')\n", "\n", "for i in range(Nit):\n", " update_predator_prey(foxes, rabbits, fig)\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plotting\n", "Here is the code to animate your simulation using the [JSAnimation package](https://github.com/jakevdp/JSAnimation)** (you will need to install this first)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from JSAnimation import IPython_display\n", "from matplotlib import animation\n", "\n", "fig = plt.figure(figsize=(12,8))\n", "ax = fig.add_subplot(111)\n", "ax.axis('off')\n", "\n", "Nit = 51 # Iterate 1 more than the death of all foxes\n", "\n", "foxes, rabbits = generate_predator_prey(Nr, Nf)\n", "\n", "\n", "def init():\n", " fig.clf()\n", "\n", "def update_predator_prey(i):\n", " \"\"\"Updates the locations of all predators and preys in the inputs and\n", " displays them in input fig using the Agent.display method\"\"\"\n", " # Clear the figure first\n", " fig.clf()\n", " for fox in foxes:\n", " fox.move()\n", " fox.display(fig)\n", " for rabbit in rabbits:\n", " rabbit.move()\n", " rabbit.display(fig)\n", "\n", "animation.FuncAnimation(fig, update_predator_prey, frames=Nit, interval=1, init_func=init)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# [Solutions](../soln/06-Predator_prey.ipynb)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Extension: Create a generator of predator and prey instances using generator comprehension**" ] } ], "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.5.1" } }, "nbformat": 4, "nbformat_minor": 0 }