{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## GPSS Example: Telephone Exchange\n",
    "\n",
    "Everything `GPSS` offers comes together in one big example, as provided by G. Gordon in _System Simulation, 2nd ed._ as the phone exchange.\n",
    "\n",
    "Assume we have a telephone system in which a series of calls come from a number of telephone lines and the system is to connect the calls by using one of a limited number of links. Only one call can be made to any one line at a time and it is assumed that the calls are lost if the called party is busy or no link is available.\n",
    "\n",
    "It will be assumed that the distribution of arrivals is Poisson with a mean interarrival time of 12 seconds. The length of the calls will also be assumed to have an exponential distribution. It will be assumed that each new call can come from any of the non-busy lines with equal probability, and that its destination is equally likely to be any line other than itself.\n",
    "\n",
    "In the telephone system, blocked class wait for a link to become free with the following service rules. Line 1 belongs to the company president. If there is an incoming call for line 1 and line 1 is free, the next free link goes to that call. Otherwise, the link goes to the call with the lowest origin number.\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "* Origin: System simulation, Figure 12-9/10-7\n",
    "* Author: G. Gordon\n",
    "*\n",
    "* Description: Simulation of a telephone exchange\n",
    "*\n",
    "* HV 7/5/2020: modified to match the AToM^3 visual model\n",
    "*\n",
    "* Time unit: 1 second \n",
    "*\n",
    "       SIMULATE\n",
    "\n",
    "*\n",
    "*      GPSS/H Block Section\n",
    "*\n",
    "\n",
    "*\n",
    "*      SIMULATION OF A TELEPHONE SYSTEM - MODEL 2 (president line)\n",
    "*\n",
    "POISS  FUNCTION  RN1,C24              Function for I/A interval (Poisson)\n",
    "0.0,0.0/0.1,0.104/0.2,0.222/0.3,0.355/0.4,0.509/0.5,0.69/\n",
    "0.6,0.915/0.7,1.2/0.75,1.38/0.8,1.6/0.84,1.83/0.88,2.12/\n",
    "0.9,2.3/0.92,2.52/0.94,2.81/0.95,2.99/0.96,3.2/0.97,3.5/\n",
    "0.98,3.9/0.99,4.6/0.995,5.3/0.998,6.2/0.999,7/0.9997,8\n",
    "\n",
    "*      The phone process\n",
    "\n",
    "       GENERATE  12,FN$POISS,,,,2PH   Arrival of calls, characterized by two halfword parameters, \n",
    "*                                     one for ORIGin and one for DESTination\n",
    "       TEST G    V$FREEL,2,ABND       Test whether system is full (all lines in use)\n",
    "ASN1   ASSIGN    ORIG,V$LN,PH         Pick an ORIGin LiNe, store in parameter ORIG\n",
    "       GATE LR   PH$ORIG,ASN1         Check whether ORIGin line is busy; if so, try to pick another line\n",
    "ASN2   ASSIGN    DEST,V$LN,PH         Pick a DESTination LiNe, store in parameter DEST\n",
    "       TEST NE   PH$ORIG,PH$DEST,ASN2 Retry if DESTination == ORIGin\n",
    "       LOGIC S   PH$ORIG              Set ORIGin line busy\n",
    "       TRANSFER  BOTH,,BLKD           If no link available, blocked in WAIT chain \n",
    "GETL   ENTER     LNKS                 Get a link\n",
    "       GATE LR   PH$DEST,BUSY         Check whether DESTination line is busy\n",
    "       LOGIC S   PH$DEST              Set DESTination line busy\n",
    "       ADVANCE   120,FN$POISS         Talk for a while, talktime mean 12min\n",
    "       LOGIC R   PH$ORIG              ORIGin hangs up\n",
    "       LOGIC R   PH$DEST              DESTination hangs up\n",
    "       LEAVE     LNKS                 Free up the link\n",
    "CKCH   TEST G    CH$WAIT,0,TERM       Test whether calls are waiting\n",
    "       GATE LR   1,GETF               Check whether line 1 (president) is free\n",
    "       UNLINK    WAIT,GETL,1,DEST$PH,1,GETF\n",
    "*                                     If a call to 1 (president) is waiting, get it out of the WAIT chain \n",
    "*                                     and let it get a link, else connect (in the other UNLINK block) the\n",
    "*                                     first waiting call (ordered by call ORIGin)\n",
    "TERM   TERMINATE                      Normal end of a succesful call \n",
    "GETF   UNLINK    WAIT,GETL,1          Connect the first waiting call (ordered by call ORIGin)\n",
    "       TRANSFER  ,TERM\n",
    "\n",
    "ABND   TERMINATE                      Abandon call\n",
    "\n",
    "BLKD   LINK      WAIT,ORIG$PH         Wait in order of call ORIGin (lower number first)\n",
    "\n",
    "BUSY   LOGIC R   PH$ORIG              Caller hangs up\n",
    "       LEAVE     LNKS                 Free up the link\n",
    "       TRANSFER  ,CKCH                Go to test for waiting calls\n",
    "\n",
    "LNKS   STORAGE   10                   Number of links\n",
    "\n",
    "LN     VARIABLE  XH$NLINES*RN1/1000+1        Pick a random LiNe \n",
    "FREEL  VARIABLE  XH$NLINES-2*S$LNKS-CH$WAIT  Number of free lines\n",
    "\n",
    "\n",
    "*      The clock process\n",
    "\n",
    "       GENERATE  60            One clock tick every minute \n",
    "       TERMINATE 1\n",
    "\n",
    "*\n",
    "*      GPSS/H Control Statements\n",
    "*\n",
    "       INITIAL   XH$NLINES,50  Set the total number of lines\n",
    "       START     10,NP         Warm up run of 10 minutes, don't print\n",
    "       RESET                   Wipe out statistics\n",
    "       START     600           Main run (10 hours)\n",
    "\n",
    "       END\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pypdevsbbl.domain.gpss import GPSS2DEVS, SNA, Function\n",
    "\n",
    "class Model(GPSS2DEVS):        \n",
    "    def __init__(self):\n",
    "        super().__init__(\"TelephoneExchange\", 2)\n",
    "        self.createStorage(\"LNKS\", 10)\n",
    "        self.createChain(\"WAIT\")\n",
    "        \n",
    "        self.functions = {\n",
    "            \"FN$POISS\": Function(\"RN1\", Function.parse(\n",
    "                \"0.0,0.0/0.1,0.104/0.2,0.222/0.3,0.355/0.4,0.509/0.5,0.69/\" \\\n",
    "                \"0.6,0.915/0.7,1.2/0.75,1.38/0.8,1.6/0.84,1.83/0.88,2.12/\" \\\n",
    "                \"0.9,2.3/0.92,2.52/0.94,2.81/0.95,2.99/0.96,3.2/0.97,3.5/\" \\\n",
    "                \"0.98,3.9/0.99,4.6/0.995,5.3/0.998,6.2/0.999,7/0.9997,8\", 24), True)\n",
    "        }\n",
    "        \n",
    "        self.savevalues = {\n",
    "            \"XH$NOLINES\": 50\n",
    "        }\n",
    "        \n",
    "        self.variables = {\n",
    "            \"V$LINE\": lambda: SNA(\"XH$NOLINES\", self) * SNA(\"RN1\", self) // 1000 + 1,\n",
    "            \"V$FREELN\": lambda: SNA(\"XH$NOLINES\", self) - 2 * SNA(\"S$LNKS\", self) - SNA(\"CH$WAIT\", self)\n",
    "        }\n",
    "        \n",
    "        # The phone process\n",
    "        self.GENERATE(\"L0\", dist=lambda x: 12 * SNA(\"FN$POISS\", self))\n",
    "        self.TEST(\"L1\", \"V$FREELN\", \"G\", \"2\")\n",
    "        self.ASSIGN(\"ASN1\", \"PH$ORIG\", lambda p: SNA(\"V$LINE\", self))\n",
    "        self.GATE(\"L3\", \"PH$ORIG\", \"ASN1\", \"R\")\n",
    "        self.ASSIGN(\"ASN2\", \"PH$DEST\", lambda p: SNA(\"V$LINE\", self))\n",
    "        self.TEST(\"L5\", \"PH$ORIG\", \"NE\", \"PH$DEST\")\n",
    "        self.LOGIC(\"L6\", \"S\", \"PH$ORIG\")\n",
    "        self.ENTER(\"GETL\", \"LNKS\")\n",
    "        self.GATE(\"L8\", \"PH$DEST\", \"BUSY\", \"R\")\n",
    "        self.LOGIC(\"L9\", \"S\", \"PH$DEST\")\n",
    "        self.ADVANCE(\"L10\", dist=lambda x:  120 * SNA(\"FN$POISS\", self), prev=\"L9\")\n",
    "        self.LOGIC(\"L11\", \"R\", \"PH$ORIG\")\n",
    "        self.LOGIC(\"L12\", \"R\", \"PH$DEST\")\n",
    "        self.LEAVE(\"L13\", \"LNKS\")\n",
    "        self.TRANSFER_BOTH(\"L7\", \"GETL\", \"LNKS\", self.GPSS2DEVS_5_L13.storage)\n",
    "        self.TEST(\"CKCH\", \"CH$WAIT\", \"G\", \"0\")\n",
    "        self.GATE(\"L15\", \"1\", \"GETF\", \"R\")\n",
    "        self.UNLINK(\"L16\", \"WAIT\", 1, \"PH$DEST\", \"1\", \"GETF\")\n",
    "        self.TERMINATE(\"TERM\")\n",
    "        self.UNLINK(\"GETF\", \"WAIT\", 1)\n",
    "        \n",
    "        self.TERMINATE(\"ABND\")\n",
    "        \n",
    "        self.LINK(\"BLKD\", \"WAIT\", \"L7\", \"PH$ORIG\")\n",
    "        \n",
    "        self.LOGIC(\"BUSY\", \"R\", \"PH$ORIG\")\n",
    "        self.LEAVE(\"L21\", \"LNKS\")\n",
    "        \n",
    "        # The clock process\n",
    "        self.GENERATE(\"L26\", dist=lambda x: 60)\n",
    "        self.TERMINATE(\"L27\", \"1\")\n",
    "        \n",
    "        \n",
    "        # CONNECTIONS\n",
    "        \n",
    "        self.connect(\"L0\", \"L1\")\n",
    "        self.connect(\"L1_Y\", \"ASN1\")\n",
    "        self.connect(\"L1_N\", \"ABND\")\n",
    "        self.connect(\"ASN1\", \"L3\")\n",
    "        self.connect(\"L3\", \"ASN2\")\n",
    "        self.connect(\"L3_B\", \"ASN1\")\n",
    "        self.connect(\"ASN2\", \"L5\")\n",
    "        self.connect(\"L5_Y\", \"L6\")\n",
    "        self.connect(\"L5_N\", \"ASN2\")\n",
    "        self.connect(\"L6\", \"L7\")\n",
    "        self.connect(\"L7\", \"BLKD\")\n",
    "        \n",
    "        self.connect(\"GETL\", \"L8\")\n",
    "        self.connect(\"L8\", \"L9\")\n",
    "        self.connect(\"L8_B\", \"BUSY\")\n",
    "        self.connect(\"L9\", \"L10\", True)\n",
    "        self.connect(\"L10\", \"L11\")\n",
    "\n",
    "        self.connect(\"L11\", \"L12\")\n",
    "        self.connect(\"L12\", \"L13\")\n",
    "        self.connect(\"L13\", \"CKCH\")\n",
    "        self.connect(\"CKCH_Y\", \"L15\")\n",
    "        self.connect(\"CKCH_N\", \"TERM\")\n",
    "        self.connect(\"L15\", \"L16\", True)\n",
    "        self.connect(\"L15_B\", \"GETF\", True)\n",
    "        self.connect(\"L16\", \"TERM\")\n",
    "        self.connect(\"L16_B\", \"GETL\")\n",
    "        self.connect(\"L16_F\", \"GETF\", True)\n",
    "        self.connect(\"GETF\", \"TERM\")\n",
    "        self.connect(\"GETF_B\", \"GETL\")\n",
    "        self.connect(\"BUSY\", \"L21\")\n",
    "        self.connect(\"L21\", \"CKCH\")\n",
    "        \n",
    "        self.connect(\"L26\", \"L27\")\n",
    "        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "STORAGES:\n",
      "       name:  LNKS\n",
      "   capacity:  10\n",
      "  remaining:  3\n",
      "      entry:  3037\n",
      "        max:  10\n",
      "       util:  0.7078792897357435\n",
      "       avgc:  7.078792897357435\n",
      "\n",
      "USER CHAINS:\n",
      "       name:  WAIT\n",
      "        max:  10\n",
      "       avgc:  0.40447562368741724\n",
      "      entry:  615\n",
      "    transit:  23.57164975609763\n",
      "    current:  0\n"
     ]
    }
   ],
   "source": [
    "from pypdevs.simulator import Simulator\n",
    "from pypdevsbbl.extra import toDot\n",
    "\n",
    "model = Model()\n",
    "sim = Simulator(model)\n",
    "sim.setClassicDEVS()\n",
    "\n",
    "sim.setTerminationCondition(lambda _, m: m.controller().finished(10))\n",
    "# sim.setVerbose(None)\n",
    "sim.simulate()\n",
    "\n",
    "# RESET\n",
    "model.storage(\"LNKS\").clear()\n",
    "model.chain(\"WAIT\").clear()\n",
    "model.controller().reset()\n",
    "\n",
    "sim.setTerminationCondition(lambda _, m: m.controller().finished(600))\n",
    "sim.simulate()\n",
    "\n",
    "print(\"STORAGES:\")\n",
    "for k, v in model.storage(\"LNKS\").summary().items():\n",
    "    print(k.rjust(11) + \": \", v)\n",
    "    \n",
    "print(\"\\nUSER CHAINS:\")\n",
    "for k, v in model.chain(\"WAIT\").summary().items():\n",
    "    print(k.rjust(11) + \": \", v)"
   ]
  }
 ],
 "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"
  },
  "pycharm": {
   "stem_cell": {
    "cell_type": "raw",
    "metadata": {
     "collapsed": false
    },
    "source": []
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
