WaitFor

Back Up Next

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:

bulletwaitUntil.py, the module providing the waitUntil capability
bulletneedResources.py, the demo application
 

horizontal rule

©Copyright 2004, 2005, 2006, 2007, 2008 SimPy Developer Team.
For problems or questions regarding this web contact SimPy webmaster.
Last updated: 20/06/08.