{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# GPSS2DEVS Example: Manufacturing Shop\n",
    "\n",
    "Let's take a look at an example from G. Gordon's _System Simulation second edition_ : the manufacturing shop.\n",
    "\n",
    "A machine tool in a manufacturing shop is turning out parts at the rate of one every 5 minutes and places them on a conveyor that carries the parts towards three inspectors. It takes 2 minutes to reach the first inspector; if he is free at the time the part arrives, he takes it for inspection. If he is busy at that time, the part takes a further 2 minutes to reach the second inspector, who takes the part if he is not busy. Parts that pass the second inspector may get picked up by the third inspector, who is a further 2 minutes along the conveyor belt; otherwise they are lost. To keep the model small, only the transit time of the parts will be recorded and the possibility of the inspectors rejecting the parts will be ignored. Each inspector takes 12 &plusmn; 9 minutes per inspection.\n",
    "\n",
    "```\n",
    "*                 Manufacturing shop model 5\n",
    "*                 G. Gordon Figure 11-1/9-10\n",
    "        SIMULATE\n",
    "  L0    GENERATE   5                            ; Create parts\n",
    "  L7    ADVANCE    2                            ; Move to the first inspector\n",
    "  L8    TRANSFER   BOTH,L5,CONV1                ; Check if first inspector is busy\n",
    "  L5    SEIZE      INSP1                        ; The first inspector becomes busy\n",
    "  L1    ADVANCE    12,9                         ; Inspect\n",
    "  L9    RELEASE    INSP1                        ; Free inspector 1\n",
    "  TAB   TABULATE   TRANSIT                      ; Tabulate parts' transit time\n",
    "  ACC   TERMINATE  1                            ; Accepted parts\n",
    "  CONV1 ADVANCE    2                            ; Move to 2nd inspector\n",
    "  C2    TRANSFER   BOTH,L13,CONV2               ; Check if 2nd inspector is busy\n",
    "  L13   SEIZE      INSP2                        ; The 2nd inspector becomes busy\n",
    "  L15   ADVANCE    12,9                         ; Inspect\n",
    "  L17   RELEASE    INSP2                        ; Free inspector 2\n",
    "  L19   TRANSFER   ,TAB                         ; To tabulate\n",
    "  CONV2 ADVANCE    2                            ; Move to 3rd inspector\n",
    "  C3    TRANSFER   BOTH,L14,TERM                ; Check if 3rd inspector is busy\n",
    "  L14   SEIZE      INSP3                        ; The third inspector becomes busy\n",
    "  L16   ADVANCE    12,9                         ; Inspect\n",
    "  L18   RELEASE    INSP3                        ; Free inspector 3\n",
    "  L20   TRANSFER   ,TAB                         ; To tabulate\n",
    "  TERM  TERMINATE                               ; Remain uninspected\n",
    "  \n",
    "  TRANSIT TABLE      M1,5,5,10\n",
    "        START      10,NP\n",
    "        RESET\n",
    "        START      1000\n",
    "```\n",
    "\n",
    "Visually, this example can be represented as follows:<br>\n",
    "<img src=\"manufacturing_5_GPSS.png\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's translate this into `Python(P)DEVS`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pypdevsbbl.domain.gpss import GPSS2DEVS, dist\n",
    "\n",
    "class Model(GPSS2DEVS):        \n",
    "    def __init__(self):\n",
    "        super().__init__(\"Manufacturing\")\n",
    "        \n",
    "        self.createFacility(\"INSP1\")\n",
    "        self.createFacility(\"INSP2\")\n",
    "        self.createFacility(\"INSP3\")\n",
    "        self.createTable(\"TRANSIT\", \"M1\", 5, 5, 10)\n",
    "        \n",
    "        self.GENERATE(\"L0\", dist=lambda x: 5)\n",
    "        self.ADVANCE(\"L7\", dist=lambda x: 2, prev=\"L0\")\n",
    "        self.SEIZE(\"L5\", \"INSP1\")\n",
    "        self.ADVANCE(\"L1\", dist=dist, args=(12,9), prev=\"L5\")\n",
    "        self.RELEASE(\"L9\", \"INSP1\")\n",
    "        self.TABULATE(\"TAB\", \"TRANSIT\")\n",
    "        self.TERMINATE(\"ACC\", '1')\n",
    "        self.TRANSFER_BOTH(\"L8\", \"L5\", \"INSP1\", self.GPSS2DEVS_5_L9.facility)\n",
    "        self.ADVANCE(\"CONV1\", dist=lambda x: 2, prev=\"L8\")\n",
    "        self.SEIZE(\"L13\", \"INSP2\")\n",
    "        self.ADVANCE(\"L15\", dist=dist, args=(12,9), prev=\"L13\")\n",
    "        self.RELEASE(\"L17\", \"INSP2\")\n",
    "        self.TRANSFER_BOTH(\"C2\", \"L13\", \"INSP2\", self.GPSS2DEVS_5_L17.facility)\n",
    "        self.ADVANCE(\"CONV2\", dist=lambda x: 2, prev=\"C2\")\n",
    "        self.SEIZE(\"L14\", \"INSP3\")\n",
    "        self.ADVANCE(\"L16\", dist=dist, args=(12,9), prev=\"L14\")\n",
    "        self.RELEASE(\"L18\", \"INSP3\")\n",
    "        self.TRANSFER_BOTH(\"C3\", \"L14\", \"INSP3\", self.GPSS2DEVS_5_L18.facility)\n",
    "        self.TERMINATE(\"TERM\")\n",
    "        \n",
    "        \n",
    "        self.connectPorts(self.GPSS2DEVS_4_L0.output, self.GPSS2DEVS_1_L7.input)\n",
    "        self.connectPorts(self.GPSS2DEVS_4_L7.output, self.GPSS2DEVS_5_L8.input)\n",
    "        self.connectPorts(self.GPSS2DEVS_4_L5.output, self.GPSS2DEVS_1_L1.input)\n",
    "        self.connectPorts(self.GPSS2DEVS_4_L1.output, self.GPSS2DEVS_5_L9.input)\n",
    "        self.connectPorts(self.GPSS2DEVS_4_L9.output, self.GPSS2DEVS_5_TAB.input)\n",
    "        self.connectPorts(self.GPSS2DEVS_4_TAB.output, self.GPSS2DEVS_5_ACC.input)\n",
    "        self.connectPorts(self.GPSS2DEVS_4_L8.output, self.GPSS2DEVS_1_CONV1.input)\n",
    "        self.connectPorts(self.GPSS2DEVS_4_CONV1.output, self.GPSS2DEVS_5_C2.input)\n",
    "        self.connectPorts(self.GPSS2DEVS_4_L13.output, self.GPSS2DEVS_1_L15.input)\n",
    "        self.connectPorts(self.GPSS2DEVS_4_L15.output, self.GPSS2DEVS_5_L17.input)\n",
    "        self.connectPorts(self.GPSS2DEVS_4_L17.output, self.GPSS2DEVS_5_TAB.input)\n",
    "        self.connectPorts(self.GPSS2DEVS_4_C2.output, self.GPSS2DEVS_1_CONV2.input)\n",
    "        self.connectPorts(self.GPSS2DEVS_4_CONV2.output, self.GPSS2DEVS_5_C3.input)\n",
    "        self.connectPorts(self.GPSS2DEVS_4_L14.output, self.GPSS2DEVS_1_L16.input)\n",
    "        self.connectPorts(self.GPSS2DEVS_4_L16.output, self.GPSS2DEVS_5_L18.input)\n",
    "        self.connectPorts(self.GPSS2DEVS_4_L18.output, self.GPSS2DEVS_5_TAB.input)\n",
    "        self.connectPorts(self.GPSS2DEVS_4_C3.output, self.GPSS2DEVS_5_TERM.input)\n",
    "        "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We follow the basic transformation rules and obtain the above class. Notice how the `GENERATE` block names are prepended with spaces? This enforces the `GENERATE` blocks to be executed **before** the `Controller`. If we don't do this, we obtain a different result compared to _GPSS World_.\n",
    "\n",
    "Also notice how the `UNLINK` block has multiple outputs that must have a corresponding `Hold` block. Furthermore, a user chain works exactly like an `ADVANCE` block as far as the `Controller` is concerned."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "       name:  TRANSIT\n",
      "      count:  10000\n",
      "       mean:  15.6721\n",
      "   variance:  32.38682027202719\n",
      "  deviation:  5.69094194945153\n",
      "  intervals:\n",
      "             -inf:  0\n",
      "                5:  201\n",
      "               10:  2086\n",
      "               15:  2636\n",
      "               20:  2628\n",
      "               25:  2197\n",
      "               30:  252\n",
      "               35:  0\n",
      "               40:  0\n",
      "              inf:  0\n",
      "\n",
      "       name:  INSP1\n",
      "      entry:  3980\n",
      "       util:  0.8602931963413106\n",
      "    transit:  12.075879396984925\n",
      "      owner:  None\n",
      "      inter:  False\n",
      "       busy:  False\n",
      "       name:  INSP2\n",
      "      entry:  3493\n",
      "       util:  0.7418586748303704\n",
      "    transit:  11.863154881190953\n",
      "      owner:  Transaction(11183, {'M1': 55920.0, 'PR': 0})\n",
      "      inter:  False\n",
      "       busy:  True\n",
      "       name:  INSP3\n",
      "      entry:  2527\n",
      "       util:  0.5382467195359911\n",
      "    transit:  11.898298377522755\n",
      "      owner:  None\n",
      "      inter:  False\n",
      "       busy:  False\n"
     ]
    }
   ],
   "source": [
    "from pypdevs.simulator import Simulator\n",
    "\n",
    "model = Model()\n",
    "sim = Simulator(model)\n",
    "sim.setClassicDEVS()\n",
    "# sim.setVerbose(None)\n",
    "\n",
    "sim.setTerminationCondition(lambda _, m: m.controller().finished(10))\n",
    "# sim.setVerbose(None)\n",
    "sim.simulate()\n",
    "\n",
    "# RESET\n",
    "model.facility(\"INSP1\").clear()\n",
    "model.facility(\"INSP2\").clear()\n",
    "model.facility(\"INSP3\").clear()\n",
    "model.table(\"TRANSIT\").clear()\n",
    "model.controller().reset()\n",
    "\n",
    "sim.setTerminationCondition(lambda _, m: m.controller().finished(10000))\n",
    "sim.simulate()\n",
    "\n",
    "# res = {\n",
    "#     \"count\": [],\n",
    "#     \"mean\": [],\n",
    "#     \"deviation\": []\n",
    "# }\n",
    "\n",
    "# N = 10000\n",
    "\n",
    "# for i in range(1, N + 1):\n",
    "#     sim.setTerminationCondition(lambda _, m: m.controller().finished(i))\n",
    "#     sim.simulate()\n",
    "#     summ = model.table(\"TRANSIT\").summary()\n",
    "#     for k in res:\n",
    "#         res[k].append(summ[k])\n",
    "        \n",
    "\n",
    "# with open(\"MS5-DEVS.csv\", 'w') as file:\n",
    "#     for i in range(N):\n",
    "#         file.write(\"%i,%f,%f\\n\" % (res[\"count\"][i], res[\"mean\"][i], res[\"deviation\"][i]))\n",
    "        \n",
    "# print(\"DONE\")\n",
    "\n",
    "for k, v in model.table(\"TRANSIT\").summary().items():\n",
    "    if k == \"intervals\":\n",
    "        print(k.rjust(11) + \":\")\n",
    "        for u, c in sorted(v.items()):\n",
    "            print(\" \" * 12, str(u).rjust(4) + \": \", c)\n",
    "    else:\n",
    "        print(k.rjust(11) + \": \", v)\n",
    "        \n",
    "print()\n",
    "\n",
    "for k, v in model.facility(\"INSP1\").summary().items():\n",
    "    print(k.rjust(11) + \": \", v)\n",
    "for k, v in model.facility(\"INSP2\").summary().items():\n",
    "    print(k.rjust(11) + \": \", v)\n",
    "for k, v in model.facility(\"INSP3\").summary().items():\n",
    "    print(k.rjust(11) + \": \", v)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, let's compare this to the results we obtain from _GPSS World_ :\n",
    "```\n",
    "TABLE              MEAN    STD.DEV.       RANGE           RETRY FREQUENCY CUM.%\n",
    " TRANSIT          15.656    5.457                           0\n",
    "                                   5.000  -       10.000           187    18.70\n",
    "                                  10.000  -       15.000           269    45.60\n",
    "                                  15.000  -       20.000           284    74.00\n",
    "                                  20.000  -       25.000           228    96.80\n",
    "                                  25.000  -       30.000            32   100.00\n",
    "```\n",
    "\n",
    "The result obtained from _GPSS World_ differs slightly from our results. This is due to the random number generators behind the scenes. The results we obtain always lie within close proximity to the expected results. If we discretize the results, the exact same values are yielded in comparison to _GPSS World_.\n",
    "\n",
    "The keys in the `TRANSIT`'s summary correspond as follows to the _GPSS World_ results:\n",
    "\n",
    "| summary key  | _GPSS World_ key | description |\n",
    "|:---:|:---:|:---|\n",
    "| `name` | `TABLE` | The name of the table that is summarized. |\n",
    "| `count` | -- | The total amount of transactions that were tableized. |\n",
    "| `mean` | `MEAN` | The average of the tableized values. |\n",
    "| `variance` | -- | The variance of the tableized values. |\n",
    "| `deviation` | `STD.DEV` | The standard deviation of the tableized values. |\n",
    "| `intervals` | `RANGE` | A sub-table, containing the intervals in which the results lie.<br>Within the DEVS solution, the keys represent the upper bounds of the range. |\n",
    "| -- | `RETRY` | The amount of retries. |"
   ]
  }
 ],
 "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.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
