{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# GPSS2DEVS Example: Link and Unlink\n",
    "\n",
    "One of the biggest strengths in `GPSS` are the user chains. A user chain is some sort of global queue where a process flow can add transactions and another (or possibly the same) can remove them again. Let's look at a simple example.\n",
    "```\n",
    "       GENERATE   10               ; Create transactions\n",
    "       UNLINK     CHAIN,JOBQ,1     ; Remove a transaction from the chain.\n",
    "       TERMINATE                   ; Terminate the transaction that removed another\n",
    "                                   ;   from the chain.\n",
    "\n",
    "       GENERATE   6,,,,1           ; Create transactions to link onto the chain.\n",
    "       LINK       CHAIN,FIFO       ; Add a transaction onto the chain, following FIFO\n",
    "       \n",
    "JOBQ   ADVANCE    1                ; Wait 1 unit after unlinking\n",
    "       TERMINATE\n",
    "       \n",
    "       GENERATE   1                ; Global clock\n",
    "       TERMINATE  1\n",
    "       START      300              ; All cases covered multiple times in 300 time-units.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Because it's the `UNLINK` that identifies where the unlinked transactions go to, the `LINK` has no immediate connections to a next block. Instead, we have to link the `JOBQ` block to the `UNLINK`'s `B` output."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pypdevs.DEVS import CoupledDEVS\n",
    "from pypdevsbbl.domain.gpss import Transaction, Hold, Controller, Chain, SNA\n",
    "from pypdevsbbl.domain.gpss import ADVANCE, TERMINATE, LINK, UNLINK\n",
    "from pypdevsbbl.generic.generators import RandomDelayGenerator as GENERATE\n",
    "\n",
    "class Model(CoupledDEVS):        \n",
    "    def __init__(self):\n",
    "        super().__init__(\"Model\")\n",
    "        \n",
    "        self.controller = self.addSubModel(Controller(\"Controller\"))\n",
    "        self.CHAIN = self.addSubModel(Chain(\"CHAIN\"))\n",
    "        \n",
    "        self.B0 = self.addSubModel(\n",
    "            GENERATE(\"  B0\", dist=lambda x: 10,\n",
    "                      func=lambda x, t: Transaction(x, t), dt=None, base=1e6))\n",
    "        self.C0 = self.addSubModel(Hold(\"C0\"))\n",
    "        self.connectPorts(self.B0.output, self.C0.input)\n",
    "        self.connectPorts(self.B0.output, self.controller.create)\n",
    "        self.connectPorts(self.controller.broadcast, self.C0.release)\n",
    "        \n",
    "        self.B1 = self.addSubModel(UNLINK(\"B1\", 1, None, None))\n",
    "        self.C1 = self.addSubModel(Hold(\"C1\"))\n",
    "        self.C1B = self.addSubModel(Hold(\"C1B\", True))\n",
    "        self.connectPorts(self.C1B.contents, self.controller.moved)\n",
    "        self.connectPorts(self.C0.output, self.B1.input)\n",
    "        self.connectPorts(self.CHAIN.update, self.B1.chain)\n",
    "        self.connectPorts(self.B1.unlink, self.CHAIN.leave)\n",
    "        self.connectPorts(self.B1.output, self.C1.input)\n",
    "        self.connectPorts(self.B1.B, self.C1B.input)\n",
    "        self.connectPorts(self.controller.broadcast, self.C1.release)\n",
    "        self.connectPorts(self.controller.broadcast, self.C1B.release)\n",
    "        \n",
    "        self.B2 = self.addSubModel(TERMINATE(\"B2\"))\n",
    "        self.connectPorts(self.C1.output, self.B2.input)\n",
    "        self.connectPorts(self.B2.output, self.controller.terminate)\n",
    "        \n",
    "        \n",
    "        self.B3 = self.addSubModel(\n",
    "            GENERATE(\"  B3\", dist=lambda x: 6,\n",
    "                      func=lambda x, t: Transaction(x, t, priority=1), dt=None))\n",
    "        self.C3 = self.addSubModel(Hold(\"C3\"))\n",
    "        self.connectPorts(self.B3.output, self.C3.input)\n",
    "        self.connectPorts(self.B3.output, self.controller.create)\n",
    "        self.connectPorts(self.controller.broadcast, self.C3.release)\n",
    "        \n",
    "        self.B4 = self.addSubModel(LINK(\"B4\", \"FIFO\"))\n",
    "        self.connectPorts(self.B4.output, self.CHAIN.enter)\n",
    "        self.connectPorts(self.CHAIN.update, self.B4.chain)   \n",
    "        self.connectPorts(self.C3.output, self.B4.input)\n",
    "        self.connectPorts(self.C3.output, self.controller.delay)\n",
    "        \n",
    "        self.JOBQ = self.addSubModel(ADVANCE(\"JOBQ\", dist=lambda x: 1))\n",
    "        self.C5 = self.addSubModel(Hold(\"C5\", True))\n",
    "        self.connectPorts(self.JOBQ.output, self.C5.input)\n",
    "        self.connectPorts(self.C5.contents, self.controller.moved)\n",
    "        self.connectPorts(self.controller.pause, self.JOBQ.pause)\n",
    "        self.connectPorts(self.controller.broadcast, self.C5.release)\n",
    "        \n",
    "        self.connectPorts(self.C1B.output, self.JOBQ.input)    \n",
    "        self.connectPorts(self.C1B.output, self.controller.delay)\n",
    "        \n",
    "        self.B6 = self.addSubModel(TERMINATE(\"B6\"))\n",
    "        self.connectPorts(self.C5.output, self.B6.input)\n",
    "        self.connectPorts(self.B6.output, self.controller.terminate)"
   ]
  },
  {
   "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": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "       name:  CHAIN\n",
      "        max:  21\n",
      "       avgc:  10.0\n",
      "      entry:  50\n",
      "    transit:  60.0\n",
      "    current:  20\n"
     ]
    }
   ],
   "source": [
    "from pypdevs.simulator import Simulator\n",
    "\n",
    "time = 300\n",
    "\n",
    "model = Model()\n",
    "sim = Simulator(model)\n",
    "sim.setClassicDEVS()\n",
    "sim.setTerminationTime(time)\n",
    "# sim.setVerbose(None)\n",
    "sim.simulate()\n",
    "\n",
    "for k, v in model.CHAIN.summary(time).items():\n",
    "    print(k.rjust(11) + \":\", v, sep='  ')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, let's compare this to the results we obtain from _GPSS World_ :\n",
    "```\n",
    "USER CHAIN         SIZE RETRY  AVE.CONT   ENTRIES  MAX     AVE.TIME\n",
    " CHAIN               20    0     10.000       50    21       60.000\n",
    "```\n",
    "\n",
    "As you can see, we obtain the exact same values for this example. Note, `Python(P)DEVS` **includes** the termination time, hence the above values were obtained by running the `GPSS` code until the end of the time instance.\n",
    "\n",
    "The keys in the `CHAIN`'s summary correspond as follows to the _GPSS World_ results:\n",
    "\n",
    "| summary key  | _GPSS World_ key | description |\n",
    "|:---:|:---:|:---|\n",
    "| `name` | `USER CHAIN` | The name of the `CHAIN` that is summarized. |\n",
    "| `max` | `MAX` | The maximal amount of contents in the `CHAIN`. |\n",
    "| `avgc` | `AVE.CONT.` | The average content of the `CHAIN`.<br/>This is the ratio of the total queueing time to the total simulation time. |\n",
    "| `entry` | `ENTRIES` | The total amount of items that entered the `CHAIN` |\n",
    "| `transit` | `AVE.TIME` | The average transit time in the `CHAIN`. |\n",
    "| `current` | `SIZE` | The current `CHAIN` size, i.e. the amount of items there currently inside. |\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
}
