{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "###### The latest version of this IPython notebook is available at [http://github.com/jckantor/ESTM60203](http://github.com/jckantor/ESTM60203) for noncommercial use under terms of the [Creative Commons Attribution Noncommericial ShareAlike License (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "J.C. Kantor (Kantor.1@nd.edu)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Getting Started with Discrete Event Simulation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This [IPython notebook](http://ipython.org/notebook.html) demonstrates elementary use of the [SimPy](http://simpy.readthedocs.org/en/latest/) package for discrete event simulation." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Initializations" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\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", "HTML(open(\"styles/custom.css\", \"r\").read())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### SimPy Installation" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Requirement already satisfied: simpy in /Users/jeff/anaconda/lib/python3.5/site-packages\r\n" ] } ], "source": [ "!pip install simpy\n", "import simpy as simpy\n", "#simpy.test()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Introduction to Modeling with SimPy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### A Minimal SimPy Model" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A typical simpy model consists of an environment, processes that create events for the environment to process, and resources. We'll start by setting up an environment and running a simulation. This won't do anything, but it is valid (if useless) simulation." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import simpy\n", "\n", "# create the simulation environment\n", "env = simpy.Environment()\n", "\n", "# run the simulation\n", "env.run()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Adding a Process" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "An example of a process is a clock that ticks at regular intervals, and at each tick prints a message showing the current time. \n", "\n", "The clock is a regular python function that executes until it encounters the `yield env.timeout(tick)` statement. At that point a new event is scheduled for tick time units in the future after which execution will continue.\n", "\n", "The `env.process(clock(env, 2.0))` statement adds a clock to the simulation environment. The `env.run(until=10)` statement processes the environment 10 simulated time units." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Time = 0.000000 minutes tick\n", "Time = 0.000000 minutes tock\n", "Time = 1.200000 minutes tock\n", "Time = 2.000000 minutes tick\n", "Time = 2.400000 minutes tock\n", "Time = 3.600000 minutes tock\n", "Time = 4.000000 minutes tick\n", "Time = 4.800000 minutes tock\n", "Time = 6.000000 minutes tick\n", "Time = 6.000000 minutes tock\n", "Time = 7.200000 minutes tock\n", "Time = 8.000000 minutes tick\n", "Time = 8.400000 minutes tock\n", "Time = 9.600000 minutes tock\n", "Time = 10.000000 minutes tick\n", "Time = 10.800000 minutes tock\n", "Time = 12.000000 minutes tock\n", "Time = 12.000000 minutes tick\n", "Time = 13.200000 minutes tock\n", "Time = 14.000000 minutes tick\n", "Time = 14.400000 minutes tock\n", "Time = 15.600000 minutes tock\n", "Time = 16.000000 minutes tick\n", "Time = 16.800000 minutes tock\n", "Time = 18.000000 minutes tock\n", "Time = 18.000000 minutes tick\n", "Time = 19.200000 minutes tock\n" ] } ], "source": [ "import simpy\n", "\n", "# define a clock process\n", "def clock(env,tick,name):\n", " while True:\n", " print(\"Time = {:8.6f} minutes\".format(env.now), name)\n", " yield env.timeout(tick)\n", "\n", "# create the simulation environment\n", "env = simpy.Environment()\n", "\n", "# add the clock process to the environment. Set the tick interval.\n", "env.process(clock(env, 2.0,'tick'))\n", "env.process(clock(env,1.2,'tock'))\n", "\n", "# run the simulation for a fixed period of time\n", "env.run(until=20)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Mutliple Instances of a Process" ] }, { "cell_type": "markdown", "metadata": {}, "source": [] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Clock A ticks. Time = 0.000000 minutes\n", "Clock B ticks. Time = 0.000000 minutes\n", "Clock B ticks. Time = 1.300000 minutes\n", "Clock A ticks. Time = 2.000000 minutes\n", "Clock B ticks. Time = 2.600000 minutes\n", "Clock B ticks. Time = 3.900000 minutes\n", "Clock A ticks. Time = 4.000000 minutes\n", "Clock B ticks. Time = 5.200000 minutes\n", "Clock A ticks. Time = 6.000000 minutes\n", "Clock B ticks. Time = 6.500000 minutes\n", "Clock B ticks. Time = 7.800000 minutes\n", "Clock A ticks. Time = 8.000000 minutes\n", "Clock B ticks. Time = 9.100000 minutes\n" ] } ], "source": [ "import simpy\n", "\n", "# define a clock process\n", "def clock(env,name,tick):\n", " while True:\n", " print \"Clock {:s} ticks. Time = {:8.6f} minutes\".format(name, env.now)\n", " yield env.timeout(tick)\n", "\n", "# create the simulation environment\n", "env = simpy.Environment()\n", "\n", "# add the clock process to the environment. Set the tick interval.\n", "env.process(clock(env, \"A\", 2.0))\n", "env.process(clock(env, \"B\", 1.3))\n", "\n", "# run the simulation for a fixed period of time\n", "env.run(until=10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Processes Manage their own State" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Clock A, tick number 1. Time = 0.000000 minutes\n", "Clock B, tick number 1. Time = 0.000000 minutes\n", "Clock B, tick number 2. Time = 1.300000 minutes\n", "Clock A, tick number 2. Time = 2.000000 minutes\n", "Clock B, tick number 3. Time = 2.600000 minutes\n", "Clock B, tick number 4. Time = 3.900000 minutes\n", "Clock A, tick number 3. Time = 4.000000 minutes\n", "Clock B, tick number 5. Time = 5.200000 minutes\n", "Clock A, tick number 4. Time = 6.000000 minutes\n", "Clock B, tick number 6. Time = 6.500000 minutes\n", "Clock B, tick number 7. Time = 7.800000 minutes\n", "Clock A, tick number 5. Time = 8.000000 minutes\n", "Clock B, tick number 8. Time = 9.100000 minutes\n", "Clock A, tick number 6. Time = 10.000000 minutes\n", "Clock B, tick number 9. Time = 10.400000 minutes\n", "Clock B, tick number 10. Time = 11.700000 minutes\n", "Clock A, tick number 7. Time = 12.000000 minutes\n", "Clock B, tick number 11. Time = 13.000000 minutes\n", "Clock A, tick number 8. Time = 14.000000 minutes\n", "Clock B, tick number 12. Time = 14.300000 minutes\n", "Clock B, tick number 13. Time = 15.600000 minutes\n", "Clock A, tick number 9. Time = 16.000000 minutes\n", "Clock B, tick number 14. Time = 16.900000 minutes\n", "Clock A, tick number 10. Time = 18.000000 minutes\n", "Clock B, tick number 15. Time = 18.200000 minutes\n", "Clock B, tick number 16. Time = 19.500000 minutes\n" ] } ], "source": [ "import simpy\n", "\n", "# define a clock process\n", "def clock(env,name,tick):\n", " nTicks = 0\n", " while True:\n", " nTicks += 1\n", " print (\"Clock {:s}, tick number {:d}. Time = {:8.6f} minutes\".format(name, nTicks, env.now))\n", " yield env.timeout(tick)\n", "\n", "# create the simulation environment\n", "env = simpy.Environment()\n", "\n", "# add the clock process to the environment. Set the tick interval.\n", "env.process(clock(env, \"A\", 2.0))\n", "env.process(clock(env, \"B\", 1.3))\n", "\n", "# run the simulation for a fixed period of time\n", "env.run(until=20)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Application Examples" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Geometric Brownian Motion" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false }, "outputs": [ { "ename": "NameError", "evalue": "name 'gbm_old' 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[1;32m 16\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[0;31m# add the clock process to the environment. Set the tick interval.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 18\u001b[0;31m \u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mprocess\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgbm_old\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"A\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msqrt\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1.0\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0;36m252\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m80.0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m.3\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 19\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 20\u001b[0m \u001b[0;31m# run the simulation for a fixed period of time\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mNameError\u001b[0m: name 'gbm_old' is not defined" ] } ], "source": [ "import simpy\n", "import random \n", "\n", "# geometric brownian motion\n", "def gbm(env,name,tick,P,mu,sigma):\n", " t = 0;\n", " while True:\n", " Plog.append(P)\n", " tlog.append(t)\n", " yield env.timeout(tick)\n", " P += P*(mu*tick + sigma*random.normalvariate(0,1)*sqrt(tick))\n", " t += tick\n", " \n", "# create the simulation environment\n", "env = simpy.Environment()\n", "\n", "# add the clock process to the environment. Set the tick interval.\n", "env.process(gbm_old(env, \"A\", sqrt(1.0/252), 80.0, 0, .3))\n", "\n", "# run the simulation for a fixed period of time\n", " \n", "Plog = []\n", "tlog = []\n", "env.run(until=10)\n", "\n", "plot(tlog,Plog)\n", "xlabel('Date')\n", "ylabel('Price')" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1.0 80.7675367205\n", "2.0 74.887121441\n", "3.0 43.3863034774\n", "4.0 36.1322746874\n", "5.0 27.3210018971\n", "6.0 22.1435662098\n", "7.0 25.1007915851\n", "8.0 32.5795722168\n", "9.0 46.6315095102\n", "10.0 51.1479983156\n", "11.0 40.2624445257\n", "12.0 55.4630517992\n", "13.0 62.2144445446\n", "14.0 90.4502257938\n", "15.0 90.0176180144\n", "16.0 106.023491267\n", "17.0 146.273459787\n", "18.0 198.014117565\n", "19.0 391.887038298\n" ] } ], "source": [ "import simpy\n", "import random\n", "import numpy as np\n", "\n", "class gbm(object):\n", " def __init__(self,env,name,tick,val,mu,sigma):\n", " self.env = env\n", " self.name = name\n", " self.tick = tick\n", " self.val = val\n", " self.mu = mu\n", " self.sigma = sigma\n", " self.t = 0\n", " \n", " def process(self):\n", " while True:\n", " yield self.env.timeout(self.tick)\n", " self.t += self.tick\n", " self.val += self.val*(self.mu*self.tick + self.sigma*random.normalvariate(0,1)*np.sqrt(self.tick))\n", "\n", "def reporter(env,tick,gbm):\n", " t = 0\n", " while True:\n", " yield env.timeout(tick)\n", " t += tick\n", " print(t, gbm.val)\n", "\n", "env = simpy.Environment()\n", "a = gbm(env,'A',1.0/np.sqrt(12.0),80.0,0,0.30)\n", "env.process(a.process())\n", "env.process(reporter(env,1.0,a))\n", "env.run(until=20)\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Application" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Setting up a class provides a means of modeling more complex behaviors. Here we'll consider a Roomba cleaning robot that can be either in a running mode or a charging mode." ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "A start charging at 0.0\n", "B start charging at 0.0\n", "B start cleaning at 0.9\n", "A start cleaning at 1.1\n", "A start charging at 3.4\n", "B start charging at 4.0\n", "A start cleaning at 4.5\n", "B start cleaning at 4.9\n", "A start charging at 6.8\n", "A start cleaning at 7.9\n", "B start charging at 8.0\n", "B start cleaning at 8.9\n", "A start charging at 10.2\n", "A start cleaning at 11.3\n", "B start charging at 12.0\n", "B start cleaning at 12.9\n", "A start charging at 13.6\n", "A start cleaning at 14.7\n", "B start charging at 16.0\n", "B start cleaning at 16.9\n", "A start charging at 17.0\n", "A start cleaning at 18.1\n", "B start charging at 20.0\n", "A start charging at 20.4\n", "B start cleaning at 20.9\n", "A start cleaning at 21.5\n", "A start charging at 23.8\n" ] } ], "source": [ "\n", "class Roomba(object):\n", " def __init__(self,env,name,charge_duration,clean_duration):\n", " self.env = env\n", " self.name = name\n", " self.charge_duration = charge_duration\n", " self.clean_duration = clean_duration\n", " self.proc = env.process(self.run())\n", "\n", " def run(self):\n", " while True:\n", " yield env.process(self.charge())\n", " yield env.process(self.clean())\n", " \n", " def clean(self):\n", " print (\"{:<3s} start cleaning at {:4.1f}\".format(self.name,env.now))\n", " yield env.timeout(self.clean_duration)\n", " \n", " def charge(self):\n", " print(\"{:<3s} start charging at {:4.1f}\".format(self.name,env.now))\n", " yield env.timeout(self.charge_duration)\n", "\n", "import simpy\n", "env = simpy.Environment()\n", "\n", "A = Roomba(env,'A',1.1,2.3)\n", "B = Roomba(env,'B',0.9,3.1)\n", "\n", "# start processes\n", "env.run(until=24)" ] }, { "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.5.3" } }, "nbformat": 4, "nbformat_minor": 0 }