#lang scribble/doc
@(require scribble/manual
          scribblings/icons
          (for-label scheme/base
                     plot/plot
                     "../simulation-with-graphics.ss"))

@title[#:tag "simpy"]{SimPy and Simulation Collection Example Comparisons}

@local-table-of-contents[]

This chapter had some example from the SimPy web site and equivalent examples in the simulation collection.

@section{Cellphone}

@subsection{Simpy Example---Cellphone}

@verbatim{
#!/usr/bin/env python
""" Simulate the operation of a BCC cellphone system using SimPy

"""
from __future__ import generators   # not needed for Python 2.3+
from SimPy.Simulation import *
from random import Random,expovariate

class Generator(Process):
 """ generates a sequence of calls """
 def __init__(self, maxN, lam):
     global busyEndTime
     Process.__init__(self)
     self.name = "generator"
     self.maxN = maxN
     self.lam = lam
     self.iatime = 1.0/lam
     self.rv = Random(gSeed)
     busyEndTime = now() # simulation start time

 def execute(self):
     for i in range(self.maxN):
         j = Job(i)
         activate(j,j.execute())
         yield hold,self,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 __init__(self,i):
        Process.__init__(self)
        self.i = i
        self.name = "Job"+`i`

    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 __init__(self,Nhours,interv,gap):
         Process.__init__(self)
         self.Nhours = Nhours
         self.interv = interv
         self.gap = gap
         self.name="Statistician"

     def execute(self):
         global busyStartTime,totalBusyTime,totalBusyVisits
         global hourBusyTime,hourBusyVisits
         for i in range(self.Nhours):
             yield hold,self,self.gap
             totalBusyTime = 0.0
             totalBusyVisits = 0
             if Nfree == 0: busyStartTime = now()
             yield hold,self,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"%(self.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(Nhours,interv,gap)
initialize()
g = Generator(maxN, lam)
activate(g,g.execute())
activate(s,s.execute())
simulate(until=10000.0)}

The output from the program follows.

@verbatim{
lambda    mu      s  Nhours interv  gap
 1.0000 0.6667    4   10     60.00  15.00
  3.274     8
  0.444     2
  3.649     8
  0.652     3
  2.277     6
  1.372     9
  0.411     4
  0.067     2
  2.910     8
  5.073     9
Busy Time:   mean =    2.01297 var=    2.55814
Busy Number: mean =    5.90000 var=    7.49000}

@subsection{Simulation Collection Example---Cellphone}

@schememod[
scheme/base
(code:comment " Cellphone simulation from PySim")

(require (planet williams/simulation/simulation))
(require (planet williams/science/random-distributions))

(code:comment " Parameters")

(define Nchannels 4)
(define maxN 1000)
(define lam 1.0)
(define mu 0.6667)
(define meanLifeTime (/ 1.0 mu))
(define Nhours 10)
(define interv 60.0)
(define gap 15.0)

(code:comment " Globals")

(define Nfree Nchannels)
(define totalBusyVisits 0)
(define totalBusyTime 0.0)
(define busyStartTime 0.0)
(define busyEndTime 0.0)


(code:comment " Statistics")

(define m #f)
(define bn #f)

(define-process (generator n)
  (do ((i 0 (+ i 1)))
      ((= i n) (void))
    (schedule now (job i))
    (wait (random-exponential (/ 1.0 lam)))))

(define-process (job i)
  (when (> Nfree 0)
    (set! Nfree (- Nfree 1))
    (when (= Nfree 0)
      (code:comment " Start busy period")
      (set! busyStartTime (current-simulation-time))
      (set! totalBusyVisits (+ totalBusyVisits 1)))
    (work (random-exponential (/ 1.0 mu)))
    (when (= Nfree 0)
      (code:comment " End busy period")
      (set! busyEndTime (current-simulation-time))
      (set! totalBusyTime (+ totalBusyTime
                             (- (current-simulation-time)
                                busyStartTime))))
    (set! Nfree (+ Nfree 1))))

(define-process (statistician)
  (do ((i 0 (+ i 1)))
      ((= i Nhours) (void))
    (code:comment " wait the specified gap time")
    (wait gap)
    (code:comment " initialize")
    (set! totalBusyTime 0.0)
    (set! totalBusyVisits 0)
    (when (= Nfree 0)
      (set! busyStartTime (current-simulation-time)))
    (code:comment " wait the specified interval time")
    (wait interv)
    (code:comment " trace busy time and busy visits")
    (when (= Nfree 0)
      (set! totalBusyTime (+ totalBusyTime
                             (- (current-simulation-time)
                                busyStartTime))))
    (printf "~a: busy time = ~a; busy visits = ~a~n"
            (current-simulation-time) totalBusyTime totalBusyVisits)
    (code:comment " Tally statistics")
    (set-variable-value! m totalBusyTime)
    (set-variable-value! bn totalBusyVisits))
  (code:comment " Print final statistics")
  (printf "Busy time:   mean = ~a, variance = ~a~n"
          (variable-mean m) (variable-variance m))
  (printf "Busy number: mean = ~a, variance = ~a~n"
          (variable-mean bn) (variable-variance bn)))

(define (run-simulation)
  (with-new-simulation-environment
   (code:comment " Print parameters")
   (printf "lambda = ~a~n" lam)
   (printf "mu     = ~a~n" mu)
   (printf "s      = ~a~n" Nchannels)
   (printf "Nhours = ~a~n" Nhours)
   (printf "interv = ~a~n" interv)
   (printf "gap    = ~a~n" gap)
   (code:comment " Initialize global variables")
   (set! Nfree Nchannels)
   (set! totalBusyVisits 0)
   (set! totalBusyTime 0.0)
   (set! busyEndTime 0.0)
   (code:comment " Create statistics")
   (set! m (make-variable))
   (tally (variable-statistics m))
   (set! bn (make-variable))
   (tally (variable-statistics bn))
   (code:comment " Create initial processes and start the execution")
   (schedule (at 0.0) (generator maxN))
   (schedule (at 0.0) (statistician))
   (schedule (at 10000.0) (stop-simulation))
   (start-simulation)))

(run-simulation)]

The output from the program follows.

@verbatim{
lambda = 1.0
mu     = 0.6667
s      = 4
Nhours = 10
interv = 60.0
gap    = 15.0
75.0: busy time = 3.19230597325965; busy visits = 11
150.0: busy time = 2.4780498393357675; busy visits = 7
225.0: busy time = 3.5391222097609045; busy visits = 12
300.0: busy time = 2.1031429717027947; busy visits = 6
375.0: busy time = 1.3915494625956057; busy visits = 3
450.0: busy time = 2.9838869629721785; busy visits = 6
525.0: busy time = 3.8242483424334637; busy visits = 8
600.0: busy time = 2.7636093265058435; busy visits = 9
675.0: busy time = 1.73398034270042; busy visits = 8
750.0: busy time = 1.957177682358406; busy visits = 9
Busy time:   mean = 2.596707311362503, variance = 0.5790891717346138
Busy number: mean = 7.9, variance = 6.089999999999996}

@section{Jackson}

@subsection{Simpy Example---Jackson}

@verbatim{
#!/usr/bin/env python
"""
  Simulation of a jackson queue network

"""
from __future__ import generators  # not needed in Python 2.3+
from SimPy.Simulation import *
from random import Random,expovariate,uniform

def sum(P):
    """ Sum of the real vector P """
    sumP = 0.0
    for i in range(len(P)): sumP += P[i]
    return (sumP)



def choose2dA(i,P,g):
    """  return a random choice from a set j = 0..n-1
       with probs held in list of lists P[j] (n by n)
       using row i
       g = random variable
       call:  next = choose2d(i,P,g)
    """
    U = g.random()
    sumP = 0.0
    for j in range(len(P[i])):  # j = 0..n-1
        sumP +=  P[i][j]
        if U < sumP: break
    return(j)

## -----------------------------------------------
class Msg(Process):
    """a message"""
    def __init__(self,i,node):
        Process.__init__(self)
        self.i = i
        self.node = node

    def execute(self):
        """ executing a message """
        global noInSystem
        startTime = now()
        noInSystem += 1
        ##print "DEBUG noInSystm = ",noInSystem
        NoInSystem.accum(noInSystem)
        self.trace("Arrived node  %d"%(self.node,))
        while self.node <> 3:
            yield request,self,processor[self.node]
            self.trace("Got processor %d"%(self.node,))
            st = Mrv.expovariate(1.0/mean[self.node])
            Stime.observe(st)
            yield hold,self,st
            yield release,self,processor[self.node]
            self.trace("Finished with %d"%(self.node,))
            self.node = choose2dA(self.node,P,Mrv)
            self.trace("Transfer to   %d"%(self.node,))
        TimeInSystem.observe(now()-startTime)
        self.trace(    "leaving       %d"%(self.node,),noInSystem)
        noInSystem -= 1
        NoInSystem.accum(noInSystem)

    def trace(self,message,nn=0):
        if MTRACING: print "%7.4f %3d %10s %3d"%(now(),self.i, message,nn)


## -----------------------------------------------
class Generator(Process):
 """ generates a sequence of msgs """
 def __init__(self, rate,maxT,maxN):
     Process.__init__(self)
     self.name = "Generator"
     self.rate = rate
     self.maxN = maxN
     self.maxT = maxT
     self.g = Random(11335577)
     self.i = 0

 def execute(self):
     while (now() < self.maxT)  & (self.i < self.maxN):
         self.i+=1
         p = Msg(self.i,startNode)
         activate(p,p.execute())
         ## self.trace("starting "+p.name)
         yield hold,self,self.g.expovariate(self.rate)
     self.trace("generator finished with "+`self.i`+" ========")
     self.stopSim()

 def trace(self,message):
     if GTRACING: print "%7.4f \t%s"%(now(), message)

## -----------------------------------------------
# generator:
GTRACING = 0
# messages:
rate = 0.5
noInSystem = 0
MTRACING = 0
startNode = 0
maxNumber = 10000
maxTime = 100000.0
Mrv = Random(77777)
TimeInSystem=Monitor()
NoInSystem= Monitor()
Stime = Monitor()

processor = [Resource(1),Resource(1),Resource(1)]
mean=[1.0, 2.0, 1.0]
P = [[0, 0.5, 0.5, 0],[0,0,0.8,0.2], [0.2,0,0,0.8]]

initialize()
g = Generator(rate,maxTime,maxNumber)
activate(g,g.execute())
simulate(until=100.0)

print "Mean numberm= %8.4f"%(NoInSystem.timeAverage(),)
print "Mean delay  = %8.4f"%(TimeInSystem.mean(),)
print "Mean stime  = %8.4f"%(Stime.mean(),)
print "Total jobs  = %8d"%(TimeInSystem.count(),)
print "Total time  = %8.4f"%(now(),)
print "Mean rate   = %8.4f"%(TimeInSystem.count()/now(),)}

The following is the output from the program.

@verbatim{
Mean numberm=   1.5412
Mean delay  =   3.9369
Mean stime  =   1.0332
Total jobs  =       38
Total time  = 100.0000
Mean rate   =   0.3800}

@subsection{Simulation Collection Example---Jackson}

@schememod[
scheme/base
;;; Jackson Network from PySim

(require (planet williams/simulation/simulation-with-graphics))
(require (planet williams/science/random-distributions))

;;; Parameters

(define n 3)
(define m #(1.0 2.0 1.0))
(define p (vector
           (make-discrete #(0.0 0.5 0.5 0.0))
           (make-discrete #(0.0 0.0 0.8 0.2))
           (make-discrete #(0.2 0.0 0.0 0.8))))
(define np '(1 1 1))
(define mu (/ 1.0 1.5))
(define max-messages 10000)

;;; Global Variables (set in main)

(define n-arrivals 0)
(define n-in-system #f)
(define time-in-system #f)
(define processor #f)

;;; Data Collection

(define-process (message i)
  (let ((arrive-time (current-simulation-time))
        (work-duration 0.0))
    (set-variable-value!
     n-in-system (+ (variable-value n-in-system) 1))
    (let loop ((node 0))               ; start node is always 0
      (when (< node n)
        (with-resource ((vector-ref processor node))
          (work (random-exponential (vector-ref m node))))
        (loop (random-discrete (vector-ref p node)))))
    (set-variable-value!
     n-in-system (- (variable-value n-in-system) 1))
    (set-variable-value!
     time-in-system (- (current-simulation-time)
                       arrive-time))))

(define (generator)
  (do ((i 0 (+ i 1)))
      ((= i max-messages) (void))
    (set! n-arrivals (+ n-arrivals 1))
    (schedule now (message i))
    (wait (random-exponential (/ mu)))))

(define (main)
  (with-new-simulation-environment
    ;; Initialize global variables
    (set! n-arrivals 0)
    (set! n-in-system (make-variable 0))
    (accumulate (variable-history n-in-system))
    (set! time-in-system (make-variable))
    (tally (variable-statistics time-in-system))
    (set! processor (list->vector
                     (map make-resource np)))
    ;;
    (schedule (at 0.0) (generator))
    (schedule (at 5000.0) (stop-simulation))
    (start-simulation)
    (printf "Mean number in system = ~a~n" (variable-mean n-in-system))
    (printf "Mean delay in system  = ~a~n" (variable-mean time-in-system))
    (printf "Total time run        = ~a~n" (current-simulation-time))
    (printf "Total jobs arrived    = ~a~n" n-arrivals)
    (printf "Total jobs completed  = ~a~n" (variable-n time-in-system))
    (printf "Average arrival rate  = ~a~n" (/ n-arrivals
                                              (current-simulation-time)))
    (write-special (history-plot (variable-history n-in-system)))
    (newline)))

(main)]

The following is the output from the program.

@verbatim{
Mean number in system = 14.726063392545544
Mean delay in system  = 21.56635336305308
Total time run        = 5000.0
Total jobs arrived    = 3419
Total jobs completed  = 3413
Average arrival rate  = 0.6838}

@image["scribblings/images/jackson.png"]

