|
| |
Note: SimPy 1.5 implements a
general "wait until" capability. The following is only shown to demonstrate
how easy SimPy extension is.
Typical model scenario: The trajectory of a simulated process'
execution often depends on one or more complex conditions, involving not only
time, but also the values of other state variables.
Problem: The only two conditional wait constructs which SimPy has are
yield hold,self,period and
yield request,self,resource. Thus,
the only conditions which can be waited for are ones which depend either on time
or on availability of one resource. This makes it difficult to implement a model
where a process has to wait for e.g. availability of several resources or
specific values of one or more state variables other than time or resources.
A solution approach: A useful generalization of the existing
conditional wait constructs could be something like
waitUntil(condition), with condition
being a Boolean expression of arbitrary complexity. When executed by a process,
the intended semantics are that the executing process becomes passive until the
Boolean expression becomes true. Such construct would generalize not only
SimPy's existing conditional wait constructs (hold
and request), but also other ones, like
waiting for events.
Clearly, such interrogative scheduling is less run-time efficient than
the imperative scheduling provided by SimPy so far. After any imperative
event, the wait conditions of processes passivated by the interrogative
form must be tested.
An experimental implementation
The simulate function in SimPy's
SimulationStep module has the capability of
calling a callback function after every imperative event. In the following
prototype of a waitUntil facility, this is exploited.
"""
waitUntil.py
Prototype of a general 'waitUntil' interrogative scheduling facility for SimPy.
If introduced into SimPy, could be implemented as 'yield waitUntil,self,cond',
with cond being a predicate function returning True if the condition to be waited
for is satisfied. 'test' would be added to SimPy and would be called in event
loop in 'simulate' in Simulation and SimulationXXXX.
"""
from __future__ import generators
from SimPy.SimulationStep import *
def test():
"""
Gets called by simulate after every event, as long as there are processes
waiting in condQ for a condition to be satisfied.
Tests the conditions for all waiting processes. Where condition satisfied,
reactivates that process immediately and removes it from queue.
"""
global condQ
rList=[]
for el in condQ:
if el.cond():
rList.append(el)
reactivate(el)
for i in rList:
condQ.remove(i)
if not condQ:
stopStepping()
def waitUntil(proc,cond):
global condQ
"""
Puts a process 'proc' waiting for a condition into a waiting queue.
'cond' is a predicate function which returns True if the condition is
satisfied.
"""
condQ.append(proc)
proc.cond=cond
startStepping() #signal 'simulate' that a process is waiting
condQ=[] |
The waitUntil could be implemented as 'yield waitUntil,self,cond',
with cond being a predicate function returning True if the condition to be
waited for is satisfied. 'test' would be added to SimPy and would be called in
event loop in 'simulate' in Simulation and every other SimulationXXXX. This
would result in even simpler applications code, but would expand the yield
part of the SimPy API. Semantically, this would be the same as the prototyped
approach shown above.
Sample application demo program
To show how a model with waitUntil could be programmed, here is a demo. The
scenario is that three workers are working in a house. To do their jobs, they
need to have specific sets of tools available. These tools are shared resources,
and not enough tools are available to allow all workers to work at the same
time.
Note the 'while not workerNeeds()'
loop. Using while instead of
if ensures that the needed resources are only
requested iff they are all available. When several waiting Worker processes
competing for the same resources get reactivated because the requested resources
have become free, the first process may invalidate the condition in
workerNeeds by taking resources other processes
are waiting for, too.
#!/usr/bin/env python
"""
needResources.py
Demo of waitUntil capability.
Scenario:
Three workers require sets of tools to do their jobs. Tools are shared, scarce
resources for which they compete.
"""
from SimPy.Simulation import *
from waitUntil import *
import random
class Worker(Process):
def work(self,heNeeds=[]):
def workerNeeds():
for item in heNeeds:
if item.n==0:
return False
return True
while now()<8*60:
while not workerNeeds():
waitUntil(self,workerNeeds)
yield passivate,self
for item in heNeeds:
yield request,self,item
print "%s %s has %s and starts job" %(now(),self.name,
[x.name for x in heNeeds])
yield hold,self,random.uniform(10,30)
for item in heNeeds:
yield release,self,item
yield hold,self,2 #rest
initialize()
brush=Resource(capacity=1,name="brush")
ladder=Resource(capacity=2,name="ladder")
hammer=Resource(capacity=1,name="hammer")
saw=Resource(capacity=1,name="saw")
painter=Worker("painter")
activate(painter,painter.work([brush,ladder]))
roofer=Worker("roofer")
activate(roofer,roofer.work([hammer,ladder,ladder]))
treeguy=Worker("treeguy")
activate(treeguy,treeguy.work([saw,ladder]))
simulate(until=9*60,callback=test)
OUTPUT:
0 painter has ['brush', 'ladder'] and starts job
26.1185159716 roofer has ['hammer', 'ladder', 'ladder'] and starts job
42.9027129532 treeguy has ['saw', 'ladder'] and starts job
42.9027129532 painter has ['brush', 'ladder'] and starts job
69.0055586819 roofer has ['hammer', 'ladder', 'ladder'] and starts job
91.9103064699 painter has ['brush', 'ladder'] and starts job
91.9103064699 treeguy has ['saw', 'ladder'] and starts job
104.370941641 roofer has ['hammer', 'ladder', 'ladder'] and starts job
128.448885411 treeguy has ['saw', 'ladder'] and starts job
128.448885411 painter has ['brush', 'ladder'] and starts job
150.991834989 roofer has ['hammer', 'ladder', 'ladder'] and starts job
170.68673949 painter has ['brush', 'ladder'] and starts job
170.68673949 treeguy has ['saw', 'ladder'] and starts job
198.122316314 roofer has ['hammer', 'ladder', 'ladder'] and starts job
226.288416489 treeguy has ['saw', 'ladder'] and starts job
226.288416489 painter has ['brush', 'ladder'] and starts job
249.242564546 roofer has ['hammer', 'ladder', 'ladder'] and starts job
270.020812519 treeguy has ['saw', 'ladder'] and starts job
270.020812519 painter has ['brush', 'ladder'] and starts job
297.081317711 roofer has ['hammer', 'ladder', 'ladder'] and starts job
316.868934437 painter has ['brush', 'ladder'] and starts job
316.868934437 treeguy has ['saw', 'ladder'] and starts job
338.142266896 roofer has ['hammer', 'ladder', 'ladder'] and starts job
357.568802017 treeguy has ['saw', 'ladder'] and starts job
357.568802017 painter has ['brush', 'ladder'] and starts job
380.223704085 roofer has ['hammer', 'ladder', 'ladder'] and starts job
403.671488521 treeguy has ['saw', 'ladder'] and starts job
403.671488521 painter has ['brush', 'ladder'] and starts job
430.41526288 roofer has ['hammer', 'ladder', 'ladder'] and starts job
450.756403756 treeguy has ['saw', 'ladder'] and starts job
450.756403756 painter has ['brush', 'ladder'] and starts job
468.327492784 roofer has ['hammer', 'ladder', 'ladder'] and starts job
491.931555555 painter has ['brush', 'ladder'] and starts job
491.931555555 treeguy has ['saw', 'ladder'] and starts job
|
Download
If you want to experiment with these experimental constructs yourself,
download here:
|