{ "cells": [ { "cell_type": "markdown", "metadata": { "pycharm": {} }, "source": [ "\n", "*This notebook contains course material from [CBE40455](https://jckantor.github.io/CBE40455) by\n", "Jeffrey Kantor (jeff at nd.edu); the content is available [on Github](https://github.com/jckantor/CBE40455.git).\n", "The text is released under the [CC-BY-NC-ND-4.0 license](https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode),\n", "and code is released under the [MIT license](https://opensource.org/licenses/MIT).*" ] }, { "cell_type": "markdown", "metadata": { "pycharm": {} }, "source": [ "\n", "< [Simulation of Discrete Event Systems](http://nbviewer.jupyter.org/github/jckantor/CBE40455/blob/master/notebooks/02.00-Simulation-of-Discrete-Event-Systems.ipynb) | [Contents](toc.ipynb) | [Queuing Systems](http://nbviewer.jupyter.org/github/jckantor/CBE40455/blob/master/notebooks/02.02-Queuing-Systems.ipynb) >

\"Open

\"Download\"" ] }, { "cell_type": "markdown", "metadata": { "pycharm": {} }, "source": [ "# Getting Started with SimPy" ] }, { "cell_type": "markdown", "metadata": { "pycharm": {} }, "source": [ "This notebook demonstrates elementary use of the [SimPy](http://simpy.readthedocs.org/en/latest/) package for discrete event simulation.\n", "\n", "* [Introduction to Modeling with SimPy]()\n", "* [Applications]\n" ] }, { "cell_type": "markdown", "metadata": { "pycharm": {} }, "source": [ "## Introduction to Modeling with SimPy" ] }, { "cell_type": "markdown", "metadata": { "pycharm": {} }, "source": [ "### A Minimal SimPy Model" ] }, { "cell_type": "markdown", "metadata": { "pycharm": {} }, "source": [ "A typical simpy model consists of an environment, processes that create events within the environment, and resources that handle then events. To demonstrate how this works, let's start by setting up an environment and running the simplest possible simulation. This doesn't do anything but it is a valid (if useless) simulation." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "pycharm": {} }, "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": { "pycharm": {} }, "source": [ "### Adding a Process" ] }, { "cell_type": "markdown", "metadata": { "pycharm": {} }, "source": [ "An example of a process is a clock that ticks at regular intervals, at each tick printing a message showing the current time. \n", "\n", "The clock is a simply a regular python function. The first argument is a variable that provides access to the environment. Additional arguments are a design decision. In this case the second argument specifies the length of each clock tick. \n", "\n", "In this case, the clock function starts an infinite loop with two actions. The first is to print the current time accessed from the environment via the variable `env.now`. After printing, the function sets a timer for period `tick`, then yields control back to the environment until the timer expires.\n", "\n", "With clock defined, the simulation is set up as before with two additions. The `env.process(clock(env, 2.0))` statement adds the clock as process in the simulation environment. The `env.run(until=10)` statement the starts the simulation for 10 simulated time units." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "pycharm": {} }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Time = 0.000000 minutes\n", "Time = 2.000000 minutes\n", "Time = 4.000000 minutes\n", "Time = 6.000000 minutes\n", "Time = 8.000000 minutes\n" ] } ], "source": [ "import simpy\n", "\n", "# define a clock process\n", "def clock(env,tick):\n", " while True:\n", " print(\"Time = {:8.6f} minutes\".format(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, 2.0))\n", "\n", "# run the simulation for a fixed period of time\n", "env.run(until=10)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": {} }, "source": [ "### Add Mutliple Instances of a Process\n", "\n", "Let's make the example more interesting by adding a second clock process. Each call to `env.process()` will establish a new instance of a clock using the same python function to define behavior.\n", "\n", "In order to distinguish the output coming from the two clocks, a second argument is added that allows one to pass a unique name. The processes are created with names \"A\" and \"B\" and with tick periods of 2.0 and 1.5, respectively." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "pycharm": {} }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Clock A ticks at 0.0000 min.\n", "Clock B ticks at 0.0000 min.\n", "Clock B ticks at 1.5000 min.\n", "Clock A ticks at 2.0000 min.\n", "Clock B ticks at 3.0000 min.\n", "Clock A ticks at 4.0000 min.\n", "Clock B ticks at 4.5000 min.\n", "Clock A ticks at 6.0000 min.\n", "Clock B ticks at 6.0000 min.\n", "Clock B ticks at 7.5000 min.\n", "Clock A ticks at 8.0000 min.\n", "Clock B ticks at 9.0000 min.\n" ] } ], "source": [ "import simpy\n", "\n", "# define a clock process\n", "def clock(env,name,tick):\n", " while True:\n", " print(\"Clock {:s} ticks at {:6.4f} min.\".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.5))\n", "\n", "# run the simulation for a fixed period of time\n", "env.run(until=10)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": {} }, "source": [ "### Processes that Manage their own State\n", "\n", "Now let's give our clocks a bit of memory. The clock function adds a local variable `nTicks` that counts the number of ticks that have occurred. Each instance of a clock process now has some independent memory." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "pycharm": {} }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Clock A, tick number 1. Time = 0.0000 min.\n", "Clock B, tick number 1. Time = 0.0000 min.\n", "Clock B, tick number 2. Time = 1.3000 min.\n", "Clock A, tick number 2. Time = 2.0000 min.\n", "Clock B, tick number 3. Time = 2.6000 min.\n", "Clock B, tick number 4. Time = 3.9000 min.\n", "Clock A, tick number 3. Time = 4.0000 min.\n", "Clock B, tick number 5. Time = 5.2000 min.\n", "Clock A, tick number 4. Time = 6.0000 min.\n", "Clock B, tick number 6. Time = 6.5000 min.\n", "Clock B, tick number 7. Time = 7.8000 min.\n", "Clock A, tick number 5. Time = 8.0000 min.\n", "Clock B, tick number 8. Time = 9.1000 min.\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 = {:6.4f} min.\".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=10)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": {} }, "source": [ "### Creating Simulation Objects\n", "\n", "An elegant and useful use of the SimPy package is to define classes of objects for use in complex simulations. \n", "\n", "For example, here we create a clock class. The class to used to create clock objects for a simulation. The class constructor `__init__()` initiates a new clock object with a name, sets properties such as the tick length and a tick counter, and then registers the class `run()` method in the simulation environment." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "pycharm": {} }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Clock A,Tick number 1,Time = 0.0000 minutes\n", "Clock B,Tick number 1,Time = 0.0000 minutes\n", "Clock B,Tick number 2,Time = 1.5000 minutes\n", "Clock A,Tick number 2,Time = 2.0000 minutes\n", "Clock B,Tick number 3,Time = 3.0000 minutes\n", "Clock A,Tick number 3,Time = 4.0000 minutes\n", "Clock B,Tick number 4,Time = 4.5000 minutes\n", "Clock A,Tick number 4,Time = 6.0000 minutes\n", "Clock B,Tick number 5,Time = 6.0000 minutes\n", "Clock B,Tick number 6,Time = 7.5000 minutes\n", "Clock A,Tick number 5,Time = 8.0000 minutes\n", "Clock B,Tick number 7,Time = 9.0000 minutes\n" ] } ], "source": [ "import simpy\n", "\n", "class clock(object):\n", " def __init__(self,env,name,tick):\n", " self.env = env\n", " self.name = name\n", " self.tick = tick\n", " self.nTicks = 0\n", " self.proc = env.process(self.run())\n", "\n", " def run(self):\n", " while True:\n", " self.nTicks += 1\n", " print(\"Clock {:s},\".format(self.name),end=''),\n", " print(\"Tick number {:d},\".format(self.nTicks),end=''),\n", " print(\"Time = {:6.4f} minutes\".format(self.env.now))\n", " yield env.timeout(self.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", "a = clock(env, \"A\", 2.0)\n", "b = clock(env, \"B\", 1.5)\n", "\n", "# run the simulation for a fixed period of time\n", "env.run(until=10)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": {} }, "source": [ "### Adding a Reporter\n", "\n", "A reporter process periodically gathers selected information for display or logging. Here we demonstrate a reporter that can be assigned to report the value of a particular class of price models." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "pycharm": {} }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1.0 82.6574856726\n", "2.0 156.937218716\n", "3.0 145.560995903\n", "4.0 190.832981281\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 + \\\n", " 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=5)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": {} }, "source": [ "## Application Examples" ] }, { "cell_type": "markdown", "metadata": { "pycharm": {} }, "source": [ "## Multiple Roombas" ] }, { "cell_type": "markdown", "metadata": { "pycharm": {} }, "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": 7, "metadata": { "pycharm": {} }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "A start cleaning at 0.0\n", "B start cleaning at 0.0\n", "B start charging at 0.9\n", "A start charging at 1.1\n", "A start cleaning at 3.4\n", "B start cleaning at 4.0\n", "A start charging at 4.5\n", "B start charging at 4.9\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 charging at {:4.1f}\".format(self.name,env.now))\n", " yield env.timeout(self.clean_duration)\n", " \n", " def charge(self):\n", " print(\"{:<3s} start cleaning 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=6)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true, "pycharm": {} }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": { "pycharm": {} }, "source": [ "\n", "< [Simulation of Discrete Event Systems](http://nbviewer.jupyter.org/github/jckantor/CBE40455/blob/master/notebooks/02.00-Simulation-of-Discrete-Event-Systems.ipynb) | [Contents](toc.ipynb) | [Queuing Systems](http://nbviewer.jupyter.org/github/jckantor/CBE40455/blob/master/notebooks/02.02-Queuing-Systems.ipynb) >

\"Open

\"Download\"" ] } ], "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.6.1" } }, "nbformat": 4, "nbformat_minor": 1 }