KarrLab/de_sim

View on GitHub
de_sim/examples/jupyter_examples_for_talk/1. DE-Sim tutorial.ipynb

Summary

Maintainability
Test Coverage
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- :Author: Arthur Goldberg <Arthur.Goldberg@mssm.edu> -->\n",
    "<!-- :Date: 2020-07-13 -->\n",
    "<!-- :Copyright: 2020, Karr Lab -->\n",
    "<!-- :License: MIT -->\n",
    "# DE-Sim tutorial\n",
    "\n",
    "DE-Sim is an open-source, object-oriented, discrete-event simulation (OO DES) tool implemented in Python.\n",
    "DE-Sim makes it easy to build and simulate discrete-event models.\n",
    "This page introduces the basic concepts of discrete-event modeling and teaches you how to build and simulate discrete-event models with DE-Sim. \n",
    "\n",
    "## Installation\n",
    "Use `pip` to install `de_sim`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: de_sim in /root/.wc/wc_sandbox/packages/de_sim (0.0.7)\n",
      "Requirement already satisfied: configobj in /usr/local/lib/python3.7/site-packages (from de_sim) (5.0.6)\n",
      "Requirement already satisfied: logging2 in /usr/local/lib/python3.7/site-packages (from de_sim) (0.1.2)\n",
      "Requirement already satisfied: matplotlib in /usr/local/lib/python3.7/site-packages (from de_sim) (3.1.2)\n",
      "Requirement already satisfied: numpy in /usr/local/lib/python3.7/site-packages (from de_sim) (1.18.1)\n",
      "Requirement already satisfied: progressbar2>=3.39 in /usr/local/lib/python3.7/site-packages (from de_sim) (3.47.0)\n",
      "Requirement already satisfied: pympler in /usr/local/lib/python3.7/site-packages (from de_sim) (0.8)\n",
      "Requirement already satisfied: setuptools in /usr/local/lib/python3.7/site-packages (from de_sim) (44.0.0)\n",
      "Requirement already satisfied: wc_utils[git]>=0.0.16 in /root/.wc/wc_sandbox/packages/wc_utils (from de_sim) (0.0.21)\n",
      "Requirement already satisfied: six in /usr/local/lib/python3.7/site-packages/six-1.12.0-py3.7.egg (from configobj->de_sim) (1.12.0)\n",
      "Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/site-packages (from matplotlib->de_sim) (1.1.0)\n",
      "Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.7/site-packages (from matplotlib->de_sim) (2.8.1)\n",
      "Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.7/site-packages (from matplotlib->de_sim) (2.4.6)\n",
      "Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.7/site-packages (from matplotlib->de_sim) (0.10.0)\n",
      "Requirement already satisfied: python-utils>=2.3.0 in /usr/local/lib/python3.7/site-packages (from progressbar2>=3.39->de_sim) (2.3.0)\n",
      "Requirement already satisfied: abduct in /usr/local/lib/python3.7/site-packages (from wc_utils[git]>=0.0.16->de_sim) (2.0.1)\n",
      "Requirement already satisfied: attrdict in /usr/local/lib/python3.7/site-packages (from wc_utils[git]>=0.0.16->de_sim) (2.0.1)\n",
      "Requirement already satisfied: dataclasses in /usr/local/lib/python3.7/site-packages (from wc_utils[git]>=0.0.16->de_sim) (0.6)\n",
      "Requirement already satisfied: diskcache in /usr/local/lib/python3.7/site-packages (from wc_utils[git]>=0.0.16->de_sim) (4.1.0)\n",
      "Requirement already satisfied: humanfriendly in /usr/local/lib/python3.7/site-packages (from wc_utils[git]>=0.0.16->de_sim) (4.18)\n",
      "Requirement already satisfied: mendeleev in /usr/local/lib/python3.7/site-packages (from wc_utils[git]>=0.0.16->de_sim) (0.5.1)\n",
      "Requirement already satisfied: natsort in /usr/local/lib/python3.7/site-packages (from wc_utils[git]>=0.0.16->de_sim) (6.2.0)\n",
      "Requirement already satisfied: objsize in /usr/local/lib/python3.7/site-packages (from wc_utils[git]>=0.0.16->de_sim) (0.3.2)\n",
      "Requirement already satisfied: openpyxl<=3.0.1,>=2.6.1 in /usr/local/lib/python3.7/site-packages (from wc_utils[git]>=0.0.16->de_sim) (3.0.1)\n",
      "Requirement already satisfied: pint>=0.10 in /usr/local/lib/python3.7/site-packages (from wc_utils[git]>=0.0.16->de_sim) (0.10)\n",
      "Requirement already satisfied: pronto>=1 in /usr/local/lib/python3.7/site-packages (from wc_utils[git]>=0.0.16->de_sim) (1.1.3)\n",
      "Requirement already satisfied: pyexcel>=0.5.9.1 in /usr/local/lib/python3.7/site-packages (from wc_utils[git]>=0.0.16->de_sim) (0.5.15)\n",
      "Requirement already satisfied: pyexcel_io>=0.5.9.1 in /usr/local/lib/python3.7/site-packages (from wc_utils[git]>=0.0.16->de_sim) (0.5.20)\n",
      "Requirement already satisfied: pyyaml>=5.1 in /usr/local/lib/python3.7/site-packages (from wc_utils[git]>=0.0.16->de_sim) (5.3)\n",
      "Requirement already satisfied: qualname in /usr/local/lib/python3.7/site-packages (from wc_utils[git]>=0.0.16->de_sim) (0.1.0)\n",
      "Requirement already satisfied: requests in /usr/local/lib/python3.7/site-packages/requests-2.22.0-py3.7.egg (from wc_utils[git]>=0.0.16->de_sim) (2.22.0)\n",
      "Requirement already satisfied: xlsxwriter in /usr/local/lib/python3.7/site-packages (from wc_utils[git]>=0.0.16->de_sim) (1.2.7)\n",
      "Requirement already satisfied: gitpython in /usr/local/lib/python3.7/site-packages (from wc_utils[git]>=0.0.16->de_sim) (3.0.5)\n",
      "Requirement already satisfied: pygithub in /usr/local/lib/python3.7/site-packages (from wc_utils[git]>=0.0.16->de_sim) (1.45)\n",
      "Requirement already satisfied: contextlib2 in /usr/local/lib/python3.7/site-packages (from abduct->wc_utils[git]>=0.0.16->de_sim) (0.6.0.post1)\n",
      "Requirement already satisfied: sqlalchemy in /usr/local/lib/python3.7/site-packages (from mendeleev->wc_utils[git]>=0.0.16->de_sim) (1.3.12)\n",
      "Requirement already satisfied: pandas in /usr/local/lib/python3.7/site-packages (from mendeleev->wc_utils[git]>=0.0.16->de_sim) (0.25.3)\n",
      "Requirement already satisfied: pyfiglet in /usr/local/lib/python3.7/site-packages (from mendeleev->wc_utils[git]>=0.0.16->de_sim) (0.8.post1)\n",
      "Requirement already satisfied: colorama in /usr/local/lib/python3.7/site-packages (from mendeleev->wc_utils[git]>=0.0.16->de_sim) (0.4.3)\n",
      "Requirement already satisfied: et-xmlfile in /usr/local/lib/python3.7/site-packages (from openpyxl<=3.0.1,>=2.6.1->wc_utils[git]>=0.0.16->de_sim) (1.0.1)\n",
      "Requirement already satisfied: jdcal in /usr/local/lib/python3.7/site-packages (from openpyxl<=3.0.1,>=2.6.1->wc_utils[git]>=0.0.16->de_sim) (1.4.1)\n",
      "Requirement already satisfied: networkx~=2.3 in /usr/local/lib/python3.7/site-packages (from pronto>=1->wc_utils[git]>=0.0.16->de_sim) (2.4)\n",
      "Requirement already satisfied: contexter~=0.1.4 in /usr/local/lib/python3.7/site-packages (from pronto>=1->wc_utils[git]>=0.0.16->de_sim) (0.1.4)\n",
      "Requirement already satisfied: frozendict~=1.2 in /usr/local/lib/python3.7/site-packages (from pronto>=1->wc_utils[git]>=0.0.16->de_sim) (1.2)\n",
      "Requirement already satisfied: fastobo~=0.6.0 in /usr/local/lib/python3.7/site-packages (from pronto>=1->wc_utils[git]>=0.0.16->de_sim) (0.6.1)\n",
      "Requirement already satisfied: nanoset~=0.1.2; platform_python_implementation == \"CPython\" in /usr/local/lib/python3.7/site-packages (from pronto>=1->wc_utils[git]>=0.0.16->de_sim) (0.1.3)\n",
      "Requirement already satisfied: chardet~=3.0 in /usr/local/lib/python3.7/site-packages/chardet-3.0.4-py3.7.egg (from pronto>=1->wc_utils[git]>=0.0.16->de_sim) (3.0.4)\n",
      "Requirement already satisfied: texttable>=0.8.2 in /usr/local/lib/python3.7/site-packages (from pyexcel>=0.5.9.1->wc_utils[git]>=0.0.16->de_sim) (1.6.2)\n",
      "Requirement already satisfied: lml>=0.0.4 in /usr/local/lib/python3.7/site-packages (from pyexcel>=0.5.9.1->wc_utils[git]>=0.0.16->de_sim) (0.0.9)\n",
      "Requirement already satisfied: idna<2.9,>=2.5 in /usr/local/lib/python3.7/site-packages/idna-2.8-py3.7.egg (from requests->wc_utils[git]>=0.0.16->de_sim) (2.8)\n",
      "Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/site-packages (from requests->wc_utils[git]>=0.0.16->de_sim) (1.24.2)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/site-packages/certifi-2019.9.11-py3.7.egg (from requests->wc_utils[git]>=0.0.16->de_sim) (2019.9.11)\n",
      "Requirement already satisfied: gitdb2>=2.0.0 in /usr/local/lib/python3.7/site-packages (from gitpython->wc_utils[git]>=0.0.16->de_sim) (2.0.6)\n",
      "Requirement already satisfied: deprecated in /usr/local/lib/python3.7/site-packages (from pygithub->wc_utils[git]>=0.0.16->de_sim) (1.2.7)\n",
      "Requirement already satisfied: pyjwt in /usr/local/lib/python3.7/site-packages (from pygithub->wc_utils[git]>=0.0.16->de_sim) (1.7.1)\n",
      "Requirement already satisfied: pytz>=2017.2 in /usr/local/lib/python3.7/site-packages (from pandas->mendeleev->wc_utils[git]>=0.0.16->de_sim) (2019.3)\n",
      "Requirement already satisfied: decorator>=4.3.0 in /usr/local/lib/python3.7/site-packages (from networkx~=2.3->pronto>=1->wc_utils[git]>=0.0.16->de_sim) (4.4.1)\n",
      "Requirement already satisfied: smmap2>=2.0.0 in /usr/local/lib/python3.7/site-packages (from gitdb2>=2.0.0->gitpython->wc_utils[git]>=0.0.16->de_sim) (2.0.5)\n",
      "Requirement already satisfied: wrapt<2,>=1.10 in /usr/local/lib/python3.7/site-packages (from deprecated->pygithub->wc_utils[git]>=0.0.16->de_sim) (1.11.2)\n",
      "\u001b[31mERROR: Error while checking for conflicts. Please file an issue on pip's issue tracker: https://github.com/pypa/pip/issues/new\n",
      "Traceback (most recent call last):\n",
      "  File \"/usr/local/lib/python3.7/site-packages/pip/_vendor/pkg_resources/__init__.py\", line 3021, in _dep_map\n",
      "    return self.__dep_map\n",
      "  File \"/usr/local/lib/python3.7/site-packages/pip/_vendor/pkg_resources/__init__.py\", line 2815, in __getattr__\n",
      "    raise AttributeError(attr)\n",
      "AttributeError: _DistInfoDistribution__dep_map\n",
      "\n",
      "During handling of the above exception, another exception occurred:\n",
      "\n",
      "Traceback (most recent call last):\n",
      "  File \"/usr/local/lib/python3.7/site-packages/pip/_vendor/pkg_resources/__init__.py\", line 3012, in _parsed_pkg_info\n",
      "    return self._pkg_info\n",
      "  File \"/usr/local/lib/python3.7/site-packages/pip/_vendor/pkg_resources/__init__.py\", line 2815, in __getattr__\n",
      "    raise AttributeError(attr)\n",
      "AttributeError: _pkg_info\n",
      "\n",
      "During handling of the above exception, another exception occurred:\n",
      "\n",
      "Traceback (most recent call last):\n",
      "  File \"/usr/local/lib/python3.7/site-packages/pip/_internal/commands/install.py\", line 535, in _determine_conflicts\n",
      "    return check_install_conflicts(to_install)\n",
      "  File \"/usr/local/lib/python3.7/site-packages/pip/_internal/operations/check.py\", line 108, in check_install_conflicts\n",
      "    package_set, _ = create_package_set_from_installed()\n",
      "  File \"/usr/local/lib/python3.7/site-packages/pip/_internal/operations/check.py\", line 50, in create_package_set_from_installed\n",
      "    package_set[name] = PackageDetails(dist.version, dist.requires())\n",
      "  File \"/usr/local/lib/python3.7/site-packages/pip/_vendor/pkg_resources/__init__.py\", line 2736, in requires\n",
      "    dm = self._dep_map\n",
      "  File \"/usr/local/lib/python3.7/site-packages/pip/_vendor/pkg_resources/__init__.py\", line 3023, in _dep_map\n",
      "    self.__dep_map = self._compute_dependencies()\n",
      "  File \"/usr/local/lib/python3.7/site-packages/pip/_vendor/pkg_resources/__init__.py\", line 3032, in _compute_dependencies\n",
      "    for req in self._parsed_pkg_info.get_all('Requires-Dist') or []:\n",
      "  File \"/usr/local/lib/python3.7/site-packages/pip/_vendor/pkg_resources/__init__.py\", line 3014, in _parsed_pkg_info\n",
      "    metadata = self.get_metadata(self.PKG_INFO)\n",
      "  File \"/usr/local/lib/python3.7/site-packages/pip/_vendor/pkg_resources/__init__.py\", line 1895, in get_metadata\n",
      "    raise KeyError(\"No metadata except PKG-INFO is available\")\n",
      "KeyError: 'No metadata except PKG-INFO is available'\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "!pip install de_sim"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![gray_line](gray_horiz_line.svg)\n",
    "\n",
    "## DE-Sim model of a one-dimensional random walk\n",
    "\n",
    "<font size=\"4\">Three steps: define an event message class; define a simulation object class; and build and run a simulation.</font>\n",
    "\n",
    "### 1: Create an event message class by subclassing [`EventMessage`](https://docs.karrlab.org/de_sim/master/source/de_sim.html#de_sim.event_message.EventMessage).\n",
    "\n",
    "<font size=\"4\">Each DE-Sim event contains an event message that provides data to the simulation object which executes the event.\n",
    "The random walk model sends event messages that contain the value of a random step.</font>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import de_sim\n",
    "\n",
    "class RandomStepMessage(de_sim.EventMessage):\n",
    "    \"An event message class that stores the value of a random walk step\"\n",
    "    step_value: float"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![gray_line](gray_horiz_line.svg)\n",
    "\n",
    "### 2: Subclass `SimulationObject` to define a simulation object class\n",
    "\n",
    "<font size=\"4\">\n",
    "Simulation objects are like threads: a simulation's scheduler decides when to execute them, and their execution is suspended when they have no work to do.\n",
    "But a DES scheduler schedules simulation objects to ensure that events occur in simulation time order. Precisely, the fundamental invariant of discrete-event simulation:\n",
    "<br>\n",
    "<br>\n",
    "1. All events in a simulation are executed in non-decreasing time order.\n",
    "\n",
    "By guaranteeing this behavior, the DE-Sim scheduler ensures that causality relationships between events are respected.\n",
    "\n",
    "This invariant has two consequences:\n",
    "\n",
    "1. All synchronization between simulation objects is controlled by the simulation times of events.\n",
    "2. Each simulation object executes its events in non-decreasing time order.\n",
    "\n",
    "The Python classes that generate and handle simulation events are simulation object classes, subclasses of `SimulationObject` which uses a custom class creation method that gives special meaning to certain methods and attributes.\n",
    "\n",
    "Below, we define a simulation object class that models a random walk which randomly selects the time delay between steps, and illustrates all key features of `SimulationObject`.\n",
    "</font>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "\n",
    "class RandomWalkSimulationObject(de_sim.SimulationObject):\n",
    "    \" A 1D random walk model, with random delays between steps \"\n",
    "\n",
    "    def __init__(self, name):\n",
    "        super().__init__(name)\n",
    "\n",
    "    def init_before_run(self):\n",
    "        \" Initialize before a simulation run; called by the simulator \"\n",
    "        self.position = 0\n",
    "        self.history = {'times': [0],\n",
    "                        'positions': [0]}\n",
    "        self.schedule_next_step()\n",
    "\n",
    "    def schedule_next_step(self):\n",
    "        \" Schedule the next event, which is a step \"\n",
    "        # A step moves -1 or +1 with equal probability\n",
    "        step_value = random.choice([-1, +1])\n",
    "        # The time between steps is 1 or 2, with equal probability\n",
    "        delay = random.choice([1, 2])\n",
    "        # Schedule an event `delay` in the future for this object\n",
    "        # The event contains a `RandomStepMessage` with `step_value=step_value`\n",
    "        self.send_event(delay, self, RandomStepMessage(step_value))\n",
    "\n",
    "    def handle_step_event(self, event):\n",
    "        \" Handle a step event \"\n",
    "        # Update the position and history\n",
    "        self.position += event.message.step_value\n",
    "        self.history['times'].append(self.time)\n",
    "        self.history['positions'].append(self.position)\n",
    "        self.schedule_next_step()\n",
    "\n",
    "    # `event_handlers` contains pairs that map each event message class\n",
    "    # received by this simulation object to the method that handles\n",
    "    # the event message class\n",
    "    event_handlers = [(RandomStepMessage, handle_step_event)]\n",
    "\n",
    "    # messages_sent registers all message types sent by this object\n",
    "    messages_sent = [RandomStepMessage]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font size=\"4\">\n",
    "DE-Sim simulation objects employ special methods and attributes:\n",
    "<br>\n",
    "\n",
    "* Special `SimulationObject` methods:\n",
    "    1. **`init_before_run`** (optional): immediately before a simulation run, the simulator calls each simulation object’s `init_before_run` method. In this method simulation objects can send initial events and perform other initializations.\n",
    "    2. **`send_event`**: `send_event(delay, receiving_object, event_message)` schedules an event to occur `delay` time units in the future at simulation object `receiving_object`. `event_message` must be an [`EventMessage`](https://docs.karrlab.org/de_sim/master/source/de_sim.html#de_sim.event_message.EventMessage) instance. An event can be scheduled for any simulation object in a simulation.\n",
    "The event will be executed at its scheduled simulation time by an event handler in the simulation object `receiving_object`.\n",
    "The `event` parameter in the handler will be the scheduled event, which contains `event_message` in its `message` attribute.\n",
    "    3. **event handlers**: Event handlers have the signature `event_handler(self, event)`, where `event` is a simulation event. A subclass of `SimulationObject` must define at least one event handler, as illustrated by `handle_step_event` above.\n",
    "<br>\n",
    "<br>\n",
    "* Special `SimulationObject` attributes:\n",
    "    1. **`event_handlers`**: a simulation object can receive arbitrarily many types of event messages, and implement arbitrarily many event handlers. The attribute `event_handlers` contains an iterator over pairs that map each event message class received to the event handler which handles the event message class.\n",
    "    2. **`time`**: `time` is a read-only attribute that always equals the current simulation time.\n",
    "</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![gray_line](gray_horiz_line.svg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3: Execute a simulation by creating and initializing a [`Simulator`](https://docs.karrlab.org/de_sim/master/source/de_sim.html#de_sim.simulator.Simulator), and running the simulation.\n",
    "\n",
    "<font size=\"4\">\n",
    "The `Simulator` class simulates models.\n",
    "Its `add_object` method adds a simulation object to the simulator.\n",
    "Each object in a simulation must have a unique `name`.\n",
    "The `initialize` method, which calls each simulation object’s `init_before_run` method, must be called before a simulation starts.\n",
    "\n",
    "At least one simulation object in a simulation must schedule an initial event--otherwise the simulation cannot start.\n",
    "More generally, a simulation with no events to execute will terminate.\n",
    "\n",
    "Finally, `run` simulates a model. It takes the maximum time of a simulation run. `run` also takes several optional configuration arguments.\n",
    "</font>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Create a simulator\n",
    "simulator = de_sim.Simulator()\n",
    "\n",
    "# Create a random walk simulation object and add it to the simulation\n",
    "random_walk_sim_obj = RandomWalkSimulationObject('rand_walk')\n",
    "simulator.add_object(random_walk_sim_obj)\n",
    "\n",
    "# Initialize the simulation\n",
    "simulator.initialize()\n",
    "\n",
    "# Run the simulation until time 10\n",
    "max_time = 10\n",
    "simulator.run(max_time)\n",
    "\n",
    "# Plot the random walk\n",
    "import matplotlib.pyplot as plt\n",
    "import matplotlib.ticker as plticker\n",
    "\n",
    "fig, ax = plt.subplots()\n",
    "loc = plticker.MultipleLocator(base=1.0)\n",
    "ax.yaxis.set_major_locator(loc)\n",
    "plt.step(random_walk_sim_obj.history['times'],\n",
    "         random_walk_sim_obj.history['positions'],\n",
    "         where='post')\n",
    "plt.xlabel('Time')\n",
    "plt.ylabel('Position')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font size=\"4\">\n",
    "This example runs a simulation for `max_time` time units, and plots the random walk’s trajectory.\n",
    "\n",
    "This trajectory illustrates two key characteristics of discrete-event models. First, the state changes at discrete times.\n",
    "Second, since the state does not change between instantaneous events, the trajectory of any state variable is a step function.\n",
    "</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![gray_line](gray_horiz_line.svg)\n",
    "\n",
    "## DE-Sim example with multiple object instances\n",
    "\n",
    "<font size=\"4\">\n",
    "We show an DE-Sim implementation of the parallel hold (PHOLD) model, frequently used to benchmark parallel DES simulators.\n",
    "<br>\n",
    "<br>\n",
    "We illustrate these DE-Sim features:\n",
    "\n",
    "* Use multiple [`EventMessage`](https://docs.karrlab.org/de_sim/master/source/de_sim.html#de_sim.event_message.EventMessage) types\n",
    "* Run multiple instances of a simulation object type\n",
    "* Simulation objects scheduling events for each other\n",
    "</font>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\" Messages for the PHOLD benchmark for parallel discrete-event simulators \"\"\"\n",
    "import random\n",
    "\n",
    "class MessageSentToSelf(de_sim.EventMessage):\n",
    "    \"A message that's sent to self\"\n",
    "\n",
    "class MessageSentToOtherObject(de_sim.EventMessage):\n",
    "    \"A message that's sent to another PHold simulation object\"\n",
    "\n",
    "class InitMsg(de_sim.EventMessage):\n",
    "    \"An initialization message\"\n",
    "\n",
    "MESSAGE_TYPES = [MessageSentToSelf, MessageSentToOtherObject, InitMsg]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "class PholdSimulationObject(de_sim.SimulationObject):\n",
    "    \"\"\" Run a PHOLD simulation \"\"\"\n",
    "    def __init__(self, name, args):\n",
    "        self.args = args\n",
    "        super().__init__(name)\n",
    "\n",
    "    def init_before_run(self):\n",
    "        self.send_event(random.expovariate(1.0), self, InitMsg())\n",
    "\n",
    "    @staticmethod\n",
    "    def record_event_header():\n",
    "        print('\\t'.join(('Sender', 'Send', \"Receivr\",\n",
    "                         'Event', 'Message type')))\n",
    "        print('\\t'.join(('', 'time', '', 'time', '')))\n",
    "        \n",
    "    def record_event(self, event):\n",
    "        record_format = '{}\\t{:.2f}\\t{}\\t{:.2f}\\t{}'\n",
    "        print(record_format.format(event.sending_object.name,\n",
    "                                   event.creation_time,\n",
    "                                   event.receiving_object.name,\n",
    "                                   self.time,\n",
    "                                   type(event.message).__name__))\n",
    "\n",
    "    def handle_simulation_event(self, event):\n",
    "        \"\"\" Handle a simulation event \"\"\"\n",
    "        # Record this event\n",
    "        self.record_event(event)\n",
    "        # Schedule an event\n",
    "        if random.random() < self.args.frac_self_events or \\\n",
    "            self.args.num_phold_objects == 1:\n",
    "            receiver = self\n",
    "        else:\n",
    "            # Send the event to another randomly selected object\n",
    "            obj_index = random.randrange(self.args.num_phold_objects - 1)\n",
    "            if int(self.name) <= obj_index:\n",
    "                obj_index += 1\n",
    "            receiver = self.simulator.simulation_objects[str(obj_index)]\n",
    "\n",
    "        if receiver == self:\n",
    "            message_type = MessageSentToSelf\n",
    "        else:\n",
    "            message_type = MessageSentToOtherObject\n",
    "        self.send_event(random.expovariate(1.0), receiver, message_type())\n",
    "\n",
    "    event_handlers = [(sim_msg_type, 'handle_simulation_event') \\\n",
    "                      for sim_msg_type in MESSAGE_TYPES]\n",
    "\n",
    "    messages_sent = MESSAGE_TYPES"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font size=\"4\">\n",
    "The PHOLD model runs multiple instances of `PholdSimulationObject`.\n",
    "\n",
    "`create_and_run` creates the objects and adds them to the simulator.\n",
    "\n",
    "Each `PholdSimulationObject` object is initialized with `args`, an object that defines two attributes used by all objects:\n",
    "\n",
    "* `args.num_phold_objects`: the number of PHOLD objects running\n",
    "* `args.frac_self_events`: the fraction of events sent to self\n",
    "\n",
    "At time 0, each PHOLD object schedules an `InitMsg` event for itself that occurs after a random exponential time delay with mean = 1.0.\n",
    "\n",
    "The `handle_simulation_event` method handles all events.\n",
    "Each event schedules one more event.\n",
    "A random value in [0, 1) is used to decide whether to schedule the event for itself (with probability `args.frac_self_events`) or for another PHOLD object.\n",
    "\n",
    "If the event is scheduled for another PHOLD object, this gets a reference to the object: \n",
    "\n",
    "    receiver = self.simulator.simulation_objects[str(obj_index)]\n",
    "\n",
    "The attribute `self.simulator` always references the running simulator, and `self.simulator.simulation_objects` is a dictionary that maps simulation object names to simulation objects.\n",
    "</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font size=\"4\">\n",
    "Each event is printed by `record_event`.\n",
    "It accesses the DE-Sim `Event` object that is passed to all event handlers.\n",
    "`de_sim.event.Event` contains five useful fields:\n",
    "\n",
    "* `sending_object`: the object that created and sent the event\n",
    "* `creation_time`: the simulation time when the event was created (a.k.a. its *send time*)\n",
    "* `receiving_object`: the object that received the event\n",
    "* `event_time`: the simulation time when the event must execute (a.k.a. its *receive time*)\n",
    "* `message`: the [`EventMessage`](https://docs.karrlab.org/de_sim/master/source/de_sim.html#de_sim.event_message.EventMessage) carried by the event\n",
    "\n",
    "However, rather than use the event's `event_time`, `record_event` uses `self.time` to report the simulation time when the event is being executed, as they are always equal.\n",
    "</font>\n",
    "![gray_line](gray_horiz_line.svg)\n",
    "\n",
    "### Execute the simulation\n",
    "<font size=\"4\">\n",
    "Run a short simulation, and print all events:\n",
    "</font>\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_and_run(args):\n",
    "\n",
    "    # create a simulator\n",
    "    simulator = de_sim.Simulator()\n",
    "\n",
    "    # create simulation objects, and send each one an initial event message to self\n",
    "    for obj_id in range(args.num_phold_objects):\n",
    "        phold_obj = PholdSimulationObject(str(obj_id), args)\n",
    "        simulator.add_object(phold_obj)\n",
    "\n",
    "    # run the simulation\n",
    "    simulator.initialize()\n",
    "    PholdSimulationObject.record_event_header()\n",
    "    event_num = simulator.simulate(args.max_time).num_events\n",
    "    print(\"Executed {} events.\\n\".format(event_num))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sender\tSend\tReceivr\tEvent\tMessage type\n",
      "\ttime\t\ttime\t\n",
      "5\t0.00\t5\t0.19\tInitMsg\n",
      "3\t0.00\t3\t0.37\tInitMsg\n",
      "5\t0.19\t3\t0.38\tMessageSentToOtherObject\n",
      "1\t0.00\t1\t0.54\tInitMsg\n",
      "3\t0.37\t2\t0.59\tMessageSentToOtherObject\n",
      "2\t0.00\t2\t0.59\tInitMsg\n",
      "2\t0.59\t2\t0.72\tMessageSentToSelf\n",
      "2\t0.59\t3\t1.11\tMessageSentToOtherObject\n",
      "3\t0.38\t5\t1.16\tMessageSentToOtherObject\n",
      "3\t1.11\t3\t1.28\tMessageSentToSelf\n",
      "0\t0.00\t0\t1.48\tInitMsg\n",
      "2\t0.72\t0\t1.55\tMessageSentToOtherObject\n",
      "0\t1.48\t3\t1.80\tMessageSentToOtherObject\n",
      "Executed 13 events.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from argparse import Namespace\n",
    "args = Namespace(max_time=2,\n",
    "                 frac_self_events=0.3,\n",
    "                 num_phold_objects=6)\n",
    "create_and_run(args)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": false,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}