===========================================================
|simpylogo|  SimPy Cheatsheet
===========================================================

:Authors:  - Tony Vignaux <Vignaux@users.sourceforge.net>,
           - Klaus Muller <Muller@users.sourceforge.net>
:Date: 2005-June-6
:SimPy version: 1.6
:Web-site: http://simpy.sourceforge.net/
:Python-Version: 2.2, 2.3, 2.4


.. .. contents:: Contents 
   :depth: 2


SimPy
-------------------

This document outlines the commands available in version 1.6
of *SimPy*.  Python 2.2 or later is required. (When using Python
2.2, the following import statement must be used at the head of SimPy
scripts: **from __future__ import generators**)

A SimPy model is made up of Processes_, Resources_ 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.



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:
 
    - **__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.

    - **A Process execution method (PEM)**, which may have arguments,
      describes the actions of a process object and must contain at
      least one of the *yield* statements to make it a Python
      generator function. The *yield* statements are:

      * **yield hold,self,t** to execute a time delay of length *t*
	(unless the process is interrupted, see below). The process
	continues at the statement following 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 a resource (see `Resources`_, below)
      * **yield request,self,rp,priority** to   release  a unit of a resource (see `Resources`_, below)
      * **yield release,self,r** to   request  a unit of a resource
	with priority  (see `Resources`_, below)
      * **yield
	(request,self,resource[,priority]),(hold,self,waittime)** to
	request a unit of a resource, perhaps with priority, but to renege after *waittime* (see reneging_, below)
      * **yield (request,self,resource),(waitevent,self,events)** to
	request a unit of a resource, perhaps with priority, but to renege if an event occurs (see reneging_, below)


* **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 execution method *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.


Signalling between processes
==============================

Events in *SimPy* are implemented by class **SimEvent**. A new event, *myEvent*,
is generated by:

**myEvent=SimEvent("MyEvent")**.

Associated with a SimEvent are

    - a boolean **occurred** to show whether an event has happened (has been signalled)
    - a list **waits**, implementing a set of processes waiting for the event
    - a list **queues**, implementing a FIFO queue of processes queueing for the event
    - an attribute **signalparam** to receive an (optional) payload from the **signal**
      method
    
Processes can *wait* for events by issuing:

    **yield waitevent,self,<events part>**

<events part> 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]*
        
Processes can *queue* for events by issuing:

    **yield queueevent,self,<events part>**
    (with <events part> as defined above)

If one of the events in *<events part>* has already happened, the process continues.
The *occurred* flag of the event(s) is toggled to False.

If none of the events in the *<events part>* has happened, the process is passivated
after joining the FIFO queue of processes queuing for all the events.

The occurrence of an event is signalled by:

    **<event>.signal(<payload parameter>)**
    
The optional *<payload parameter>*  can be of any Python type.

When issued, *signal* causes the *occurred* flag of the event to be toggled to True, if
waiting set and and queue are empty. Otherwise, all processes in the event's *waits*
list are reactivated at the current time, as well as the first process in its *queues*
FIFO queue.

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

A process can wait for an arbitrary condition by issuing:

    **yield waituntil,self,<cond>**
    
where *<cond>* is a reference to a function (without parameters) which returns the state
of the condition to be waited for as a *boolean* value.

.. ===================================================================

Resources
-------------------
 

The modeller may define Resources.  These inherit from class
*Resource* which is imported at the start of the program:
*from SimPy.Simulation import Resource*

A *Resource*, *r*, is established using the command: 
 
* **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. Its default setting is 1 but can be any positive integer.
- *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 default.  An alternative is *PriorityQ* (see below)
- *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* (see below)
- *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 currently free units
-  *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 Proceeses in the active queue is *len(r.activeQ)*
-  *r.waitMon* A Monitor recording the number in *r.waitQ* (see
   Monitor_, below)
-  *r.actMon*  A Monitor recording the number in *r.activeQ* (see
   Monitor_, below)

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,priority**, where *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.
 

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,priority**, where *priority* is real or
  integer.  Larger values of *priority* 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[,priority]),(<reneging clause>)**. 

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[,priority]),(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[,priority]),(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

.. ====================================================================

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 random,expovariate**


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.


Monitors
---------------------------- 

Monitors are part of the SimPy package.

To define a new Monitor object:

* **m=Monitor(name='a_Monitor', ylab='y', tlab='t')**, where *name* is
  the name of the monitor object, *ylab* and *tlab* are provided as
  labels for plotting graphs from the data held in the Monitor. 

Methods:
 
* **m.observe(y [,t])** record the current value of *y* and time *t* (the current
  time, *now()*, if *t* is missing). 
* **m.reset([t])** reset 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()*.

 
Simple data summaries: 

* **m.series()**, the recorded time series as a list of
  data pairs. Each pair, *[t,y]*, records one observation and its time.
* **m.yseries()**, a list of the recorded data values.
* **m.tseries()**, a list of the recorded times.
* **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.
* **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.
* **m.__str__()**, a  string that briefly describes the current state
  of the monitor.


Deprecated methods:

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


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.



Acknowledgements
-------------------

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



:Version:  $Revision: 1.1.1.2.2.1.6.3 $
:Python-Version: 2.2, 2.3, 2.5
:Created: 2002-December-10

.. ----------------------------------------------------------------------------
.. some useful stuff used above
.. |simpylogo| image:: images/sm_SimPy_Logo.png
.. _Monitor: Monitors_
.. _reneging: `Reneging -- leaving a queue before acquiring a resource`_
.. _interrupted: `Asynchronous interruptions`_
.. _events: `Signalling between processes`_
.. _waituntil: `"wait until" synchronisation -- waiting for any condition`_


..
   Local Variables:
   mode: rst
   indent-tabs-mode: nil
   sentence-end-double-space: t
   fill-column: 70 
   End:

