Package SimPy :: Module SimulationRT |
[hide private]
[frames] | no frames]

# Source Code for Module SimPy.SimulationRT

      1  #!/usr / bin / env python
      2  # $Revision: 271 $ $Date: 2009-03-25 13:27:06 +0100 (Mi, 25 Mrz 2009) $ kgm
      3  """SimulationRT 2.0 Provides synchronization of real time and SimPy simulation time.
      4  Implements SimPy Processes, resources, and the backbone simulation scheduling
      5  by coroutine calls.
      6  Based on generators (Python 2.3 and later; not 3.0)
      7
      8  LICENSE:
      9  Copyright (C) 2002, 2005, 2006, 2007, 2008  Klaus G. Muller, Tony Vignaux
     10  mailto: kgmuller@xs4all.nl and Tony.Vignaux@vuw.ac.nz
     11
     12      This library is free software; you can redistribute it and / or
     13      modify it under the terms of the GNU Lesser General Public
     14      License as published by the Free Software Foundation; either
     15      version 2.1 of the License, or (at your option) any later version.
     16
     17      This library is distributed in the hope that it will be useful,
     18      but WITHOUT ANY WARRANTY; without even the implied warranty of
     19      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     20      Lesser General Public License for more details.
     21
     22      You should have received a copy of the GNU Lesser General Public
     23      License along with this library; if not, write to the Free Software
     24      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111 - 1307  USA
     25  END OF LICENSE
     26
     27
     28  **Change history:**
     29      4 / 8/2003: - Experimental introduction of synchronization of simulation
     30                  time and real time (idea of Geoff Jarrad of CSIRO -- thanks,
     31                  Geoff!).
     32                  * Changes made to class EvlistRT, _nextev(), simulate()
     33
     34      Dec 11, 2003:
     35              - Updated to Simulation 1.4alpha API
     36
     37      13 Dec 2003: Merged in Monitor and Histogram
     38
     39      27 Feb 2004: Repaired bug in activeQ monitor of class Resource. Now actMon
     40                   correctly records departures from activeQ.
     41
     42      19 May 2004: Added erroneously omitted Histogram class.
     43
     44      5 Sep 2004: Added SimEvents synchronization constructs
     45
     46      17 Sep 2004: Added waituntil synchronization construct
     47
     48      28 Sep 2004: Changed handling of real time -- now uses time.clock for Win32, and
     49                   time.time for all other OS (works better on Linux, Unix).
     50
     51      01 Dec 2004: SimPy version 1.5
     52                   Changes in this module: Repaired SimEvents bug re proc.eventsFired
     53
     54      12 Jan 2005: SimPy version 1.5.1
     55                   Changes in this module: Monitor objects now have a default name
     56                                           'a_Monitor'
     57
     58      29 Mar 2005: Start SimPy 1.6: compound 'yield request' statements
     59
     60      05 Jun 2005: Fixed bug in _request method -- waitMon did not work properly in
     61                   preemption case
     62
     63      09 Jun 2005: Added test in 'activate' to see whether 'initialize()' was called first.
     64
     65      23 Aug 2005: - Added Tally data collection class
     66                   - Adjusted Resource to work with Tally
     67                   - Redid function allEventNotices() (returns prettyprinted string with event
     68                     times and names of process instances
     69                   - Added function allEventTimes (returns event times of all scheduled events)
     70
     71      16 Mar 2006: - Added Store and Level classes
     72                   - Added 'yield get' and 'yield put'
     73
     74      10 May 2006: - Repaired bug in Store._get method
     75                   - Repaired Level to allow initialBuffered have float value
     76                   - Added type test for Level get parameter 'nrToGet'
     77
     78      06 Jun 2006: - To improve pretty - printed output of 'Level' objects, changed attribute
     79                     _nrBuffered to nrBuffered (synonym for amount property)
     80                   - To improve pretty - printed output of 'Store' objects, added attribute
     81                     buffered (which refers to _theBuffer)
     82
     83      25 Aug 2006: - Start of version 1.8
     84                   - made 'version' public
     85                   - corrected condQ initialization bug
     86
     87      30 Sep 2006: - Introduced checks to ensure capacity of a Buffer > 0
     88                   - Removed from __future__ import (so Python 2.3 or later needed)
     89
     90      15 Oct 2006: - Added code to register all Monitors and all Tallies in variables
     91                     'allMonitors' and 'allTallies'
     92                   - Added function 'startCollection' to activate Monitors and Tallies at a
     93                     specified time (e.g. after warmup period)
     94                   - Moved all test / demo programs to after 'if __name__ == '__main__':'.
     95
     96      17 Oct 2006: - Added compound 'put' and 'get' statements for Level and Store.
     97
     98      18 Oct 2006: - Repaired bug: self.eventsFired now gets set after an event fires
     99                     in a compound yield get / put with a waitevent clause (reneging case).
    100
    101      21 Oct 2006: - Introduced Store 'yield get' with a filter function.
    102
    103      22 Oct 2006: - Repaired bug in prettyprinting of Store objects (the buffer
    104                     content==._theBuffer was not shown) by changing ._theBuffer
    105                     to .theBuffer.
    106
    107      04 Dec 2006: - Added printHistogram method to Tally and Monitor (generates
    108                     table - form histogram)
    109
    110      07 Dec 2006: - Changed the __str__ method of Histogram to print a table
    111                     (like printHistogram).
    112
    113      18 Dec 2006: - Added trace printing of Buffers' 'unitName' for yield get and put.
    114
    115      09 Jun 2007: - Cleaned out all uses of 'object' to prevent name clash.
    116
    117      18 Nov 2007: - Start of 1.9 development
    118                   - Added 'start' method (alternative to activate) to Process
    119
    120      22 Nov 2007: - Major change to event list handling to speed up larger models:
    121                      * Drop dictionary
    122                      * Replace bisect by heapq
    123                      * Mark cancelled event notices in unpost and skip them in
    124                        nextev (great idea of Tony Vignaux))
    125
    126      4 Dec 2007: - Added twVariance calculation for both Monitor and Tally (gav)
    127
    128      5 Dec 2007: - Changed name back to timeVariance (gav)
    129
    130      1 Mar 2008: - Start of 1.9.1 bugfix release
    131                  - Delete circular reference in Process instances when event
    132                    notice has been processed (caused much circular garbage)
    133
    134      2 Apr 2008: - Repair of wallclock synchronisation algorithm
    135
    136      10 Aug 2008: - Renamed __Evlist to EvlistRT and let it inherit from
    137                     Evlist (from Simulation.py) (Stefan Scherfke)
    138                   - New class SimulationRT contains the old method simulate
    139                   - Removed everything else and imported it from Simulation.py
    140
    141      19 Mar 2009: - Repair of wallclock synchronisation algorithm (again)
    142
    143  """
    144  import time
    145
    146  from SimPy.Simulation import *
    147
    148
    149  __TESTING = False
    150  version = __version__ = '2.0 $Revision: 271 $ $Date: 2009-03-25 13:27:06 +0100 (Mi, 25 Mrz 2009) $'
    151  if __TESTING:
    152      print 'SimPy.SimulationRT %s' %__version__,
    153      if __debug__:
    154          print '__debug__ on'
    155      else:
    156          print
    157
    158
    159 -class EvlistRT(Evlist):
    160      """Defines event list and operations on it"""
    161 -    def __init__(self, sim):
    162          # always sorted list of events (sorted by time, priority)
    163          # make heapq
    164          self.sim = sim
    165          self.timestamps = []
    166          self.sortpr = 0
    167          self.real_time = False
    168          self.rel_speed = 1
    169          self.rtlast = self.sim.wallclock()
    170          self.stlast = 0
    171
    172 -    def _nextev(self):
    173          """Retrieve next event from event list"""
    174          noActiveNotice = True
    175          ## Find next event notice which is not marked cancelled
    176          while noActiveNotice:
    177              if self.timestamps:
    178                   ## ignore priority value
    179                  (_tnotice, p,nextEvent, cancelled) = hq.heappop(self.timestamps)
    180                  noActiveNotice = cancelled
    181              else:
    182                  raise Simerror('No more events at time %s' % self.sim._t)
    183          nextEvent._rec = None
    184          _tsimold = self.sim._t
    185          self.sim._t = _tnotice
    186          ## Calculate any wait time
    187          ## (tRTnow - tRTold + delay)/(tsim - tsimold) = 1/rel_speed
    188          if self.real_time:
    189              delay = float(_tnotice-_tsimold)/self.rel_speed-self.sim.rtnow()+self.rtlast
    190              if delay > 0:
    191                  time.sleep(delay)
    192              self.rtlast = self.sim.wallclock()-self.sim.rtstart
    193              self.stlast = self.sim._t
    194          if self.sim._t > self.sim._endtime:
    195              self.sim._t = self.sim._endtime
    196              self.sim._stop = True
    197              return (None,)
    198          try:
    199              resultTuple = nextEvent._nextpoint.next()
    200          except StopIteration:
    201              nextEvent._nextpoint = None
    202              nextEvent._terminated = True
    203              nextEvent._nextTime = None
    204              resultTuple = None
    205          return (resultTuple, nextEvent)
    206
    207
    208 -class SimulationRT(Simulation):
    209
    210 -    def __init__(self):
    211          if sys.platform == 'win32':  #take care of differences in clock accuracy
    212              self.wallclock = time.clock
    213          else:
    214              self.wallclock = time.time
    215          self.rtstart = self.wallclock()
    216          Simulation.__init__(self)
    217          self.initialize()
    218
    219 -    def initialize(self):
    220          self.rtstart = self.wallclock()
    221          Simulation.initialize(self)
    222          self._e = EvlistRT(self)
    223          self._e.rtlast = 0
    224
    225 -    def rtnow(self):
    226          return self.wallclock() - self.rtstart
    227
    228 -    def rtset(self, rel_speed = 1):
    229          """resets the the ratio simulation time over clock time(seconds).
    230          """
    231          if self._e is None:
    232              raise FatalSimerror('Fatal SimPy error: Simulation not initialized')
    233          self._e.rel_speed = float(rel_speed)
    234
    235 -    def simulate(self, until = 0, real_time = False, rel_speed = 1):
    236          """Schedules Processes / semi - coroutines until time 'until'"""
    237
    238          """Gets called once. Afterwards, co - routines (generators) return by
    239          'yield' with a cargo:
    240          yield hold, self, <delay>: schedules the 'self' process for activation
    241                                     after < delay > time units.If <,delay > missing,
    242                                     same as 'yield hold, self, 0'
    243
    244          yield passivate, self    :  makes the 'self' process wait to be re - activated
    245
    246          yield request, self,<Resource > [,<priority>]: request 1 unit from < Resource>
    247              with < priority > pos integer (default = 0)
    248
    249          yield release, self,<Resource> : release 1 unit to < Resource>
    250
    251          yield waitevent, self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]:
    252              wait for one or more of several events
    253
    254
    255          yield queueevent, self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]:
    256              queue for one or more of several events
    257
    258          yield waituntil, self, cond : wait for arbitrary condition
    259
    260          yield get, self,<buffer > [,<WhatToGet > [,<priority>]]
    261              get < WhatToGet > items from buffer (default = 1);
    262              <WhatToGet > can be a pos integer or a filter function
    263              (Store only)
    264
    265          yield put, self,<buffer > [,<WhatToPut > [,priority]]
    266              put < WhatToPut > items into buffer (default = 1);
    267              <WhatToPut > can be a pos integer (Level) or a list of objects
    268              (Store)
    269
    270          EXTENSIONS:
    271          Request with timeout reneging:
    272          yield (request, self,<Resource>),(hold, self,<patience>) :
    273              requests 1 unit from < Resource>. If unit not acquired in time period
    274              <patience>, self leaves waitQ (reneges).
    275
    276          Request with event - based reneging:
    277          yield (request, self,<Resource>),(waitevent, self,<eventlist>):
    278              requests 1 unit from < Resource>. If one of the events in < eventlist > occurs before unit
    279              acquired, self leaves waitQ (reneges).
    280
    281          Get with timeout reneging (for Store and Level):
    282          yield (get, self,<buffer>,nrToGet etc.),(hold, self,<patience>)
    283              requests < nrToGet > items / units from < buffer>. If not acquired < nrToGet > in time period
    284              <patience>, self leaves < buffer>.getQ (reneges).
    285
    286          Get with event - based reneging (for Store and Level):
    287          yield (get, self,<buffer>,nrToGet etc.),(waitevent, self,<eventlist>)
    288              requests < nrToGet > items / units from < buffer>. If not acquired < nrToGet > before one of
    289              the events in < eventlist > occurs, self leaves < buffer>.getQ (reneges).
    290
    291
    292
    293          Event notices get posted in event - list by scheduler after 'yield' or by
    294          'activate' / 'reactivate' functions.
    295
    296          if real_time == True, the simulation time and real (clock) time get
    297          synchronized as much as possible. rel_speed is the ratio simulation time
    298          over clock time(seconds). Example: rel_speed == 100: 100 simulation time units take
    299          1 second clock time.
    300
    301          """
    302          global _endtime, _e, _stop, _t, _wustep
    303          self._stop = False
    304
    305          if self._e is None:
    306              raise FatalSimerror('Simulation not initialized')
    307          self._e.real_time = real_time
    308          self._e.rel_speed = rel_speed
    309          #~ self._e.rtlast = self.wallclock()
    310          #~ self._e.stlast = 0
    311          if self._e._isEmpty():
    312              message = 'SimPy: No activities scheduled'
    313              return message
    314
    315          self._endtime = until
    316          message = 'SimPy: Normal exit'
    317          dispatch={hold:holdfunc, request:requestfunc, release:releasefunc,
    318                    passivate:passivatefunc, waitevent:waitevfunc, queueevent:queueevfunc,
    319                    waituntil:waituntilfunc, get:getfunc, put:putfunc}
    320          commandcodes = dispatch.keys()
    321          commandwords={hold:'hold', request:'request', release:'release', passivate:'passivate',
    322              waitevent:'waitevent', queueevent:'queueevent', waituntil:'waituntil',
    323              get:'get', put:'put'}
    324          nextev = self._e._nextev ## just a timesaver
    325          while not self._stop and self._t <= self._endtime:
    326              try:
    327                  a = nextev()
    328                  if not a[0] is None:
    329                      ## 'a' is tuple '(<yield command>, <action>)'
    330                      if type(a[0][0]) == tuple:
    331                          ##allowing for yield (request, self, res),(waituntil, self, cond)
    332                          command = a[0][0][0]
    333                      else:
    334                          command = a[0][0]
    335                      if __debug__:
    336                          if not command in commandcodes:
    337                              raise FatalSimerror('Illegal command: yield %s'%command)
    338                      dispatch[command](a)
    339              except FatalSimerror, error:
    340                  print 'SimPy: ' + error.value
    341                  sys.exit(1)
    342              except Simerror, error:
    343                  message = 'SimPy: ' + error.value
    344                  self._stop = True
    345              if self._wustep:
    346                  self._test()
    347          self._stopWUStepping()
    348          self._e = None
    349          return message
    350
    351  # For backward compatibility
    352  Globals.sim = SimulationRT()
    353
    354 -def rtnow():
    355      return Globals.sim.rtnow()
    356
    357  rtset =  Globals.sim.rtset
    358
    359 -def simulate(until = 0, real_time = False, rel_speed = 1):
    360      return Globals.sim.simulate(until = until, real_time = real_time, rel_speed = rel_speed)
    361
    362  wallclock = Globals.sim.wallclock
    363  # End backward compatibility
    364
    365  if __name__ == '__main__':
    366      print 'SimPy.SimulationRT %s' %__version__
    367      ############# Test / demo functions #############
    368 -    def test_demo():
    369          class Aa(Process):
    370              sequIn = []
    371              sequOut = []
    372              def __init__(self, holdtime, name):
    373                  Process.__init__(self, name)
    374                  self.holdtime = holdtime
    375
    376              def life(self, priority):
    377                  for i in range(1):
    378                      Aa.sequIn.append(self.name)
    379                      print now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
    380                            len(rrr.activeQ)
    381                      print 'waitQ: ',[(k.name, k._priority[rrr]) for k in rrr.waitQ]
    382                      print 'activeQ: ',[(k.name, k._priority[rrr]) \
    383                                 for k in rrr.activeQ]
    384                      assert rrr.n + len(rrr.activeQ) == rrr.capacity, \
    385                     'Inconsistent resource unit numbers'
    386                      print now(),self.name, 'requests 1 ', rrr.unitName
    387                      yield request, self, rrr, priority
    388                      print now(),self.name, 'has 1 ', rrr.unitName
    389                      print now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
    390                            len(rrr.activeQ)
    391                      print now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
    392                            len(rrr.activeQ)
    393                      assert rrr.n + len(rrr.activeQ) == rrr.capacity, \
    394                     'Inconsistent resource unit numbers'
    395                      yield hold, self, self.holdtime
    396                      print now(),self.name, 'gives up 1', rrr.unitName
    397                      yield release, self, rrr
    398                      Aa.sequOut.append(self.name)
    399                      print now(),self.name, 'has released 1 ', rrr.unitName
    400                      print 'waitQ: ',[(k.name, k._priority[rrr]) for k in rrr.waitQ]
    401                      print now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
    402                            len(rrr.activeQ)
    403                      assert rrr.n + len(rrr.activeQ) == rrr.capacity, \
    404                             'Inconsistent resource unit numbers'
    405
    406          class Observer(Process):
    407              def __init__(self):
    408                  Process.__init__(self)
    409
    410              def observe(self, step, processes, res):
    411                  while now() < 11:
    412                      for i in processes:
    413                          print ' %s %s: act:%s, pass:%s, term: %s, interr:%s, qu:%s'\
    414                                %(now(),i.name, i.active(),i.passive(),i.terminated()\
    415                              ,i.interrupted(),i.queuing(res))
    416                      print
    417                      yield hold, self, step
    418
    419          print'\n+++test_demo output'
    420          print '****First case == priority queue, resource service not preemptable'
    421          initialize()
    422          rrr = Resource(5, name = 'Parking', unitName = 'space(s)', qType = PriorityQ,
    423                       preemptable = 0)
    424          procs = []
    425          for i in range(10):
    426              z = Aa(holdtime = i, name = 'Car ' + str(i))
    427              procs.append(z)
    428              activate(z, z.life(priority = i))
    429          o = Observer()
    430          activate(o, o.observe(1, procs, rrr))
    431          a = simulate(until = 10000, real_time = True, rel_speed = 1)
    432          print a
    433          print 'Input sequence: ', Aa.sequIn
    434          print 'Output sequence: ', Aa.sequOut
    435
    436          print '\n****Second case == priority queue, resource service preemptable'
    437          initialize()
    438          rrr = Resource(5, name = 'Parking', unitName = 'space(s)', qType = PriorityQ,
    439                       preemptable = 1)
    440          procs = []
    441          for i in range(10):
    442              z = Aa(holdtime = i, name = 'Car ' + str(i))
    443              procs.append(z)
    444              activate(z, z.life(priority = i))
    445          o = Observer()
    446          activate(o, o.observe(1, procs, rrr))
    447          Aa.sequIn = []
    448          Aa.sequOut = []
    449          a = simulate(until = 10000)
    450          print a
    451          print 'Input sequence: ', Aa.sequIn
    452          print 'Output sequence: ', Aa.sequOut
    453
    454 -    def test_interrupt():
    455          class Bus(Process):
    456              def __init__(self, name):
    457                  Process.__init__(self, name)
    458
    459              def operate(self, repairduration = 0):
    460                  print now(),rtnow(),'>> %s starts' % (self.name)
    461                  tripleft = 1000
    462                  while tripleft > 0:
    463                      yield hold, self, tripleft
    464                      if self.interrupted():
    465                          print 'interrupted by %s' %self.interruptCause.name
    466                          print '%s(%s): %s breaks down ' %(now(),rtnow(),self.name)
    467                          tripleft = self.interruptLeft
    468                          self.interruptReset()
    469                          print 'tripleft ', tripleft
    470                          reactivate(br, delay = repairduration) # breakdowns only during operation
    471                          yield hold, self, repairduration
    472                          print now(),rtnow(),' repaired'
    473                      else:
    474                          break # no breakdown, ergo bus arrived
    475                  print now(),'<< %s done' % (self.name)
    476
    477          class Breakdown(Process):
    478              def __init__(self, myBus):
    479                  Process.__init__(self, name = 'Breakdown ' + myBus.name)
    480                  self.bus = myBus
    481
    482              def breakBus(self, interval):
    483
    484                  while True:
    485                      yield hold, self, interval
    486                      if self.bus.terminated(): break
    487                      self.interrupt(self.bus)
    488
    489          print'\n\n+++test_interrupt'
    490          initialize()
    491          b = Bus('Bus 1')
    492          activate(b, b.operate(repairduration = 20))
    493          br = Breakdown(b)
    494          activate(br, br.breakBus(200))
    495          print simulate(until = 4000, real_time = True, rel_speed = 200)
    496
    497 -    def testSimEvents():
    498          class Waiter(Process):
    499              def waiting(self, theSignal):
    500                  while True:
    501                      yield waitevent, self, theSignal
    502                      print '%s: process \'%s\' continued after waiting for %s' % (now(),self.name, theSignal.name)
    503                      yield queueevent, self, theSignal
    504                      print '%s: process \'%s\' continued after queueing for %s' % (now(),self.name, theSignal.name)
    505
    506          class ORWaiter(Process):
    507              def waiting(self, signals):
    508                  while True:
    509                      yield waitevent, self, signals
    510                      print now(),'one of %s signals occurred' % [x.name for x in signals]
    511                      print '\t%s (fired / param)'%[(x.name, x.signalparam) for x in self.eventsFired]
    512                      yield hold, self, 1
    513
    514          class Caller(Process):
    515              def calling(self):
    516                  while True:
    517                      signal1.signal('wake up!')
    518                      print '%s: signal 1 has occurred'%now()
    519                      yield hold, self, 10
    520                      signal2.signal('and again')
    521                      signal2.signal('sig 2 again')
    522                      print '%s: signal1, signal2 have occurred'%now()
    523                      yield hold, self, 10
    524          print'\n\n+++testSimEvents output'
    525          initialize()
    526          signal1 = SimEvent('signal 1')
    527          signal2 = SimEvent('signal 2')
    528          signal1.signal('startup1')
    529          signal2.signal('startup2')
    530          w1 = Waiter('waiting for signal 1')
    531          activate(w1, w1.waiting(signal1))
    532          w2 = Waiter('waiting for signal 2')
    533          activate(w2, w2.waiting(signal2))
    534          w3 = Waiter('also waiting for signal 2')
    535          activate(w3, w3.waiting(signal2))
    536          w4 = ORWaiter('waiting for either signal 1 or signal 2')
    537          activate(w4, w4.waiting([signal1, signal2]),prior = True)
    538          c = Caller('Caller')
    539          activate(c, c.calling())
    540          print simulate(until = 100)
    541
    542 -    def testwaituntil():
    543          """
    544          Demo of waitUntil capability.
    545
    546          Scenario:
    547          Three workers require sets of tools to do their jobs. Tools are shared, scarce
    548          resources for which they compete.
    549          """
    550
    551
    552          class Worker(Process):
    553              def __init__(self, name, heNeeds = []):
    554                  Process.__init__(self, name)
    555                  self.heNeeds = heNeeds
    556              def work(self):
    557
    558                  def workerNeeds():
    559                      for item in self.heNeeds:
    560                          if item.n == 0:
    561                              return False
    562                      return True
    563
    564                  while now() < 8 * 60:
    565                      yield waituntil, self, workerNeeds
    566                      for item in self.heNeeds:
    567                          yield request, self, item
    568                      print '%s %s has %s and starts job' % (now(),self.name,
    569                          [x.name for x in self.heNeeds])
    570                      yield hold, self, random.uniform(10, 30)
    571                      for item in self.heNeeds:
    572                          yield release, self, item
    573                      yield hold, self, 2 #rest
    574
    575          print '\n+++\nwaituntil demo output'
    576          initialize()
    577          brush = Resource(capacity = 1, name = 'brush')
    578          ladder = Resource(capacity = 2, name = 'ladder')
    579          hammer = Resource(capacity = 1, name = 'hammer')
    580          saw = Resource(capacity = 1, name = 'saw')
    581          painter = Worker('painter',[brush, ladder])
    582          activate(painter, painter.work())
    583          roofer = Worker('roofer',[hammer, ladder, ladder])
    584          activate(roofer, roofer.work())
    585          treeguy = Worker('treeguy',[saw, ladder])
    586          activate(treeguy, treeguy.work())
    587          for who in (painter, roofer, treeguy):
    588              print '%s needs %s for his job' % (who.name,[x.name for x in who.heNeeds])
    589          print
    590          print simulate(until = 9 * 60)
    591      test_demo()
    592      # Run tests
    593      test_interrupt()
    594      #testSimEvents()
    595      #testwaituntil()
    596