# simulates a carwash; cars queue up at the entrance; each car chooses
# one of two grades of wash, MostlyClean (1.0 unit of time) and Gleaming
# (2.0 units of time); there is only one bay in the carwash, so only one
# is served at a time; at the exit there is a buffer space where cars
# wait to go out onto the street; cross traffic does not stop, so a car
# must wait for a large enough gap in the traffic in order to move out
# onto the street 

# usage:  

# python CarWash.py ArrRate PropMostlyClean BufSize CrossRate ExitTime MaxSimTime

# where:

#    ArrRate = rate of arrivals of calls to carwash (reciprocal of mean 
#              time between arrivals)
#    PropMostlyClean = proportion of cars that opt for the MostlyClean wash
#    BufSize = number of cars that can fit in the exit buffer
#    CrossRate = rate of arrivals of cars on the street passing the carwash
#    ExitTime = time needed for one car to get out onto the street
#    MaxSimtime = amount of time to simulate

# basic strategy of the simulation:  model the carwash itself as a
# Resource, and do the same for the buffer and the front spot in the
# buffer; when a car acquires the latter, it watches for a gap big
# enough to enter the street

import sys,random

from SimPy.Simulation import *
from PeriodicSampler import *

class Globals:
   Rnd = random.Random(12345)
   Debug = False

class Street(Process):
   CrossRate = None  
   ExitTime = None
   NextArrival = None  # time of next street arrival 
   CrossArrive = SimEvent()
   def Run(self):
      while 1:
         TimeToNextArrival = Globals.Rnd.expovariate(Street.CrossRate)
         Street.NextArrival = now() + TimeToNextArrival
         Street.CrossArrive.signal()  # tells car at front of buffer to 
                                      # check new TimeToNextArrival
         yield hold,self,TimeToNextArrival
         if Globals.Debug: 
            print 
            print 'time',now()
            print 'street arrival'

class Car(Process):
   NextID = 0  # for debugging
   PropMostlyClean = None
   CurrentCars = []  # for debugging and code verification
   TotalWait = 0.0  # total wait times of all cars, from arrival to
                    # carwash to exit onto the street
   TotalBufTime = 0.0  # total time in buffer for all cars
   NStuckInBay = 0  # number of cars stuck in bay when wash done, due to
                    # full buffer
   AllDone = 0  # number of cars that have gotten onto the street
   def __init__(self):
      Process.__init__(self)
      self.ID = Car.NextID
      Car.NextID += 1
      self.ArrTime = None  # time this car arrived at carwash
      self.WashDoneTime = None  # time this car will finish its wash
      self.LeaveTime = None  # time this car will exit
      self.StartBufTime = None  # start of period in buffer
   def Run(self):  # simulates one call
      self.State = 'waiting for bay'
      Car.CurrentCars.append(self)
      self.ArrTime = now()
      if Globals.Debug: ShowStatus('carwash arrival')
      yield request,self,CarWash.Bay
      self.State = 'in bay'
      if Globals.Rnd.uniform(0,1) < Car.PropMostlyClean: WashTime = 1.0
      else: WashTime = 2.0
      self.WashDoneTime = now() + WashTime
      if Globals.Debug: ShowStatus('start wash')
      yield hold,self,WashTime
      self.State = 'waiting for buffer'
      self.WashDoneTime = None
      if Globals.Debug: ShowStatus('wash done')
      if CarWash.Buf.n == 0: Car.NStuckInBay += 1
      yield request,self,CarWash.Buf
      self.StartBufTime = now()
      yield release,self,CarWash.Bay
      self.State = 'in buffer'
      if Globals.Debug: ShowStatus('got into buffer')
      yield request,self,CarWash.BufFront 
      # OK, now wait to get out onto the street; every time a new car
      # arrives in cross traffic, it will signal us to check the new
      # next arrival time
      while True:
         PossibleLeaveTime = now() + Street.ExitTime
         if Street.NextArrival >= PossibleLeaveTime:
            self.State = 'on the way out'
            self.LeaveTime = PossibleLeaveTime
            if Globals.Debug: ShowStatus('leaving')
            yield hold,self,Street.ExitTime
            Car.CurrentCars.remove(self)
            self.LeaveTime = None
            Car.TotalWait += now() - self.ArrTime
            Car.TotalBufTime += now() - self.StartBufTime
            if Globals.Debug: ShowStatus('gone')
            Car.AllDone += 1
            yield release,self,CarWash.BufFront
            yield release,self,CarWash.Buf
            return
         yield waitevent,self,Street.CrossArrive

class CarWash(Process):
   ArrRate = None
   BufSize = None
   Buf = None  # will be Resource(BufSize) instance representing buffer
   BufFront = Resource(1)  # front buffer slot, by the street
   NextArrival = None # time of next carwash arrival (for debugging/code
                      # verification)
   Bay = Resource(1)  # the carwash
   def __init__(self):
      Process.__init__(self)
   def Run(self): # arrivals
      while 1:
         TimeToNextArrival = Globals.Rnd.expovariate(CarWash.ArrRate)
         CarWash.NextArrival = now() + TimeToNextArrival
         yield hold,self,TimeToNextArrival
         C = Car()
         activate(C,C.Run())

class BufMonClass(Process):  # to enable use of PeriodicSampler
   def __init__(self):
      Process.__init__(self)
      self.BufMon = Monitor()
   def RecordNInBuf(self):
      return CarWash.BufSize - CarWash.Buf.n

def ShowStatus(msg):  # for debugging and code verification
   print
   print 'time', now()
   print msg
   print 'current cars:'
   for C in Car.CurrentCars:
      print '  ',C.ID,C.State,
      if C.WashDoneTime != None: 
         print 'wash will be done at',C.WashDoneTime
      elif C.LeaveTime != None:
         print 'gone at',C.LeaveTime
      else: print
   print 'next carwash arrival at',CarWash.NextArrival
   print 'next street arrival at',Street.NextArrival

def main():
   if 'debug' in sys.argv: Globals.Debug = True
   CarWash.ArrRate = float(sys.argv[1])
   Car.PropMostlyClean = float(sys.argv[2])
   CarWash.BufSize = int(sys.argv[3])
   CarWash.Buf = Resource(CarWash.BufSize)
   Street.CrossRate = float(sys.argv[4])
   Street.ExitTime = float(sys.argv[5])
   initialize()  
   CWArr = CarWash()
   activate(CWArr,CWArr.Run())
   StArr = Street()
   activate(StArr,StArr.Run())
   MaxSimtime = float(sys.argv[6])
   BMC = BufMonClass()
   BMC.PrSmp = PerSmp(0.1,BMC.BufMon,BMC.RecordNInBuf)
   activate(BMC.PrSmp,BMC.PrSmp.Run())
   simulate(until=MaxSimtime)
   print 'number of cars getting onto the street',Car.AllDone
   print 'mean total wait:',Car.TotalWait/Car.AllDone
   MeanWaitInBuffer = Car.TotalBufTime/Car.AllDone
   print 'mean wait in buffer:',MeanWaitInBuffer
   print 'proportion of cars blocked from exiting bay:', \
      float(Car.NStuckInBay)/Car.AllDone
   print "mean number of cars in buffer, using Little's Rule:", \
      MeanWaitInBuffer * CarWash.ArrRate  
   print 'mean number of cars in buffer, using alternate method:', \
      BMC.BufMon.mean()

if __name__ == '__main__': main()
