This describes version 1.5.1 of SimPy.
There have been no changes to the SimPy 1.4 API, so existing code under
SimPy 1.5 works as before.
There are two new synchronization/scheduling facilities (see Advanced
synchronization/scheduling commands):
- events and signalling, with yield waitevent and yield queueevent
- process waiting for arbitrary conditions, with yield waituntil
This document briefly outlines the commands available in SimPy. It
refers to SimPy version 1.5 or later. The facilities described
require Python 2.2 or later. (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 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 request,self,r (see Resources, below)
- yield request,self,rp,priority (see Resources, below)
- yield release,self,r (see Resources, below)
- p = Pclass(..), constructs a new Pclass object, called, p,
where the arguments are those specified in the Class's __init__
method.
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.
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()
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 discipling 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
- r.actMon A Monitor recording the number in r.activeQ
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.
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.
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.
SimPy uses the standard random variate routines in the Python
random module. To use them, import the random module:
- from random import Random
- g = Random([seed]) defines a random variable object g using
a large integer, seed to initialize the sequence.
A good range of distributions is available. For example:
- g.random(), returns the next (uniform) random number between 0 and 1
- g.expovariate(lambd), returns a sample from the exponential
distribution with mean 1.0/lambd.
- g.normalvariate(mu,sigma), returns a sample from the normal (Gaussian)
distribution. mu is the mean, and sigma is the standard deviation.
SimPy 1.5 introduces two advanced process scheduling, event signalling and
a general "wait until" construct. They complement the existing scheduling
facilities, such as yield hold, and can make the implementation of many
simulation models easier. Because they are higher level constructs,they
can lead to much clearer and shorter scripts.
Events in SimPy are implemented by class SimEvent. 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.
An instance of a SimEvent is generated by something like 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 contines.
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 ocurrence of an event is signalled by:
<event>.signal(<payload parameter>)
The <payload parameter> is optional. It 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.
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.
The "wait until" construct is the most powerful synchronization construct.
It effectively generalizes all other SimPy synchronization constructs,
i.e., it could replace all of them (but at a runtime cost).
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 mey 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).
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.
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: 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.
We will be grateful for any corrections or suggestions for improvements
to the document.
Version: | $Revision: 1.1.1.2.2.1 $ |
Python-Version: | 2.2, 2.3, 2.5 |
Created: | 2002-December-10 |