simpylogo SimPy Cheatsheet
Description: SimPy Python Simulation Language
Keywords: simulation python stochastic
Date: 2006-06-05

# SimPy Cheatsheet

Authors: | Tony Vignaux <Vignaux@users.sourceforge.net>, Klaus Muller <Muller@users.sourceforge.net>
SimPy version: | 1.7.1
Web-site: | http://simpy.sourceforge.net/
Python-Version: | 2.2, 2.3, 2.4
Revision: | 1.1.1.18
Date: | 2006-06-05

Contents

- SimPy
- Processes
  - Starting and stopping SimPy Processes
  - Asynchronous interruptions
  - Advanced synchronisation/scheduling capabilities
  - Defining a SimEvent
- Resources
  - Requesting resources with priority
  - Requesting a resource with preemptive priority
  - Reneging -- leaving a queue before acquiring a resource
- Levels
  - Defining a Level
  - Getting and Putting
- Stores
  - Defining a Store
  - Getting and Putting
- Random variates
- Monitors
  - Defining a Monitor
  - Observing data
  - Data Summaries
  - Special Monitor methods:
  - Histograms
- Error Messages
  - Advisory messages
  - Fatal error messages
  - Monitor error messages
- Acknowledgements

# SimPy

This document outlines the commands available in version 1.7 of SimPy. A more complete description is held in the Manual.

A SimPy model is made up of Processes, Resources, Levels, Stores and Monitors and operations on them.

Basic structure of a SimPy simulation:

- from SimPy.Simulation import * which imports all facilities for the simulation program.
- initialize() which sets up the simulation model
- ... the activation of at least one process....
- simulate(until=endtime) starts the simulation which will run until one of the following:
  - there are no more events to execute. now()==last event time
  - the simulation time reaches endtime. now()==endtime
  - the stopSimulation() command is executed. now()==stop time

now() always returns the current simulation time and stopSimulation() will stop all simulation activity.

SimPy requires Python 2.2 or later [1].

[1] | If Python 2.2 is used, the command: from __future__ import generators must be placed at the top of all SimPy scripts.

[Return to Top ]

# Processes

Processes inherit from class Process, imported from SimPy.Simulation.

- class Pclass(Process): defines a new Process class (here, Pclass). Such a class must have a Process Execution Method (PEM) and may have an __init__ and other methods:
  - A Process execution method (PEM), which may have arguments, describes the actions of a process object and must contain at least one of the following yield statements to make it a Python generator function:
    - yield hold,self,t to execute a time delay of length t (unless the process is interrupted, see Asynchronous interruptions , below). The process continues at the next statement, after a delay in simulated time.
    - yield passivate,self to suspend operations indefinitely.
    - yield waituntil,self,<cond> to wait until a condition becomes True (see waituntil, below).
    - yield waitevent,self,<events part> to wait until an event occurs (see events, below).
    - yield queueevent,self,<events part> to queue until an event occurs (see events, below).
    - yield request,self,r to request a unit of resource, r (see Resources, below).
    - yield request,self,r,P to request a unit of resource, r, with priority, P (see Resources, below.)
    - yield release,self,r to release a unit of a resource (see Resources, below).
    - yield (request,self,resource [,P]),(hold,self,waittime) to request a unit of a resource, perhaps with priority, P, but to renege after waittime (see reneging, below).
    - yield (request,self,resource [,P]),(waitevent,self,events) to request a unit of a resource, perhaps with priority, P, but to renege if an event occurs (see reneging, below).
    - yield put,self,cB,q [,P] to put an amount q into a Level, cB, perhaps with priority, P. (see Levels, below)
    - yield get,self,cB,q [,P] to get an amount q from a Level, cB perhaps with priority, P. (see Levels, below)
    - yield put,self,sB,L [,P] to put a list, L, of objects into a Store, cB, perhaps with priority, P. (see Stores, below)
    - yield get,self,sB,n [,P] to get a list of length, n from a Store, cB perhaps with priority, P. (see Stores, below)
  - __init__(self,..), the first line of which must be a call to the Class __init__ in the form: Process.__init__(self,name='a_process'). Other commands can be used to initialize attributes of the object.
- p = Pclass(...), constructs a new Pclass object, called, p, where the arguments are those specified in the Class's __init__ method.

## Starting and stopping SimPy Processes

By the process itself:

- yield passivate,self suspends the process itself.

By other processes:

- activate(p,p.execute(args),at=t,delay=period,prior=boolean) activates the PEM, p.execute(), of Process p with arguments args. The default action is to activate at the current time, otherwise one of the optional timing clauses operate. If prior==True, the process will be activated before any others in the event list at the specified time.
- reactivate(p,at=t,delay=period,prior=boolean) will reactivate p after it has been passivated. The optional timing clauses work as for activate.
- self.cancel(p) deletes all scheduled future events for process p. Note: This new format replaces the p.cancel() form of earlier SimPy versions.

## Asynchronous interruptions

- self.interrupt(victim) interrupts another process. The interrupt is just a signal. After this statement, the interrupting process immediately continues its current method.

  The victim must be active to be interrupted (that is executing a yield hold,self,t) otherwise the interruption has no effect.

  The introduction of interrupts changes the semantics of yield hold. After before=now(); yield hold,self,T, we have the post-condition now()== before+T OR (self.interrupted() AND now()< before+T). The program must allow for this, i.e., for interrupted, incomplete activities.

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

- self.interrupted() which returns True if it has been interrupted. If so:

  - self.interruptCause gives the interruptor instance.
  - self.interruptLeft is the time remaining in the interrupted yield hold,

  The interruption is reset at the victims next call to a yield hold,. Alternatively it can be reset by calling

- self.interruptReset()

## Advanced synchronisation/scheduling capabilities

These include signalling between processes using events and a general wait-until command.

## Defining a SimEvent

Events in SimPy are objects of class SimEvent [2].

[2] | This name was chosen because the term 'event' is already being used in Python for e.g. tkinter events or in Python's standard library module signal -- Set handlers for asynchronous events.

A SimEvent, `sE` is established by the following statement:

    sE = SimEvent(name="a_SimEvent")

A SimEvent, `sE` has the following attributes:

> - `sE.occurred` (boolean, initially `False`) to indicate whether an event has happened (has been signalled)
> - `sE.waits` a list of processes waiting for the event
> - `sE.queues` a FIFO queue of processes queueing for the event
> - `SE.signalparam` a possible payload from the signal method

### Waiting or Queueing for a SimEvent

    yield waitevent,self,<events part>

called by SimPy Process, `SP`, waits for an event in <events part>. This can be:

> - an event variable, e.g. `myEvent`
> - a tuple of events, e.g. `(myEvent,myOtherEvent,TimeOut)`, or
> - a list of events, e.g. `[myEvent,myOtherEvent,TimeOut]`

If one of the events in the <events part> has already happened, the process continues. The `occurred` flag of the event(s) is toggled to `False`. Otherwise the process is passivated after joining the waits`` list of the event.

    yield queueevent,self,<events part>

called by SimProcess, `SP`, queues for an event in <events part> which is as defined above.

If one of the events in <event>s part> has already happened, the process continues. The `occurred` flag of the event(s) is toggled to `False`. Otherwise, the process is passivated after joining the FIFO `queues` for all the events.

### Signalling a SimEvent

    sE.signal(<payload parameter>)

Is used by a Process to signal a SimEvent, `sE`, where the payload parameter , of any Python type, is optional. It can be read by the process(es) triggered by the signal as the SimEvent attribute `sE.signalparam`, like `message = sE.signalparam`.

When this is called the flag `sE.occurred` is toggled to `True` if `sE.waits` and `sE.queues` are empty. Otherwise, all processes in the `sE.waits` list are reactivated at the current time, as well as the first process in `sE.queues` FIFO queue.

#### "wait until" synchronisation -- waiting for any condition

    yield waituntil,self,<cond>**

called by SimProcess, `SP`, allows it to wait for an arbitrary condition, <cond>. This is a reference to a function (without parameters) which returns the state of the condition to be waited for as a boolean value.

[Return to Top ]

# Resources

The modeller may define Resources. A Resource, `r`, is established by the following statement:

    r=Resource(capacity=1,
               name="a_resource",
               unitName="units",
               qType=FIFO, preemptable=False,
               monitored=False,
               monitorType=Monitor)

where

> - 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 is either `FIFO` or `PriorityQ`. It specifies the queue discipline of the waiting queue of processes; typically, this is FIFO (First-in, First-out) and this is the presumed value.
> - preemptable is a boolean (`False` or `True`) and indicates, if it is `True` that a process being put into the queue may also pre-empt a lower-priority process already using a unit of the resource. This only has an effect when `qType == PriorityQ`.
> - monitored is a boolean (`False` or `True`) that indicates if the size of the `waitQ` and `activeQ` queues (see below) are to be monitored. (see Monitors, below)
> - monitorType is either `Monitor` or `Tally` and is the variety of monitor to be used. (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). `len(r.waitQ)` is the number of Processes held in the waiting queue at any time.
> - `r.activeQ` A queue (list) of processes holding units. `len(r.activeQ)` is the number of Processes held in the active queue at any time.
> - `r.waitMon` A Monitor automatically recording the activity in r.waitQ if monitored==True
> - `r.actMon` A Monitor automatically recording the activity in r.activeQ if monitored==True

A unit of resource, r, can be requested and later released by a process using the following yield commands:

- yield request,self,r to request a unit of resource, r. The process may be temporarily queued and suspended until a unit is available.
- yield release,self,r releases a unit of r. This may have the side-effect of allocating the released unit to the next process in the Resource's waiting queue.

## 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,p, where P is the priority of the request and can be real or integer. Larger values of P represent higher priorities and these will go to the head of the r.waitQ if there not enough units immediately.

## Requesting a resource with preemptive priority

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

- yield request,self,r,p, where P is the priority of the request and can be real or integer. Larger values of P represent higher priorities and if there are not enough units available immediately, one of the active processes may be preempted.

If there are several lower priority processes, that with the lowest priority is suspended, put at the front of the waitQ and the higher priority, 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.

## Reneging -- leaving a queue before acquiring a resource

SimPy provides an extended (compound) yield request statement form to model reneging.

yield (request,self,resource[,P]),(<reneging clause>).

where P is the priority of the request if the resource is defined with qType=PriorityQ.

The structure of a SimPy model with reneging is:

    yield (request,self,resource),(<reneging clause>)
    if self.acquired(resource):
       ## process got resource and did not renege
       . . . .
       yield release,self,resource
    else:
       ## process reneged before acquiring resource
       . . . . .

A call to method (`self.acquired(resource)`) is mandatory after a compound yield request statement. It is not only a predicate which indicates whether or not the process has acquired the resource, but it also removes the reneging process from the resource's waitQ.

SimPy 1.6 implements two reneging clauses, one for reneging after a certain time and one for reneging when an event has happened.

### Reneging after a time limit

The reneging clause used is (hold,self,waittime)

- yield (request,self,resource[,P]),(hold,self,waittime)

If the resource unit has not been acquired by waittime, the process leaves the queue (reneges) and its execution continues. Method self.acquired(resource) must be called to check whether the resource has been acquired or not.

### Reneging when an event has happened

The reneging clause used is (waitevent,self,events).

- yield (request,self,resource[,P]),(waitevent,self,events)

where events is an event or list of events (see events). If one of the events has been signalled before the unit of resource has been acquired the process reneges. As before, self.acquired(resource) must be called to check whether the resource has been acquired or not

### Monitoring a resource

If the argument 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). The monitors are called r.waitMon and r.actMon, respectively.

The argument monitorType indicates which variety of monitor is to be used, either Monitor or Tally. The default is Monitor. If this is chosen, a complete time series for both queue lengths are maintained so that a graph of the queue length can be plotted and statistics, such as the time average can be found at any time. If Tally is chosen, statistics are accumulated continuously and time averages can be reported but, to save memory, no complete time series is kept.

[Return to Top ]

# Levels

A Level holds a scalar (real or integer) level. Processes can put amounts into the buffer get (remove) amounts from it.

## Defining a Level

A Level is established by the following statement:

    cB = Level(name="a_level", unitName="units",
                     capacity="unbounded",
                     initialBuffered=0,
                     putQType=FIFO, getQType=FIFO,
                     monitored=False, monitorType=Monitor,

where

> - name (string type) is the name by which the buffer is known (eg `"inventory"`).
> - unitName (string type) is the name of the unit of the buffer (eg `"widgets"`).
> - capacity (positive real or integer) is the capacity of the buffer. The default value is set to "unbounded" which translates as `sys.maxint`.
> - initialBuffered is the initial content of the buffer.
> - putQType (`FIFO` or `PriorityQ`) is the (producer) queue discipline.
> - getQType (`FIFO` or `PriorityQ`) is the (consumer) queue discipline.
> - monitored (boolean) sets the monitoring of the queues and the buffer.
> - monitorType (`Monitor` or `Tally`) sets the type of Monitor to be used. `

A Level, `cB`, has the following additional attributes:

> - `cB.amount` is the amount currently held in the Level.
> - `cB.putQ` is a queue of processes waiting to add amounts to the buffer. `len(cB.putQ` is the number of processes waiting to add amounts.
> - `cB.getQ` is a queue of processes waiting to get amounts from the buffer. `len(cB.getQ)` is the number of processes waiting to get amounts.
> - `cB.monitored` is `True` if the queues are to be monitored. In this case `cB.putQMon`, `cB.getQMon`, and `cB.bufferMon` exist.
> - `cB.putQMon` is a Monitor observing `cB.putQ`.
> - `cB.getQMon` is a Monitor observing `cB.getQ`.
> - `cB.bufferMon` is a Monitor observing `cB.amount`.

## Getting and Putting

    yield get,self,cB,q [,P]

called by SimPy Process, `SP` extracts an amount `q` from a Level, `cB` with priority `P` if `putQType==PriorityQ`. Here `q` can be a positive real or integer. If `q > cB.amount` the requesting process will be passivated and queued (in `cB.getQ`). It will be reactivated when there is enough available.

    yield put,self,cB,r [,P]

called by SimPy Process, `SP` can add an amount `r` to the Level, `cB` with priority `P` if `getQType==PriorityQ`. Here `r` is a positive real or integer. If (`cB.amount + r > cB.capacity`) the putting process is passivated and queued (in `cB.putQ`) until there is sufficient room.

# Stores

A Store buffers a number of distinguishable objects (of any type, including processes). Objects are put into the Store by processes and taken out by others.

## Defining a Store

A Store is established by the following statement:

    sB = Store(name="a_store", unitName="units",
                    capacity="unbounded",
                    initialBuffered=None,
                    putQType=FIFO, getQType=FIFO,
                    monitored=False, monitorType=Monitor)

where

> - name (string type) is the name by which the buffer is known (eg `"Inventory"`).
> - unitName (string type) is the name of the unit of the buffer (eg `"widgets"`).
> - capacity (positive real or integer) is the capacity of the buffer.
> - initialBuffered (a list) is the initial content of the buffer.
> - putQType (`FIFO` or `PriorityQ`) is the (producer) queue discipline.
> - getQType (`FIFO` or `PriorityQ`) is the (consumer) queue discipline.
> - monitored (boolean) sets the monitoring of the queues and the buffer.
> - monitorType (`Monitor` or `Tally`) sets the type of monitor to be used. `

A Store has the following additional attributes:

> - `sB.theBuffer` is a queue (list) containing the buffered objects (FIFO order unless the user is storing them in a particular order). This is read-only and cannot be changed by the user.
> - `sB.nrBuffered` is the number of objects currently buffered. This is read-only and cannot be changed by the user.
> - `sB.putQ` is a queue of processes waiting to add objects to the buffer. `len(sB.putQ` is the number of processes waiting to add objects.
> - `sB.getQ` is a queue of processes waiting to get objects from the buffer. `len(sB.getQ)` is the number of processes waiting to get objects.
> - `sB.monitored` is set to `True` when the buffer is created if the queues are to be monitored. In this case `sB.putQMon`, `sB.getQMon`, and `sB.bufferMon` exist.
> - `sB.putQMon` is a monitor observing `sB.putQ`.
> - `sB.getQMon` is a monitor observing `sB.getQ`.
> - `sB.bufferMon` is a monitor observing `sB.nrBuffered`.

## Getting and Putting

    yield get,self,sB,n [,P]

called by SimPy Process, `SP`, gets the first `n` objects from Store, `sB` with priority `P` if `getQType==PriorityQ`. The retrieved objects are returned in the list `SP.got`. If the buffer does not hold enough objects the requesting process will be passivated and queued (in `sB.getQ`). It will be reactivated when the request can be satisfied.

    yield put,self,sB,S [,P]

called by SimPy Process, `SP`, adds a list, `S` of objects to the Store, `sB` with priority `P` if `putQType==PriorityQ`. If this statement would lead to an overflow (that is, `sB.nrBuffered + len(S) > cB.capacity`) the putting process is passivated and queued (in `sB.putQ`) until there is sufficient room.

The objects are stored in queue `sB.theBuffer` in FIFO order unless the user has called an `sB.addSort(f)` method which will sort the buffer on any `yield get` using the user-defined routine `f`. (see the Manual for more details.

# Random variates

SimPy uses the standard random variate routines in the Python random module. To use them, import methods from the random module:

- from random import seed random,expovariate

The seed method allows the user to set the initial seed value for the pseudo-random stream. It is usual to set this at the start of all simulation programs.

- seed(i), sets the intial seed value to i (a large integer)

A good range of distributions is available. For example:

- random(), returns the next (uniform) random number between 0 and 1
- expovariate(lambd), returns a sample from the exponential distribution with mean 1.0/lambd.
- normalvariate(mu,sigma), returns a sample from the normal (Gaussian) distribution. mu is the mean, and sigma is the standard deviation.

[Return to Top ]

# Monitors

Monitors are used to observe variables of interest and to return a simple data summary during a simulation run. Each monitoring object observes one variable.

There are two varieties of monitoring objects, Tally and Monitor. The simpler class Tally, records enough information (sums and sums of squares) to return simple data summaries at any time. The more complicated class Monitor, keeps a complete series of observed data values, y, and associated times, t.

Both varieties of monitor use the same observe method to record data on the variable.

## Defining a Monitor

To define a new Tally object:

- m=Tally(name=<string>, ylab=<string>, tlab=<string>)
  - name is the name of the tally object (default='a_Tally').
  - ylab and tlab are provided as labels for plotting graphs(defaults='y' and 't', respectively)

To define a new Monitor object:

- m=Monitor(name=<string>, ylab=<string>, tlab=<string>)
  - name is the name of the tally object(default='a_Monitor').
  - ylab and tlab are provided as labels for plotting graphs (defaults='y' and 't', respectively)

## Observing data

For a monitor, m:

- m.observe(y [,t]) records the current value of 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().

## Data Summaries

- m.count() the current number of observations.
- m.total(), the sum of the y values
- m.mean(), the simple average of the observations, unaffected by the time measurements
- m.var(), the sample variance of the observations.
- m.timeAverage([t]), the average of the y values weighted by the time differences between observations. This is 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. values.
- m.__str__(), a string that briefly describes the current state of the monitor.

## Special Monitor methods:

The Monitor variety is a sub-class of List and has a few extra methods:

- m[i] holds the i th observation as a list, [ti, yi]
- m.yseries() a list of the recorded data values, yi
- m.tseries() a list of the recorded times, ti

## Histograms

A histogram object is basically a list of bins. Each bin contains the number of y values observed in its range of values. Both varieties of monitor can return a histogram of the data but they do this in different ways. A histogram can be graphed using the plotHistogram method in the SimPlot package.

Monitor objects accumulate the histogram values from the stored data series when needed. A histogram can be both set up (that is, specifying the number of bins and the range of observations) and returned in a single call, e.g.:

- h = m.histogram(low=0.0,high=100.0,nbins=10)

Tally objects accumulate values for the histogram as each value is observed. The histogram must therefore be set up before any values are observed using the setHistogram method, e.g.:

- m.setHistogram(name = '',low=0.0,high=100.0,nbins=10)

Then, after observing the data we return the histogram by:

- h = m.getHistogram() to return a completed histogram using the

    histogram parameters as set up.

[Return to Top ]

# 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().
- SimPy: Attempt to schedule event in the past: A yield hold statement has a negative delay time parameter.
- SimPy: initialBuffered exceeds capacity: Attempt to initialize a Store or Level with more units in the buffer than its capacity allows.
- SimPy: initialBuffered param of Level negative: x: Attempt to initialize a Level with a negative amount x in the buffer.
- SimPy: Level: wrong type of initialBuffered (parameter=x): Attempt to initialize a buffer with a non-numerical initial buffer content x.
- SimPy: Level: put parameter not a number: Attempt to add a non-numerical amount to a Level's buffer.
- SimPy: Level: put parameter not positive number: Attempt to add a negative number to a Level's amount.
- SimPy: Level: get parameter not positive number: x: Attempt to get a negative amount x from a Level.
- SimPy: Store: initialBuffered not a list: Attempt to initialize a Store with other than a list of items in the buffer.
- SimPy: Item to put missing in yield put stmt: A yield put was malformed by not having a parameter for the item(s) to put into the Store.
- SimPy: put parameter is not a list: yield put for a Store must have a parameter which is a list of items to put into the buffer.
- SimPy: Store: get parameter not positive number: x: A yield get for a Store had a negative value for the number to get from the buffer.
- SimPy: Fatal error: illegal command: yield x: A yield statement with an undefined command code (first parameter) x was executed.

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

[Return to Top ]

# Acknowledgements

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

[Return to Top ]