# CarWashMultPar.py:  same as CarWash.py, but which runs some
# combinations of input parameter values

# usage:  

# python CarWashMultPar.py 

# 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 

#    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 *

# RPy:  import RPy
from rpy 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())

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():
   SimResults = [] 
   MaxSimtime = 100.0
   CarWash.BufSize = 1
   Car.PropMostlyClean = 0.5
   Street.ExitTime = 0.5
   for i in range(5):
      CarWash.ArrRate = 0.1 + 5*i
      for j in range(5):
         CarWash.Buf = Resource(CarWash.BufSize)
         Street.CrossRate = 1.5 + j*0.1
         initialize()  
         CWArr = CarWash()
         activate(CWArr,CWArr.Run())
         StArr = Street()
         activate(StArr,StArr.Run())
         simulate(until=MaxSimtime)
         MeanTotalWait = Car.TotalWait/Car.AllDone
         # RPy:  put all the output in one long list, rather than
         # having to deal with two-dimensional lists
         SimResults.extend([CarWash.ArrRate,Street.CrossRate,MeanTotalWait])
   # RPy:  main code is here; load wireframe(), put list SimResults in R
   # namespace, form matrix, convert to data frame, plot 3-D
   r.library('lattice')
   r.assign('simres',SimResults)
   r('sr <- matrix(simres,ncol=3,byrow=TRUE)')
   r('sr <- as.data.frame(sr)')
   r('plot(wireframe(V3 ~ V1+V2,sr))')
   while 1: continue

if __name__ == '__main__': main()
