# SimPy Manual

Authors: | Tony Vignaux <Vignaux@users.sourceforge.net> Klaus Muller <Muller@users.sourceforge.net>
SimPy version: | 1.4
Web-site: | http://simpy.sourceforge.net/

Contents

- SimPy Manual
  - A Note for users of SimPy Version 1.3
  - Introduction
  - Simulation with SimPy
  - Processes
    - Defining a process
    - Elapsing time in a Process
    - Starting and stopping SimPy Processes
    - Asynchronous interruptions
    - Starting the simulation
    - A complete SimPy script
    - A source fragment
  - Resources
    - Requesting resources with priority
    - Requesting a resource with preemptive priority
    - Monitoring a resource
  - Random Number Generation
  - Monitors
  - Other Links
  - Acknowledgments
  - Appendices
    - A1. SimPy Error Messages
    - A2. SimPy Process States
    - A3. SimPlot, The Simpy plotting utility
    - A4. SimGUI, The Simpy Graphical User Interface
    - A5. SimulationTrace, the SimPy tracing utility
    - A6. SimulationStep, the SimPy event stepping utility
    - A7. SimulationRT, a real-time synchronizing utility

SimPy is an efficient, process-based, open-source simulation language using Python as a base. The facilities it offers are Processes, Resources, and Monitors.

This describes version 1.4 of SimPy.

## A Note for users of SimPy Version 1.3

There have been changes to Monitor. It is now incorporated into Simulation.py. All old code should work but some old methods, such as tally and accum are now deprecated and replaced by observe(y,t). Where we have used x as the observed value, now we use y for compatibility with the plotting routines.

The Manual Appendix includes descriptions of a number of new SimPy varieties, for plotting, interaction using a GUI, tracing, event-stepping, and a real-time version.

## Introduction

SimPy is a Python-based discrete-event simulation system. It uses parallel processes to model active components such as messages, customers, trucks, planes.

SimPy provides a number of facilities for the simulation programmer. They include Processes, Resources, and, importantly, ways of recording the histories of chosen variables in Monitors.

Processes are the basic component of a SimPy simulation script. A Process models an active component (for example, a Truck, a Customer, or a Message) which may have to queue for scarce Resources, to work for fixed or random times, and to interact with other components.

A SimPy script consists of the declaration of one or more Process classes and the instantiation of process objects from them. Each such process describes how the object behaves, elapses time, uses logic, and waits for Resources. In addition, Resources and Monitors may be defined and used.

Before attempting to use SimPy, you should know how to write Python code. In particular, you should be able to use and define classes of objects. Python is free and available on most machine types. We do not introduce it here. You can find out more about it and download it from the Python web-site, http://www.Python.org

This document assumes Python 2.2 or later. NOTE that if Python 2.2 is used, the following must be placed at the top of all SimPy scripts: from __future__ import generators

## Simulation with SimPy

All discrete-event simulation programs automatically maintain the current simulation time in a software clock. In SimPy this can be accessed using the now() function. This is used in controlling the simulation and in producing printed traces of its operation.

While a simulation program runs, time steps forward from one event to the next. An event occurs whenever the state of the simulated system changes. For example, an arrival of a customer is an event. So is a departure.

To use the event scheduling mechanism of SimPy we must import the Simulation module:

> from SimPy.Simulation import *

Before any SimPy simulation statements, such as defining processes or resources, are issued, the following statement must appear in the script:

> initialize()

Then there will be some SimPy statements, creating and activating objects. Execution of the timing mechanism itself starts when the following statement appears in the script:

> simulate(until=endtime)

The simulation then starts, the timer routine seeking the first scheduled event. The simulation will run until one of the following states:

> - there are no more events to execute (now() == the time of the last event)
> - the simulation time reaches endtime (now() == endtime)
> - the stopSimulation() command is executed (now() == the time when stopSimulation() was called).

The simulation can be stopped at any time using the command:

> stopSimulation()

which immediately stops the execution of the simulation.

Further statements can still be executed after exit from simulate.

The following partial script shows only the main block in a simulation program. Arrivals is a Process class (previously defined) and p is established as an object of that class. Activating p has the effect of scheduling at least one event The simulate(until=1000.0) starts the program start and it will immediately jump to that first event. It will continue until it runs out of events to execute or the simulation time reaches 1000.0:

    initialize()
    p = Arrivals(mean)
    activate(p,p.execute(),at=0.0)
    simulate(until=1000.0)

    Report()  #  when the simulation finishes

## Processes

The active objects for discrete-event simulation in SimPy are of classes that inherit from class Process.

For example, if we are simulating a messaging system we would model a message as a Process. A message arrives in a computing network; it makes transitions between nodes, waits for service at each one, and eventually leaves the system. The Message class describes these actions in an execute method. Individual messages are created as the program runs and they go through their modelled lifetimes.

### Defining a process

A process is a class that that inherits from the class Process. For example here is the header of the definition of a new Message process class:

- class Message(Process):

The user must define two particular methods and may define any others.

- __init__(self,...), where ... indicates method arguments. This function initializes the Process object, setting values for any attributes. The first line of this method must be a call to the Class __init__() in the form: Process.__init__(self,name='a_process')

  Then other commands can be used to initialize attributes of the object. The __init__() method is called automatically when a new message is created.

  In this example of an __init__() method for a Message class we give each new message an integer identification number, i, and message length, len as instance variables:

      def __init__(self,i,len):
          Process.__init__(self,name='Message'+str(i))
          self.i = i
          self.len = len

- A process execution method (PEM) This describes the actions of the process object and must contain at least one of the yield statements, described later, to make it a Python generator function. It can have arguments. Typically this can be called execute() or run() but any name may be chosen.

  The execution method starts when the process is activated and the simulate(until=...) statement has been called.

  In this example of the process execution method for the same Message class, the message prints out the current time, its identification number and the word 'Starting'. After a simulated delay it then announces it has 'Arrived':

      def go(self):
          print now(), self.i, 'Starting'
          yield hold,self,100.0
          print now(), self.i, 'Arrived'

A Process must be activated in order to start it operating (see Starting and stopping SimPy Processes)

Following is a complete, runnable, SimPy script. We declare a Message class and define __init__() and go() methods for it. Two messages, p1 and p2 are created. We do not actually use the len attribute in this example. p1 and p2 are activated to start at simulation times 0.0 and 6.0, respectively. Nothing happens until the simulate(until=200) statement. When they have both finished (at time 6.0+100.0=106.0) there will be no more events so the simulation will stop at that time:

    from __future__ import generators
    from SimPy.Simulation import *

    class Message(Process):
       """ a simple Process """
       def __init__(self,i,len):
           Process.__init__(self,name='Message'+str(i))
           self.i = i
           self.len = len

       def go(self):
           print now(), self.i, 'Starting'
           yield hold,self,100.0
           print now(), self.i, 'Arrived'

    initialize()
    p1  = Message(1,203)
    activate(p1,p1.go())
    p2  = Message(2,33)
    activate(p2,p2.go(),at=6.0)
    simulate(until=200)
    print now() # will print 106.0

### Elapsing time in a Process

An execution method can cause time to elapse for a process using the yield hold command:

- yield hold,self,t causes the object to wait for a delay of t time units (unless it is interrupted). It then continues its operation with the next statement. During the hold the object is suspended.
- yield passivate,self suspends the process's operations indefinitely.

This example of an execution method (buy) for a Customer class demonstrates that the method can have arguments which can be used in the activation. The Customer also has an identification attribute id. The yield hold is executed 4 times:

    def buy(self,budget=0):
       print 'Here I am at the shops ',self.id
       t = 5.0
       for i in range(4):
           yield hold,self,t
           print 'I just bought something ',self.id
           budget -= 10.00
       print   'All I have left is ', budget,\
               ' I am going home ',self.id,

    initialize()
    C = Customer(1)
    activate(C,C.buy(budget=100),at=10.0)
    simulate(until=100.0)

### Starting and stopping SimPy Processes

Once a Process object has been created, it is 'passive', i.e., it has no event scheduled. It must be activated to start the process execution method:

- activate(p,p.PEM(args)[,at=t][,delay=period][,prior=boolean]) will activate the execution method p.PEM() of Process instance, p with arguments args. The default action is to activate at the current time, otherwise one of the optional timing clauses operate. If prior is true, the process will be activated before any others at the specified time in the event list.

The process can be suspended and reactivated:

- yield passivate,self suspends the process itself. It becomes 'passive'.
- reactivate(p,at=t,delay=period,prior=boolean) will reactivate a passive process, p. It becomes 'active'. The optional timing clauses work as for activate. A process cannot reactivate itself. If that is required, use yield hold,self, . . . instead.
- self.cancel(p) deletes all scheduled future events for process p. Only 'active' processes can be cancelled. A process cannot cancel itself. If that is required, use yield passivate,self instead. Note: This new format replaces the p.cancel() form of earlier SimPy versions.

When all statements in a process execution method have been completed, a process becomes 'terminated'. If the instance is still referenced, it becomes just a data container. Otherwise, it is automatically destroyed.

And, finally,

- stopSimulation() stops all simulation activity, even if some processes still have events scheduled.

### Asynchronous interruptions

One process (the interrupter) can interrupt another, active, process (the victim). A process cannot interrupt itself.

- self.interrupt(victim)

The interrupt is just a signal. After this statement, the interrupter continues its current method.

The victim must be active. An active process is one that has an event scheduled for it (that is, it is 'executing' a yield hold,self,t). If the victim is not active (that is it is either passive or terminated) the interrupt has no effect on it. As processes queuing for resources are passive, they cannot be interrupted. Active processes which have acquired a resource can be interrupted.

If interrupted, the victim returns from its yield hold prematurely. It can sense if it has been interrupted by calling

- self.interrupted() which returns True if it has been interrupted. It can then either continue in the current activity or switch to an alternative, making sure it tidies up the current state, such as releasing any resources it owns. When this is True:
  - self.interruptCause is a reference to the interrupter instance.
  - self.interruptLeft gives the time remaining in the interrupted yield hold,

The interruption is reset at the victim's next call to a yield hold,. It can also be reset by calling

- self.interruptReset()

Here is a complete example of a simulation with interrupts. A bus is subject to breakdowns which are modelled as interruptions. Notice that in the first yield hold, interrupts may occur, so a reaction to the interrupt (= repair) has been programmed:

    class Bus(Process):
      def __init__(self,name):
         Process.__init__(self,name)

      def operate(self,repairduration,triplength):  # process execution method
         tripleft = triplength
         while tripleft > 0:
            yield hold,self,tripleft                # try to get through trip
            if self.interrupted():
                  print self.interruptCause.name, "at %s" %now() # breakdown
                  tripleft=self.interruptLeft       # yes; time to drive
                  self.interruptReset()             # end interrupt state
                  reactivate(br,delay=repairduration) # delay any breakdowns
                  yield hold,self,repairduration
                  print "Bus repaired at %s" %now()
            else:
                  break                             # no breakdown, bus arrived
            print "Bus has arrived at %s" %now()

    class Breakdown(Process):
       def __init__(self,myBus):
           Process.__init__(self,name="Breakdown "+myBus.name)
           self.bus=myBus

       def breakBus(self,interval):                 # process execution method
           while True:
              yield hold,self,interval
              if self.bus.terminated(): break
              self.interrupt(self.bus)

    initialize()
    b=Bus("Bus")
    activate(b,b.operate(repairduration=20,triplength=1000))
    br=Breakdown(b)
    activate(br,br.breakBus(300))
    print simulate(until=4000)

The ouput from this example:

    Breakdown Bus at 300
    Bus repaired at 320
    Breakdown Bus at 620
    Bus repaired at 640
    Breakdown Bus at 940
    Bus repaired at 960
    Bus has arrived at 1060
    SimPy: No more events at time 1260

Where interrupts can occur, the process which may be the victim of interrupts must test for interrupt occurrence after every "yield hold" and react to it. If a process holds a resource when it gets interrupted, it continues holding the resource.

### Starting the simulation

Even activated processes will not start until the following statement has been executed:

- simulate(until=T) starts the simulation going and it will continue until time T unless it runs out of events to execute or the command stopSimulation() is executed.

### A complete SimPy script

This complete runnable script simulates a firework with a time fuse. I have put in a few extra yield hold commands for added suspense:

    from __future__ import generators
    from SimPy.Simulation import *

    class Firework(Process):
       def __init__(self):
           Process.__init__(self)

       def execute(self):
           print now(), ' firework activated'
           yield hold,self, 10.0
           for i in range(10):
               yield hold,self,1.0
               print now(),  ' tick'
           yield hold,self,10.0
           print now(), ' Boom!!'

    initialize()
    f = Firework()
    activate(f,f.execute(),at=0.0)
    simulate(until=100)

The output from Example . No formatting of the output was attempted so it looks a bit ragged:

    0.0  firework activated
    11.0  tick
    12.0  tick
    13.0  tick
    14.0  tick
    15.0  tick
    16.0  tick
    17.0  tick
    18.0  tick
    19.0  tick
    20.0  tick
    30.0  Boom!!

### A source fragment

One useful program pattern is the source. This is an process with an execution method that generates events or activates other processes as a sequence -- it is a source of other processes. Random arrivals can be modelled using random (exponential) intervals between activations.

The following example is of a source which activates a series of customers to arrive at regular intervals of 10.0 units of time. The sequence continues until the simulation time exceeds the specified finishTime. (Of course, to achieve random'' arrivals of *customer*s the *yield hold method should use an exponential random variate instead of, as here, a constant 10.0 value) The example assumes that the Customer class has been defined with a PEM called run:

    class Source(Process):
       def __init__(self,finish):
           Process.__init__(self)
           self.finishTime = finish

       def execute(self):
          while now() < self.finishTime:
             c = Customer()          ## new customer
             activate(c,c.run())     ## activate it now
             print now(), ' customer'
             yield hold,self,10.0

    initialize()
    g = Source(33.0)
    activate(g,g.execute(),at=0.0)   ## start the source
    simulate(until=100)

## Resources

A resource models a congestion point where there may be queueing. For example in a manufacturing plant, a Task (modelled as a process) needs work done at a Machine (modelled as a resource). If a Machine unit is not available, the Task will have to wait until one becomes free. The Task will then have the use of it for however long it needs. It is not available for other Tasks until released. These actions are all automatically taken care of by the SimPy resource.

A resource can have a number of identical units. So there may be a number of identical Machine units. A process gets service by requesting a unit of the resource and, when it is finished, releasing it. A resource maintains a queue of waiting processes and another list of processes using it. These are defined and updated automatically.

A Resource is established by the following statement:

- r=Resource(capacity=1, name='a_resource', unitName='units', qType=FIFO, preemptable=0, monitored=False)

  - capacity is the number of identical units of the resource available.
  - name is the name by which the resource is known (eg gasStation)
  - unitName is the name of a unit of the resource (eg pump)
  - qType describes the queue discipline of the waiting queue of processes; typically, this is FIFO (First-in, First-out). and this is the presumed value. An alternative is PriorityQ
  - preemptable indicates, if it has a non-zero value, that a process being put into the PriorityQ may also pre-empt a lower-priority process already using a unit of the resource. This only has an effect when qType == PriorityQ
  - monitored indicates if the number of processes in the resource's queues (see below) are to be monitored (see Monitors, below)

  A Resource, r, has the following attributes:

  - r.n The number of units that are currently free.
  - r.waitQ A waiting queue (list) of processes (FIFO by default) The number of Processes waiting is len(r.waitQ)
  - r.activeQ A queue (list) of processes holding units. The number of Processes in the active queue is len(r.activeQ)
  - r.waitMon A Monitor recording the number in r.waitQ
  - r.actMon A Monitor recording the number in r.activeQ

A process can request and release a unit of resource, r using the following yield commands:

- yield request,self,r requests a unit of resource, r. The process may be temporarily queued and suspended until one is available.

  If, or when, a unit is free, the requesting process will take one and continue its execution. The resource will record that the process is using a unit (that is, the process will be listed in r.activeQ)

  If one is not free , the the process will be automatically placed in the resource's waiting queue, r.waitQ, and suspended. When a unit eventually becomes available, the first process in the waiting queue, taking account of the priority order, will be allowed to take it. That process is then reactivated.

  If the resource has been defined as being a priorityQ with preemption == 1 then the requesting process can pre-empt a lower-priority process already using a unit. (see Requesting a resource with preemptive priority, below)

- yield release,self,r releases the unit of r. This may have the side-effect of allocating the released unit to the next process in the Resource's waiting queue.

  In this example, the current Process requests and, if necessary waits for, a unit of a Resource, r. On acquisition it holds it while it pauses for a random time (exponentially distributed, mean 20.0) and then releases it again:

      yield request,self,r
      yield hold,self,g.expovariate(1.0/20.0)
      yield release,self,r

### Requesting resources with priority

If a Resource, r is defined with priority queueing (that is qType==PriorityQ) a request can be made for a unit by:

- yield request,self,r,priority requests a unit with priority. priority is real or integer. Larger values of priority represent higher priorities and these will go to the head of the r.waitQ if there not enough units immediately.

An example of a complete script where priorities are used. Four clients with different priorities request a resource unit from a server at the same time. They get the resource in the order set by their relative priorities:

    from __future__ import generators
    from SimPy.Simulation import *
    class Client(Process):
        inClients=[]
        outClients=[]

        def __init__(self,name):
           Process.__init__(self,name)

        def getserved(self,servtime,priority,myServer):
            Client.inClients.append(self.name)
            print self.name, 'requests 1 unit at t=',now()
            yield request, self, myServer, priority
            yield hold, self, servtime
            yield release, self,myServer
            print self.name,'done at t=',now()
            Client.outClients.append(self.name)

    initialize()
    server=Resource(capacity=1,qType=PriorityQ)
    c1=Client(name='c1') ; c2=Client(name='c2')
    c3=Client(name='c3') ; c4=Client(name='c4')
    activate(c1,c1.getserved(servtime=100,priority=1,myServer=server))
    activate(c2,c2.getserved(servtime=100,priority=2,myServer=server))
    activate(c3,c3.getserved(servtime=100,priority=3,myServer=server))
    activate(c4,c4.getserved(servtime=100,priority=4,myServer=server))
    simulate(until=500)
    print 'Request order: ',Client.inClients
    print 'Service order: ',Client.outClients

This program results in the following output:

    c1 requests 1 unit at t= 0
    c2 requests 1 unit at t= 0
    c3 requests 1 unit at t= 0
    c4 requests 1 unit at t= 0
    c1 done at t= 100
    c4 done at t= 200
    c3 done at t= 300
    c2 done at t= 400
    Request order:  ['c1', 'c2', 'c3', 'c4']
    Service order:  ['c1', 'c4', 'c3', 'c2']

### Requesting a resource with preemptive priority

In some models, higher priority processes can preempt lower priority processes when all resource units have been allocated. A resource with preemption can be created by setting arguments qType==PriorityQ and preemptable non-zero.

When a process requests a unit of resource and all units are in use it can preempt a lower priority process holding a resource unit. If there are several processes already active (that is, in the activeQ), the one with the lowest priority is suspended, put at the front of the waitQ and the preempting process gets its resource unit and is put into the activeQ. The preempted process is the next one to get a resource unit (unless another preemption occurs). The time for which the preempted process had the resource unit is taken into account when the process gets into the activeQ again. Thus, the total hold time is always the same, regardless of whether or not a process gets preempted.

An example of a complete script. Two clients of different priority compete for the same resource unit:

    from __future__ import generators
    from SimPy.Simulation import *
    class Client(Process):
        def __init__(self,name):
           Process.__init__(self,name)

        def getserved(self,servtime,priority,myServer):
            print self.name, 'requests 1 unit at t=',now()
            yield request, self, myServer, priority
            yield hold, self, servtime
            yield release, self,myServer
            print self.name,'done at t=',now()

    initialize()
    server=Resource(capacity=1,qType=PriorityQ,preemptable=1)
    c1=Client(name='c1')
    c2=Client(name='c2')
    activate(c1,c1.getserved(servtime=100,priority=1,myServer=server),at=0)
    activate(c2,c2.getserved(servtime=100,priority=9,myServer=server),at=50)
    simulate(until=500)

The output from this program is:

    c1 requests 1 unit at t= 0
    c2 requests 1 unit at t= 50
    c2 done at t= 150
    c1 done at t= 200

Here, c2 preempted c1 at t=50. At that time, c1 had held the resource for 50 of the total of 100 time units. c1 got the resource back when c2 completed at t=150.

### Monitoring a resource

If monitored is set True for a resource, r, the length of the waiting queue, len(r.waitQ) and the active queue,*len(r.activeQ)* are Both monitored automatically (see Monitors, below). This solves a problem, particularly for the waiting queue which cannot be monitored externally to the resource. The monitors are called r.waitMon and r.actMon, respectively. Complete time series for both queue lengths are maintained so that statistics, such as the time average can be found.

In this example, the resource, server is monitored and the time-average of each is calculated:

    from SimPy.Simulation import *
    class Client(Process):
         inClients=[]
         outClients=[]

         def __init__(self,name):
            Process.__init__(self,name)

         def getserved(self,servtime,myServer):
             print self.name, 'requests 1 unit at t=',now()
             yield request, self, myServer
             yield hold, self, servtime
             yield release, self,myServer
             print self.name,'done at t=',now()

    initialize()
    server=Resource(capacity=1,monitored=True)
    c1=Client(name='c1') ; c2=Client(name='c2')
    c3=Client(name='c3') ; c4=Client(name='c4')
    activate(c1,c1.getserved(servtime=100,myServer=server))
    activate(c2,c2.getserved(servtime=100,myServer=server))
    activate(c3,c3.getserved(servtime=100,myServer=server))
    activate(c4,c4.getserved(servtime=100,myServer=server))
    simulate(until=500)
    print 'Average waiting',server.waitMon.timeAverage()
    print 'Average in service',server.actMon.timeAverage()

The output from this program is:

    c1 requests 1 unit at t= 0
    c2 requests 1 unit at t= 0
    c3 requests 1 unit at t= 0
    c4 requests 1 unit at t= 0
    c1 done at t= 100
    c2 done at t= 200
    c3 done at t= 300
    c4 done at t= 400
    Average waiting 1.5
    Average in service 1.0

## Random Number Generation

Simulation usually needs pseudo-random numbers. SimPy uses the standard Python random module. Its documentation should be consulted for details. We can have multiple random streams, as in Simscript and ModSim.

One imports the Class and methods needed:

- from random import Random

You must define a random variable object using:

- g = Random([seed]) sets up the random variable object g using seed to initialize the sequence.

  For example, g= Random(111333) sets up the random variable object g and initializes its seed to 111333.

A good range of distributions is available. For example:

- g.random() returns the next random floating point number in the range [0.0, 1.0).
- g.expovariate(lambd) returns a sample from the exponential distribution. lambd is 1.0 divided by the desired mean. (The parameter would be called lambda, but that is a reserved word in Python.) Returned values range from 0 to positive infinity.
- g.normalvariate(mu,sigma) returns a sample from the normal distribution. mu is the mean, and sigma is the standard deviation.

This example uses exponential and normal random variables. The random object, g is initialized with its initial seed set to 333555. X and Y are pseudo-random variates from the two distributions using the object g:

    from random import Random

    g = Random(333555)

    X = g.expovariate(10.0)
    Y = g.normalvariate(100.0, 5.0)

## Monitors

A Monitor, a subclass of list, records a series of observed data values, y, and associated times, t. Simple averages can then be calculated from the series. Each Monitor observes one series of data values. For example we might use one Monitor to record the waiting times for customers and another to record the total number of customers in the shop. Because SimPy is a discrete-event system, the number of customers changes only at events and it is these that are recorded.

Monitors are not intended as a substitute for real statistical analysis but they have proved useful in developing simulations in SimPy.

Monitors are included in the Simulation module of the SimPy package. (In versions of SimPy 1.3 and earlier, the Monitor module was separate from the Simulation module and had to be imported independently. Previous programs still work as long as the from SimPy.Monitor import Monitor occurs after the importation from SimPy.Simulation)

To define a new Monitor object:

- m=Monitor(name='', ylab='y', tlab='t'), where name is the name of the monitor object, set to an empty string if it is missing. ylab and tlab are provided as labels for plotting graphs from the data held in the Monitor.

Methods of the Monitor class include:

- m.observe(y [,t]) records the current value of the variable, y and time t (the current time, now(), if t is missing).
- m.reset([t]) resets the observations. The recorded time series is set to the empty list, [] and the starting time to t or, if it is missing, to the current simulation time, now().

the Monitor object, m, holds the recorded data as a list of data pairs. Each pair, [t,y], records the time and the value of one observation. Simple data summaries can be obtained from such a Monitor object:

- m[i] holds the i th observation as a list, [ti, yi]
- m.yseries() returns a list of the recorded data values.
- m.tseries() returns a list of the recorded times.
- m.total() returns the sum of the y values
- m.count() returns the current number of observations. This is the same as len(m).
- m.mean() returns the simple average of the observations (see the left-hand picture below) If there are no observations, the message: 'SimPy: No observations for mean' is printed.
- m.var() returns the simple variance of the observations. If there are no observations, the message: 'SimPy: No observations for sample variance' is printed.
- m.timeAverage([t]) returns the time-average of the integrated y values, calculated from time 0 (or the last time m.reset([t]) was called) to time t (the current simulation time if t is missing). It is assumed that y is continuous in time but changes in steps at the times that observe(y) is called. In calculating the time-average the area under the graph is calculated as shown in the right-hand figure below. If there are no observations, the message 'SimPy: No observations for timeAverage'. If no time has elapsed, the message 'SimPy: No elapsed time for timeAverage' is printed.

- m.__str__() is a string that briefly describes the current state of the monitor. This can be used in a print statement.
- m.histogram(low=0.0,high=100.0,nbins=10) is a histogram object (a derived class of list) which contains the number of y values in each of its bins. It is calculated from the monitored y values. A histogram can be graphed using the plotHistogram method in the SimPlot package.
  - low is the lowest value of the histogram
  - high is the highest value of the histogram
  - nbins is the number of bins beween low and high into which the histogram is to be divided. The number of y values in each of the divisions is counted into the appropriate bin. Another 2 bins are constructed, counting the number of y values under the low value and the number over the high value. There are nbins+2 counts altogether.

Note: The following methods are retained for backwards compatibility but are not recommended. They may be removed in future releases of SimPy.

- m.tally(y) records the current value of y and the current time, now().
- m.accum(y [,t]) records the current value of y and time t (the current time, now(), if t is missing).

In this example we establish a Monitor to estimate the mean and variance of 1000 observations of an exponential random variate. A histogram with 30 bins (plus an under and an over count is also returned.:

    from SimPy.Simulation import *
    from random import Random

    M = Monitor()
    g = Random()

    for i in range(1000):
       y = g.expovariate(0.1)
       M.observe(y)

    print 'mean= ',M.mean(), 'var= ',M.var()
    h = M.histogram(low=0.0, high=20, nbins=30)

In this example, the number in the system, recorded as N, is being monitored to estimate the average number in the system (This example is only fragmentary):

    from SimPy.Simulation import *

    M = Monitor()

       ...   # upon an arrival of a job, increment N
             # the time used is now()
       N = N +1
       M.observe(N)
       ...

       ...   # upon a departure of a job
       N = N -1
       M.observe(N)

    print 'mean= ',M.timeAverage()

## Other Links

Several SimPy models are included with the SimPy code distribution.

Klaus Müller and Tony Vignaux, SimPy: Simulating Systems in Python, O'Reilly ONLamp.com, 2003-Feb-27, http://www.onlamp.com/pub/a/python/2003/02/27/simpy.html

Norman Matloff, Introduction to the SimPy Discrete-Event Simulation Package, U Cal: Davis, 2003, http://heather.cs.ucdavis.edu/~matloff/simpy.html

David Mertz, Charming Python: SimPy simplifies complex models, IBM Developer Works, Dec 2002, http://www-106.ibm.com/developerworks/linux/library/l-simpy.html

## Acknowledgments

We will be grateful for any corrections or suggestions for improvements to the document.

## Appendices

Here are some

### A1. SimPy Error Messages

#### Advisory messages

These messages are returned by simulate(), as in message=simulate(until=123).

Upon a normal end of a simulation, simulate() returns the message:

- SimPy: Normal exit. This means that no errors have occurred and the simulation has run to the time specified by the until parameter.

The following messages, returned by simulate(), are produced at a premature termination of the simulation but allow continuation of the program.

- SimPy: No more events at time x. All processes were completed prior to the endtime given in simulate(until=endtime).
- SimPy: No activities scheduled. No activities were scheduled when simulate() was called.

#### Fatal error messages

These messages are generated when SimPy-related fatal exceptions occur. They end the SimPy program. Fatal SimPy error messages are output to sysout.

- Fatal SimPy error: activating function which is not a generator (contains no 'yield'). A process tried to (re)activate a function which is not a SimPy process (=Python generator). SimPy processes must contain at least one yield . . . statement.
- Fatal SimPy error: Simulation not initialized. The SimPy program called simulate() before calling initialize().

#### 'Monitor' error messages

- SimPy: No observations for mean. No observations were made by the monitor before attempting to calculate the mean.

- SimPy: No observations for sample variance. No observations were made by the monitor before attempting to calculate the sample variance.

- SimPy: No observations for timeAverage, No observations

    were made by the monitor before attempting to calculate the time-average.

- SimPy: No elapsed time for timeAverage. No simulation time has elapsed before attempting to calculate the time-average.

### A2. SimPy Process States

From the point of the model builder, at any time, a SimPy process, p, can be in one of the following states:

- Active: Waiting for a scheduled event. This state simulates an activity in the model. Simulated time passes in this state. The process state p.active() returns True.
- Passive: Not active or terminated. Awaiting (re-)activation by another process. This state simulates a real world process which has not finished and is waiting for some trigger to continue. Does not change simulation time. p.passive() returns True.
- Terminated: The process has executed all its action statements and continues as a data instance, if referenced. p.terminated() returns True

Initially (upon creation of the Process instance), a process returns passive.

In addition, a SimPy process, p, can be in the following (sub)states:

- Interrupted: Active process has been interrupted by another

    process. It can immediately respond to the interrupt. This simulates an interruption of a simulated activity before its scheduled completion time. p.interrupted() returns True.

- Queuing: Active process has requested a busy resource and is waiting (passive) to be reactivated upon resource availability. p.queuing(a_resource) returns True.

### A3. SimPlot, The Simpy plotting utility

SimPlot provides an easy way to graph the results of simulation runs.

### A4. SimGUI, The Simpy Graphical User Interface

SimGUI provides a way for users to interact with a SimPy program, changing its parameters and examining the output.

### A5. SimulationTrace, the SimPy tracing utility

SimulationTrace has been developed to give users insight into the dynamics of the execution of SimPy simulation programs. It can help developers with testing and users with explaining SimPy models to themselves and others (e.g. for documentation or teaching purposes).

### A6. SimulationStep, the SimPy event stepping utility

SimulationStep can assist with debugging models, interacting with them on an event-by-event basis, getting event-by-event output from a model (e.g. for plotting purposes), etc.

It caters for:

> - running a simulation model, with calling a user-defined procedure after every event,
> - running a simulation model one event at a time by repeated calls,
> - starting and stopping the event stepping mode under program control.

### A7. SimulationRT, a real-time synchronizing utility

SimulationRT allows synchronizing simulation time and real (wallclock) time. This capability can be used to implement e.g. interactive game applications or to demonstrate a model's execution in real time.

Revision: | $Revision: 1.0 $
Date: | $Date: 2004-05-15 11:09:19+02 $ gav
Python-Version: | 2.2, 2.3
Created: | 2003-April-6