#!/usr/bin/env python
from __future__ import generators   # not needed for Python 2.3+
from SimPy.Simulation import *
from random import Random

# 2004 December gav cleaned up and simplified
# $Version: $ $Date: 2006/02/02 13:35:45 $ $Author: kgmuller $

""" Simulate the operation of a BCC cellphone system using SimPy

The objective is to determine the statistics of busy periods in the 
operation of a BCC cellphone system.

No external arguments are used; all data is set up in the script itself. The
data to be set up and appropriate values are listed:
NChannels  The number of channels used (4)
gSeed - RV seed for the call arrival generator (11111111)
JrvSeed RV seed for the call length ( 3333333)
lam - call arrival rate (calls per time-unit) (1.0)
      time unit assumed to be, say, a minute
mu  - service rate = 1/(mean servvice time) per time unit.
      This must be less than lam (0.6667)
Nhours  length of the simulation in periods ( 10)
interv - period: interval to gather statistics (60.0 time units)
gap - gap left after a period when no statistics are gathered.
      This is to reduce autocorrelation between periods (15.0) 

Output, to the system is 
1. a list of the basic inputs:
lambda    mu      s  Nhours interv  gap
2. a sequnce of Nhours pairs: 
    the mean busy time in the period
    the total number of busy times
3. a summary giving the overall:
   Busy time   mean variance
   Busy number mean variance.
"""



class Generator(Process):
 """ generates a sequence of calls """    
      
 def execute(self, maxN, lam):
    global busyEndTime
    busyEndTime = now() # simulation start time
    rv = Random(gSeed)
    for i in range(maxN):
         j = Job("Job"+str(i))
         activate(j,j.execute())
         yield hold,self,rv.expovariate(lam)
    self.trace("WARNING generator finished -- ran out of jobs")

 def trace(self,message):
     if GTRACING: print "%7.4f \t%s"%(self.time(), message)


class Job(Process):
    """ instances of the Job class represent calls arriving at
    random at the chnnels."""
 
    def execute(self):
        global busyStartTime,totalBusyVisits,totalBusyTime
        global Nfree,busyEndTime,Jrv
        self.trace("arrived ")
        if Nfree == 0: self.trace("blocked and left")
        else:
             self.trace("got a channel")
             Nfree -=  1
             if Nfree == 0:
                 self.trace("start busy period======")
                 busyStartTime = now()
                 totalBusyVisits += 1
                 interIdleTime = now() - busyEndTime
             yield hold,self,Jrv.expovariate(mu)
             self.trace("finished")
             if Nfree == 0:
                 self.trace("end   busy period++++++")
                 busyEndTime = now()
                 busy = now() - busyStartTime
                 self.trace("         busy  = %9.4f"%(busy,))
                 totalBusyTime +=busy
             Nfree += 1

    def trace(self,message):
         if TRACING: print "%7.4f \t%s %s "%(now(), message , self.name)
  

class Statistician(Process):
     """ observes the system at intervals """

     def execute(self,Nhours,interv,gap):
         global busyStartTime,totalBusyTime,totalBusyVisits
         global hourBusyTime,hourBusyVisits
         for i in range(Nhours):
             yield hold,self,gap
             totalBusyTime = 0.0
             totalBusyVisits = 0
             if Nfree == 0: busyStartTime = now()
             yield hold,self,interv
             if Nfree == 0: totalBusyTime += now()-busyStartTime
             if STRACING:  print "%7.3f %5d"%(totalBusyTime,totalBusyVisits)
             m.tally(totalBusyTime)
             bn.tally(totalBusyVisits)
         print("Busy Time:   mean = %10.5f var= %10.5f"%(m.mean(),m.var()))
         print("Busy Number: mean = %10.5f var= %10.5f"%(bn.mean(),bn.var()))


     def trace(self,message):
         if STRACING: print "%7.4f \t%s"%(time(), message)

totalBusyVisits = 0
totalBusyTime   = 0.0
NChannels =  4  # number of channels in the cell
Nfree   = NChannels
maxN    = 1000
gSeed   = 11111111
JrvSeed = 3333333
lam = 1.0
mu = 0.6667
meanLifeTime = 1.0/mu
TRACING  = 0
GTRACING = 0
STRACING = 1
Nhours  =  10

interv = 60.0    # monitor
gap    = 15.0    # monitor
print "lambda    mu      s  Nhours interv  gap"
print "%7.4f %6.4f %4d %4d    %6.2f %6.2f"%(lam,mu,NChannels,Nhours,interv,gap)

m = Monitor()
bn=Monitor()
Jrv = Random(JrvSeed)
s = Statistician('Statistician')

initialize()
g = Generator('Generator')
activate(g,g.execute(maxN, lam))
activate(s,s.execute(Nhours,interv,gap))
simulate(until=10000.0)

