Authors: |
|
---|---|
SimPy version: | 1.5.1 |
Date: | 2004-December-1 |
Web-site: | http://simpy.sourceforge.net/ |
Python-Version: | 2.2, 2.3, 2.4 |
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.5.1 of SimPy.
The SimPy API provided by version 1.4.x is unchanged and programs written to it work under version 1.5.1 as before.
SimPy 1.5 has added two Advanced synchronization/scheduling capabilities, namely event signalling and a general "wait until condition" construct. They can be used by four new commands:
- yield waitevent,self,event(s)
- yield queueevent,self,event(s)
- event.signal(some_payload)
- yield waituntil,self,condition
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
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
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.
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:
The user must define a Process Execution Method (PEM) and may define an __init__ method and 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
If you do not wish to set any attributes (other than a name, the __init__ method may be dispensed with.
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
An execution method can cause time to elapse for a process using the yield hold command:
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)
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:
The process can be suspended and reactivated:
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,
One process (the interrupter) can interrupt another, active, process (the victim). A process cannot interrupt itself.
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
The interruption is reset at the victim's next call to a yield hold,. It can also be reset by calling
Here is an 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. The bus Process here does not require an __init__ method:
class Bus(Process): def operate(self,repairduration,triplength): # process execution method (PEM) 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.
Even activated processes will not start until the following statement has been executed:
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 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!!
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)
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)
A Resource, r, has the following attributes:
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
If a Resource, r is defined with priority queueing (that is qType==PriorityQ) a request can be made for a unit by:
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']
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.
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
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:
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:
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)
(SimPy 1.5 and beyond) All scheduling constructs discussed so far are either time-based, i.e., they make processes wait until a certain time has passed, or use direct reactivation of processes. For a wide range of models, these constructs are totally satisfactory and sufficient.
In some modeling situations, the SimPy scheduling constructs are too rich or too generic and could be replaced by simpler, safer constructs. SimPy 1.5 has introduced synchronization by events and signals as one such possible construct.
On the other side, there are models which require synchronization/scheduling by other than time-related wait conditions. SimPy has introduced a general "wait until" to support clean implementation of such models.
Event signalling is particularly useful in situations where processes must wait for completion of activities of unknown duration. This situation is often encountered, e.g. when modeling real time systems or operating systems.
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]
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 set of processes waiting for all the events.
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.
Here is a small, complete SimPy script illustrating the new constructs:
from __future__ import generators from SimPy.Simulation import * class Waiter(Process): def waiting(self,myEvent): yield waitevent,self,myEvent print "%s: after waiting, event %s has happened"%(now(),myEvent.name) class Queuer(Process): def queueing(self,myEvent): yield queueevent,self,myEvent print "%s: after queueing, event %s has happened"%(now(),myEvent.name) print " just checking: event(s) %s fired"%([x.name for x in self.eventsFired]) class Signaller(Process): def sendSignals(self): yield hold,self,1 event1.signal() yield hold,self,1 event2.signal() yield hold,self,1 event1.signal() event2.signal() initialize() event1=SimEvent("event1"); event2=SimEvent("event2") s=Signaller(); activate(s,s.sendSignals()) w0=Waiter(); activate(w0,w0.waiting(event1)) w1=Waiter(); activate(w1,w1.waiting(event1)) w2=Waiter(); activate(w2,w2.waiting(event2)) q1=Queuer(); activate(q1,q1.queueing(event1)) q2=Queuer(); activate(q2,q2.queueing(event1)) simulate(until=10)
When run, this produces:
1: after waiting, event event1 has happened 1: after waiting, event event1 has happened 1: after queueing, event event1 has happened just checking: event(s) ['event1'] fired 2: after waiting, event event2 has happened 3: after queueing, event event1 has happened just checking: event(s) ['event1'] fired
When event1 fired at time 1, two processes (w0 and w1)were waiting for it and both got reactivated. Two proceses were queueing for it(q1 and q2), but only one got reactivated. The second queueing process got reactivated when event1 fired again. The 'just checking' line reflects the content of the process' self.eventsFired attribute.
Simulation models where progress of a process depends on a general condition involving non-timerelated state-variables (such as "goodWeather OR (nrCustomers>50 AND price<22.50") are difficult to implement with SimPy constructs prior to version 1.5. They require interrogative scheduling, while all other SimPy synchronization constructs are imperative: after every SimPy event, the condition must be tested until it becomes True. Effectively, a new (hidden, system) process has to interrogate the value of the condition. Clearly, this is less runtime-efficient than the event-list scheduling used for the other SimPy constructs. The SimPy 1.5.1 implementation therefore only activates that interrogation process when there is a process waiting for a condition. When this is not the case, the runtime overhead is minimal (about 1 procent extra runtime).
The new construct takes the form:
yield waituntil,self,<cond>
<cond> is a reference to a function without parameters which returns the state of condition to be waited for as a boolean value.
Here is a simple program showing the use of yield waituntil:
from SimPy.Simulation import * import random class Player(Process): def __init__(self,lives=1): Process.__init__(self) self.lives=lives self.damage=0 def life(self): self.message="I survived alien attack!" def killed(): return self.damage>5 while True: yield waituntil,self,killed self.lives-=1; self.damage=0 if self.lives==0: self.message= "I was wiped out by alien at time %s!"%now() stopSimulation() class Alien(Process): def fight(self): while True: if random.randint(0,10)<2: #simulate firing target.damage+=1 #hit target yield hold,self,1 initialize() gameOver=100 target=Player(lives=3); activate(target,target.life()) shooter=Alien(); activate(shooter,shooter.fight()) simulate(until=gameOver) print target.message
In summary, 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).
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:
Methods of the Monitor class include:
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() a list of the recorded data values.
m.tseries() a list of the recorded times.
m.total() the sum of the y values
m.count() the current number of observations. This is the same as len(m).
m.mean() 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.
This is illustrated in the figure.
Note: The following methods are retained for backwards compatibility but are not recommended. They may be removed in future releases of SimPy.
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()
Several SimPy models are included with the SimPy code distribution.
Klaus Muller 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
We will be grateful for any corrections or suggestions for improvements to the document.
These messages are returned by simulate(), as in message=simulate(until=123).
Upon a normal end of a simulation, simulate() returns the message:
The following messages, returned by simulate(), are produced at a premature termination of the simulation but allow continuation of the program.
These messages are generated when SimPy-related fatal exceptions occur. They end the SimPy program. Fatal SimPy error messages are output to sysout.
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.
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.
From the point of the model builder, at any time, a SimPy process, p, can be in one of the following states:
Initially (upon creation of the Process instance), a process returns passive.
In addition, a SimPy process, p, can be in the following (sub)states:
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.
SimPlot provides an easy way to graph the results of simulation runs.
SimGUI provides a way for users to interact with a SimPy program, changing its parameters and examining the output.
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).
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.
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.
Created: | 2003-April-6 |
---|