#!/usr/bin/env python

# MdMd1.py

# Example of SimPy for discrete-time situations.

# "M/M/1 queue," but in discrete time.  Time is slotted.  At each time
# slot, if the server is busy at the beginning of a time slot, it
# finishes that job at the end of that slot with probability p.  At the
# start of each slot, a new arrival occurs with probability q.   A job
# which arrives in a given slot and finds an idle server will start
# service right away.

# The server is modeled as by the Server process, and the arrivals are
# modeled by the Arrivals process.  We do not use a Resource here, just
# modeling the queue with a list Queue.  Alternatively, we could define
# a Job process, with one created with each arrival. 

# Note:  The service and interarrival times, under the assumptions given
# above (and the implied assumption of independence), are geometrically
# distributed.  We could write the code below that way as an
# alternative.

from __future__ import generators  # delete if use Python >= 2.3 
from SimPy.Simulation import *
from random import Random,expovariate,uniform

import sys

class Server(Process):
   def __init__(self):
      Process.__init__(self)  
   def Run(self):
      self.Queue = []  # queue starts out empty
      self.NDone = 0  # number of jobs completed
      self.TotWait = 0  # total of all wait times so far
      while 1:
         self.Busy = 0  # 1 for busy, 0 for idle
         # if no jobs, sleep until one arrives
         if len(self.Queue) == 0:
            yield passivate,self
         # serve job at head of queue
         self.Busy = 1
         while 1:
            yield hold,self,1.0
            if Globs.Rnd.random() < Globs.P:
               break
         # job done, do bookkeeping 
         self.NDone += 1
         self.TotWait += now() - self.Queue[0]
         # delete now-useless object
         del self.Queue[0]

class Arrivals(Process):
   def __init__(self):
      Process.__init__(self) 
   def Run(self):
      while 1:
         if Globs.Rnd.random() < Globs.Q:
            # new arrival 
            # add job to queue, in form of its arrival time
            Globs.Srvr.Queue.append(now())
            # wake server if it's idle
            if Globs.Srvr.Busy == 0:
               reactivate(Globs.Srvr)
         # go to next time slot
         yield hold,self,1.0

class Globs:
   Rnd = Random(12345)
   P = float(sys.argv[1])
   Q = float(sys.argv[2])
   Srvr = Server()
   Arrs = Arrivals()

# "main" program starts here

def main():
   initialize()  
   activate(Globs.Srvr,Globs.Srvr.Run(),delay=0.0)
   activate(Globs.Arrs,Globs.Arrs.Run(),delay=0.0)
   MaxSimtime = 10000.0
   simulate(until=MaxSimtime)
   print "mean residence time", Globs.Srvr.TotWait/Globs.Srvr.NDone

if __name__ == '__main__':
   main()
