Package SimPy :: Module Simulation

[hide private]
[frames] | no frames]

# Source Code for Module SimPy.Simulation

       1  #!/usr / bin / env python
       2  # coding=utf-8
       3  # $Revision: 484 $ $Date: 2008-09-10 17:25:13 +0200 (Mi, 10 Sep 2008)
       4  """Simulation 2.1 Implements SimPy Processes, Resources, Buffers, and the backbone simulation
       5  scheduling by coroutine calls. Provides data collection through classes
       6  Monitor and Tally.
       7  Based on generators (Python 2.3 and later; not 3.0)
       8
       9  LICENSE:
      10  Copyright (C) 2002, 2005, 2006, 2007, 2008, 2009, 2010  Klaus G. Muller, Tony Vignaux
      11  mailto: kgmuller at xs4all.nl and Tony.Vignaux at vuw.ac.nz
      12
      13      This library is free software; you can redistribute it and / or
      14      modify it under the terms of the GNU Lesser General Public
      15      License as published by the Free Software Foundation; either
      16      version 2.1 of the License, or (at your option) any later version.
      17
      18      This library is distributed in the hope that it will be useful,
      19      but WITHOUT ANY WARRANTY; without even the implied warranty of
      20      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      21      Lesser General Public License for more details.
      22
      23      You should have received a copy of the GNU Lesser General Public
      24      License along with this library; if not, write to the Free Software
      25      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111 - 1307  USA
      26  END OF LICENSE
      27
      28  """
      29
      30  import random
      31  import sys
      32  import types
      33  from heapq import heappush, heappop
      34
      35  from SimPy.Lister import Lister
      36  from SimPy.Recording import Monitor, Tally
      37  from SimPy.Lib import Process, SimEvent, PriorityQ, Resource, Level, \
      38                        Store, Simerror, FatalSimerror
      39
      40  # Required for backward compatibility
      41  import SimPy.Globals as Globals
      42  from SimPy.Globals import initialize, simulate, now, stopSimulation, \
      43          allEventNotices, allEventTimes, startCollection,\
      44          _startWUStepping, _stopWUStepping, activate, reactivate
      45
      46
      47  __TESTING = False
      48  version = __version__ = '2.1 $Revision: 484 $ $Date: 2010-05-10 09:18:06 +0200 (Mon, 10 May 2010) $'
      49  if __TESTING:
      50      print 'SimPy.Simulation %s' %__version__,
      51      if __debug__:
      52          print '__debug__ on'
      53      else:
      54          print
      55
      56  # yield keywords
      57  hold = 1
      58  passivate = 2
      59  request = 3
      60  release = 4
      61  waitevent = 5
      62  queueevent = 6
      63  waituntil = 7
      64  get = 8
      65  put = 9
      66
      67
      68 -class Infinity(object):
      69 -    def __cmp__(self, other):
      70          return 1
      71
      72  infinity = Infinity()
      73
      74 -def holdfunc(a):
      75      a[0][1]._hold(a)
      76
      77 -def requestfunc(a):
      78      """Handles 'yield request, self, res' and
      79      'yield (request, self, res),(<code>,self, par)'.
      80      <code > can be 'hold' or 'waitevent'.
      81      """
      82      if type(a[0][0]) == tuple:
      83          ## Compound yield request statement
      84          ## first tuple in ((request, self, res),(xx, self, yy))
      85          b = a[0][0]
      86          ## b[2] == res (the resource requested)
      87          ##process the first part of the compound yield statement
      88          ##a[1] is the Process instance
      89          b[2]._request(arg = (b, a[1]))
      90          ##deal with add - on condition to command
      91          ##Trigger processes for reneging
      92          class _Holder(Process):
      93              """Provides timeout process"""
      94              def __init__(self,name,sim=None):
      95                  Process.__init__(self,name=name,sim=sim)
      96              def trigger(self, delay):
      97                  yield hold, self, delay
      98                  if not proc in b[2].activeQ:
      99                      proc.sim.reactivate(proc)
     100
     101          class _EventWait(Process):
     102              """Provides event waiting process"""
     103              def __init__(self,name,sim=None):
     104                  Process.__init__(self,name=name,sim=sim)
     105              def trigger(self, event):
     106                  yield waitevent, self, event
     107                  if not proc in b[2].activeQ:
     108                      proc.eventsFired = self.eventsFired
     109                      proc.sim.reactivate(proc)
     110
     111          #activate it
     112          proc = a[0][0][1] # the process to be woken up
     113          actCode = a[0][1][0]
     114          if actCode == hold:
     115              proc._holder = _Holder(name = 'RENEGE - hold for %s'%proc.name,
     116                                     sim=proc.sim)
     117              ##                                          the timeout delay
     118              proc.sim.activate(proc._holder, proc._holder.trigger(a[0][1][2]))
     119          elif actCode == waituntil:
     120              raise FatalSimerror('Illegal code for reneging: waituntil')
     121          elif actCode == waitevent:
     122              proc._holder = _EventWait(name = 'RENEGE - waitevent for %s'\
     123                                        %proc.name,sim=proc.sim)
     124              ##                                          the event
     125              proc.sim.activate(proc._holder, proc._holder.trigger(a[0][1][2]))
     126          elif actCode == queueevent:
     127              raise FatalSimerror('Illegal code for reneging: queueevent')
     128          else:
     129              raise FatalSimerror('Illegal code for reneging %s'%actCode)
     130      else:
     131          ## Simple yield request command
     132          a[0][2]._request(a)
     133
     134 -def releasefunc(a):
     135      a[0][2]._release(a)
     136
     137 -def passivatefunc(a):
     138      a[0][1]._passivate(a)
     139
     140 -def waitevfunc(a):
     141      #if waiting for one event only (not a tuple or list)
     142      evtpar = a[0][2]
     143      if isinstance(evtpar, SimEvent):
     144          a[0][2]._wait(a)
     145      # else, if waiting for an OR of events (list / tuple):
     146      else: #it should be a list / tuple of events
     147          # call _waitOR for first event
     148          evtpar[0]._waitOR(a)
     149
     150 -def queueevfunc(a):
     151      #if queueing for one event only (not a tuple or list)
     152      evtpar = a[0][2]
     153      if isinstance(evtpar, SimEvent):
     154          a[0][2]._queue(a)
     155      #else, if queueing for an OR of events (list / tuple):
     156      else: #it should be a list / tuple of events
     157          # call _queueOR for first event
     158          evtpar[0]._queueOR(a)
     159
     160 -def waituntilfunc(par):
     161      par[0][1].sim._waitUntilFunc(par[0][1], par[0][2])
     162
     163 -def getfunc(a):
     164      """Handles 'yield get, self, buffer, what, priority' and
     165      'yield (get, self, buffer, what, priority),(<code>,self, par)'.
     166      <code > can be 'hold' or 'waitevent'.
     167      """
     168      if type(a[0][0]) == tuple:
     169          ## Compound yield request statement
     170          ## first tuple in ((request, self, res),(xx, self, yy))
     171          b = a[0][0]
     172          ## b[2] == res (the resource requested)
     173          ##process the first part of the compound yield statement
     174          ##a[1] is the Process instance
     175          b[2]._get(arg = (b, a[1]))
     176          ##deal with add - on condition to command
     177          ##Trigger processes for reneging
     178          class _Holder(Process):
     179              """Provides timeout process"""
     180              def __init__(self,**par):
     181                  Process.__init__(self,**par)
     182              def trigger(self, delay):
     183                  yield hold, self, delay
     184                  #if not proc in b[2].activeQ:
     185                  if proc in b[2].getQ:
     186                      a[1].sim.reactivate(proc)
     187
     188          class _EventWait(Process):
     189              """Provides event waiting process"""
     190              def __init__(self,**par):
     191                  Process.__init__(self,**par)
     192              def trigger(self, event):
     193                  yield waitevent, self, event
     194                  if proc in b[2].getQ:
     195                      a[1].eventsFired = self.eventsFired
     196                      a[1].sim.reactivate(proc)
     197
     198          #activate it
     199          proc = a[0][0][1] # the process to be woken up
     200          actCode = a[0][1][0]
     201          if actCode == hold:
     202              proc._holder = _Holder(name='RENEGE - hold for %s'%proc.name,
     203                                     sim=proc.sim)
     204              ##                                          the timeout delay
     205              a[1].sim.activate(proc._holder, proc._holder.trigger(a[0][1][2]))
     206          elif actCode == waituntil:
     207              raise FatalSimerror('waituntil: Illegal code for reneging: waituntil')
     208          elif actCode == waitevent:
     209              proc._holder = _EventWait(name="RENEGE - waitevent for%s"\
     210                                        %proc.name,sim=proc.sim)
     211              ##                                          the event
     212              a[1].sim.activate(proc._holder, proc._holder.trigger(a[0][1][2]))
     213          elif actCode == queueevent:
     214              raise FatalSimerror('Illegal code for reneging: queueevent')
     215          else:
     216              raise FatalSimerror('Illegal code for reneging %s'%actCode)
     217      else:
     218          ## Simple yield request command
     219          a[0][2]._get(a)
     220
     221
     222 -def putfunc(a):
     223      """Handles 'yield put' (simple and compound hold / waitevent)
     224      """
     225      if type(a[0][0]) == tuple:
     226          ## Compound yield request statement
     227          ## first tuple in ((request, self, res),(xx, self, yy))
     228          b = a[0][0]
     229          ## b[2] == res (the resource requested)
     230          ##process the first part of the compound yield statement
     231          ##a[1] is the Process instance
     232          b[2]._put(arg = (b, a[1]))
     233          ##deal with add - on condition to command
     234          ##Trigger processes for reneging
     235          class _Holder(Process):
     236              """Provides timeout process"""
     237              def __init__(self,**par):
     238                  Process.__init__(self,**par)
     239              def trigger(self, delay):
     240                  yield hold, self, delay
     241                  #if not proc in b[2].activeQ:
     242                  if proc in b[2].putQ:
     243                      a[1].sim.reactivate(proc)
     244
     245          class _EventWait(Process):
     246              """Provides event waiting process"""
     247              def __init__(self,**par):
     248                  Process.__init__(self,**par)
     249              def trigger(self, event):
     250                  yield waitevent, self, event
     251                  if proc in b[2].putQ:
     252                      a[1].eventsFired = self.eventsFired
     253                      a[1].sim.reactivate(proc)
     254
     255          #activate it
     256          proc = a[0][0][1] # the process to be woken up
     257          actCode = a[0][1][0]
     258          if actCode == hold:
     259              proc._holder = _Holder(name='RENEGE - hold for %s'%proc.name,
     260                                     sim=proc.sim)
     261              ##                                          the timeout delay
     262              a[1].sim.activate(proc._holder, proc._holder.trigger(a[0][1][2]))
     263          elif actCode == waituntil:
     264              raise FatalSimerror('Illegal code for reneging: waituntil')
     265          elif actCode == waitevent:
     266              proc._holder = _EventWait(name='RENEGE - waitevent for %s'\
     267                                        %proc.name,sim=proc.sim)
     268              ##                                          the event
     269              a[1].sim.activate(proc._holder, proc._holder.trigger(a[0][1][2]))
     270          elif actCode == queueevent:
     271              raise FatalSimerror('Illegal code for reneging: queueevent')
     272          else:
     273              raise FatalSimerror('Illegal code for reneging %s'%actCode)
     274      else:
     275          ## Simple yield request command
     276          a[0][2]._put(a)
     277
     278
     279 -class Simulation(object):
     280      _dispatch = {
     281              hold: holdfunc, request: requestfunc, release: releasefunc,
     282              passivate: passivatefunc, waitevent: waitevfunc,
     283              queueevent: queueevfunc, waituntil: waituntilfunc, get: getfunc,
     284              put: putfunc,
     285      }
     286      _commandcodes = _dispatch.keys()
     287      _commandwords = {
     288              hold: 'hold', request: 'request', release: 'release',
     289              passivate: 'passivate', waitevent: 'waitevent',
     290              queueevent: 'queueevent', waituntil: 'waituntil', get: 'get',
     291              put: 'put'
     292      }
     293
     294 -    def __init__(self):
     295          self.initialize()
     296
     297 -    def initialize(self):
     298          self._t = 0
     299          self.next_time = 0
     300
     301          # Eventqueue stuff.
     302          self._timestamps = []
     303          self._sortpr = 0
     304
     305          self._start = False
     306          self._stop = False
     307          self.condQ = []
     308          self.allMonitors = []
     309          self.allTallies = []
     310
     311 -    def now(self):
     312          return self._t
     313
     314 -    def stopSimulation(self):
     315          """Application function to stop simulation run"""
     316          self._stop = True
     317
     318 -    def _post(self, what, at, prior = False):
     319          """Post an event notice for process what for time at"""
     320          # event notices are Process instances
     321          if at < self._t:
     322              raise FatalSimerror('Attempt to schedule event in the past')
     323          what._nextTime = at
     324          self._sortpr -= 1
     325          if prior:
     326              # before all other event notices at this time
     327              # heappush with highest priority value so far (negative of
     328              # monotonely decreasing number)
     329              # store event notice in process instance
     330              what._rec = [at, self._sortpr, what, False]
     331              # make event list refer to it
     332              heappush(self._timestamps, what._rec)
     333          else:
     334              # heappush with lowest priority
     335              # store event notice in process instance
     336              what._rec = [at,-self._sortpr, what, False]
     337              # make event list refer to it
     338              heappush(self._timestamps, what._rec)
     339
     340 -    def _unpost(self, whom):
     341          """
     342          Mark event notice for whom as cancelled if whom is a suspended process
     343          """
     344          if whom._nextTime is not None:  # check if whom was actually active
     345              whom._rec[3] = True ## Mark as cancelled
     346              whom._nextTime = None
     347
     348 -    def allEventNotices(self):
     349          """Returns string with eventlist as;
     350                  t1: processname, processname2
     351                  t2: processname4, processname5, . . .
     352                  . . .  .
     353          """
     354          ret = ''
     355          tempList = []
     356          tempList[:] = self._timestamps
     357          tempList.sort()
     358          # return only event notices which are not cancelled
     359          tempList = [[x[0],x[2].name] for x in tempList if not x[3]]
     360          tprev = -1
     361          for t in tempList:
     362              # if new time, new line
     363              if t[0] == tprev:
     364                  # continue line
     365                  ret += ', %s'%t[1]
     366              else:
     367                  # new time
     368                  if tprev == -1:
     369                      ret = '%s: %s' % (t[0],t[1])
     370                  else:
     371                      ret += '\n%s: %s' % (t[0],t[1])
     372                  tprev = t[0]
     373          return ret + '\n'
     374
     375 -    def allEventTimes(self):
     376          """Returns list of all times for which events are scheduled.
     377          """
     378          r = []
     379          r[:] = self._timestamps
     380          r.sort()
     381          # return only event times of not cancelled event notices
     382          r1 = [x[0] for x in r if not x[3]]
     383          tprev = -1
     384          ret = []
     385          for t in r1:
     386              if t == tprev:
     387                  #skip time, already in list
     388                  pass
     389              else:
     390                  ret.append(t)
     391                  tprev = t
     392          return ret
     393
     394 -    def activate(self, obj, process, at = 'undefined', delay = 'undefined',
     395                   prior = False):
     396          """Application function to activate passive process."""
     397          if __debug__:
     398              if not (obj.sim == self):
     399                  txt="activate: Process %s not in activating Simulation instance"\
     400                      %obj.name
     401                  raise FatalSimerror(txt)
     402              if not (type(process) == types.GeneratorType):
     403                  raise FatalSimerror('Activating function which'+
     404                      ' is not a generator (contains no \'yield\')')
     405          if not obj._terminated and not obj._nextTime:
     406              #store generator reference in object; needed for reactivation
     407              obj._nextpoint = process
     408              if at == 'undefined':
     409                  at = self._t
     410              if delay == 'undefined':
     411                  zeit = max(self._t, at)
     412              else:
     413                  zeit = max(self._t, self._t + delay)
     414              self._post(obj, at = zeit, prior = prior)
     415
     416 -    def reactivate(self, obj, at = 'undefined', delay = 'undefined',
     417                     prior = False):
     418          """Application function to reactivate a process which is active,
     419          suspended or passive."""
     420          # Object may be active, suspended or passive
     421          if not obj._terminated:
     422              a = Process('SimPysystem',sim=self)
     423              a.cancel(obj)
     424              # object now passive
     425              if at == 'undefined':
     426                  at = self._t
     427              if delay == 'undefined':
     428                  zeit = max(self._t, at)
     429              else:
     430                  zeit = max(self._t, self._t + delay)
     431              self._post(obj, at = zeit, prior = prior)
     432
     433 -    def startCollection(self, when = 0.0, monitors = None, tallies = None):
     434          """Starts data collection of all designated Monitor and Tally objects
     435          (default = all) at time 'when'.
     436          """
     437          class Starter(Process):
     438              def collect(self, monitors, tallies):
     439                  for m in monitors:
     440                      m.reset()
     441                  for t in tallies:
     442                      t.reset()
     443                  yield hold, self
     444          if monitors is None:
     445              monitors = self.allMonitors
     446          if tallies is None:
     447              tallies = self.allTallies
     448          if when == 0.0:
     449              for m in monitors:
     450                  try:
     451                      ylast = m[-1][1]
     452                      empty = False
     453                  except IndexError:
     454                      empty = True
     455                  m.reset()
     456                  if not empty:
     457                      m.observe(t = now(), y = ylast)
     458              for t in tallies:
     459                  t.reset()
     460          else:
     461              s = Starter(sim = self)
     462              self.activate(s, s.collect(monitors = monitors, tallies = tallies),\
     463                        at = when, prior = True)
     464
     465
     466 -    def _waitUntilFunc(self, proc, cond):
     467          """
     468          Puts a process 'proc' waiting for a condition into a waiting queue.
     469          'cond' is a predicate function which returns True if the condition is
     470          satisfied.
     471          """
     472          if not cond():
     473              self.condQ.append(proc)
     474              proc.cond = cond
     475              # passivate calling process
     476              proc._nextTime = None
     477          else:
     478              #schedule continuation of calling process
     479              self._post(proc, at = self._t, prior = 1)
     480
     481 -    def _terminate(self, process):
     482          """Marks a process as terminated."""
     483          process._nextpoint = None
     484          process._terminated = True
     485          process._nextTime = None
     486
     487 -    def has_events(self):
     488          """
     489          Checks if there are events which can be processed. Returns ``True`` if
     490          there are events and the simulation has not been stopped.
     491          """
     492          return not self._stop and self._timestamps
     493
     494 -    def peek(self):
     495          """
     496          Returns the time of the next event or infinity, if no
     497          more events are scheduled.
     498          """
     499          if not self._timestamps:
     500              return infinity
     501          else:
     502              return self._timestamps[0][0]
     503
     504 -    def step(self):
     505          """
     506          Executes the next uncancelled event in the eventqueue.
     507          """
     508
     509          # Fetch next process and advance its process execution method.
     510          noActiveNotice = True
     511          # Get an uncancelled event
     512          while noActiveNotice:
     513              if self._timestamps:
     514                  _tnotice, p, proc, cancelled = heappop(self._timestamps)
     515                  noActiveNotice = cancelled
     516              else:
     517                  return None
     518
     519          # Advance simulation time.
     520          proc._rec = None
     521          self._t = _tnotice
     522
     523          # Execute the event. This will advance the process execution method.
     524          try:
     525              resultTuple = proc._nextpoint.next()
     526
     527              # Process the command function which has been yielded by the
     528              # process.
     529              if type(resultTuple[0]) == tuple:
     530                  # allowing for reneges, e.g.:
     531                  # >>> yield (request, self, res),(waituntil, self, cond)
     532                  command = resultTuple[0][0]
     533              else:
     534                  command = resultTuple[0]
     535              if __debug__:
     536                  if not command in self._commandcodes:
     537                      raise FatalSimerror('Illegal command: yield %s'%command)
     538              self._dispatch[command]((resultTuple, proc))
     539          except StopIteration:
     540              # Process execution method has terminated.
     541              self._terminate(proc)
     542
     543          # Test the conditions for all waiting processes if there are any at
     544          # all. Where condition are satisfied, reactivate that process
     545          # immediately and remove it from queue.
     546          # Always test the wait conditions. They might be triggered by on a
     547          # terminating process execution method (e.g. the above next() call
     548          # raises the StopIteration exception)
     549          if self.condQ:
     550              i = 0
     551              while i < len(self.condQ):
     552                  proc = self.condQ[i]
     553                  if proc.cond():
     554                      self.condQ.pop(i)
     555                      self.reactivate(proc)
     556                  else:
     557                      i += 1
     558
     559          # Return time of the next scheduled event.
     560          #return self._timestamps[0][0] if self._timestamps else None
     561          if self._timestamps:
     562              return self._timestamps[0][0]
     563          else:
     564              return None
     565
     566 -    def simulate(self, until=0):
     567          """
     568          Start the simulation and run its loop until the timeout ``until`` is
     569          reached, stopSimulation is called, or no more events are scheduled.
     570          """
     571          try:
     572              if not self._timestamps:
     573                  return 'SimPy: No activities scheduled'
     574
     575              # Some speedups. Storing these values in local variables prevents
     576              # the self-lookup. Note that this can't be done for _stop because
     577              # this variable will get overwritten, bools are immutable.
     578              step = self.step
     579              timestamps = self._timestamps
     580              while not self._stop and timestamps and timestamps[0][0] <= until:
     581                  step()
     582
     583              if not self._stop and timestamps:
     584                  # Timestamps left, simulation not stopped
     585                  self._t = until
     586                  return 'SimPy: Normal exit at time %s' % self._t
     587              elif not timestamps:
     588                  # No more timestamps
     589                  return 'SimPy: No more events at time %s' % self._t
     590              else:
     591                  # Stopped by call of stopSimulation
     592                  return 'SimPy: Run stopped at time %s' % self._t
     593          except FatalSimerror, error:
     594                  print 'SimPy: ' + error.value
     595                  raise FatalSimerror, 'SimPy: ' + error.value
     596          except Simerror, error:
     597              return 'SimPy: ' + error.value
     598          finally:
     599              self._stop = True
     600
     601  # For backward compatibility
     602  Globals.sim = Simulation()
     603
     604  peek = Globals.sim.peek
     605
     606  step = Globals.sim.step
     607  # End backward compatibility
     608
     609  if __name__ == '__main__':
     610      print 'SimPy.Simulation %s' %__version__
     611      ############# Test / demo functions #############
     612 -    def test_demo():
     613          class Aa(Process):
     614              sequIn = []
     615              sequOut = []
     616              def __init__(self, holdtime, name,sim=None):
     617                  Process.__init__(self, name,sim=sim)
     618                  self.holdtime = holdtime
     619
     620              def life(self, priority):
     621                  for i in range(1):
     622                      Aa.sequIn.append(self.name)
     623                      print self.sim.now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
     624                            len(rrr.activeQ)
     625                      print 'waitQ: ',[(k.name, k._priority[rrr]) for k in rrr.waitQ]
     626                      print 'activeQ: ',[(k.name, k._priority[rrr]) \
     627                                 for k in rrr.activeQ]
     628                      assert rrr.n + len(rrr.activeQ) == rrr.capacity, \
     629                     'Inconsistent resource unit numbers'
     630                      print self.sim.now(),self.name, 'requests 1 ', rrr.unitName
     631                      yield request, self, rrr, priority
     632                      print self.sim.now(),self.name, 'has 1 ', rrr.unitName
     633                      print self.sim.now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
     634                            len(rrr.activeQ)
     635                      print self.sim.now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
     636                            len(rrr.activeQ)
     637                      assert rrr.n + len(rrr.activeQ) == rrr.capacity, \
     638                     'Inconsistent resource unit numbers'
     639                      yield hold, self, self.holdtime
     640                      print self.sim.now(),self.name, 'gives up 1', rrr.unitName
     641                      yield release, self, rrr
     642                      Aa.sequOut.append(self.name)
     643                      print self.sim.now(),self.name, 'has released 1 ', rrr.unitName
     644                      print 'waitQ: ',[(k.name, k._priority[rrr]) for k in rrr.waitQ]
     645                      print self.sim.now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
     646                            len(rrr.activeQ)
     647                      assert rrr.n + len(rrr.activeQ) == rrr.capacity, \
     648                             'Inconsistent resource unit numbers'
     649
     650          class Observer(Process):
     651              def __init__(self,**vars):
     652                  Process.__init__(self,**vars)
     653
     654              def observe(self, step, processes, res):
     655                  while self.sim.now() < 11:
     656                      for i in processes:
     657                          print '++ %s process: %s: active:%s, passive:%s, terminated: %s, interrupted:%s, queuing:%s'\
     658                                %(self.sim.now(),i.name, i.active(),i.passive(),\
     659                                  i.terminated(),i.interrupted(),i.queuing(res))
     660                      print
     661                      yield hold, self, step
     662
     663          print'\n+++test_demo output'
     664          print '****First case == priority queue, resource service not preemptable'
     665          s=Simulation()
     666          s.initialize()
     667          rrr = Resource(5, name = 'Parking', unitName = 'space(s)', qType = PriorityQ,
     668                       preemptable = 0,sim=s)
     669          procs = []
     670          for i in range(10):
     671              z = Aa(holdtime = i, name = 'Car ' + str(i),sim=s)
     672              procs.append(z)
     673              s.activate(z, z.life(priority = i))
     674          o = Observer(sim=s)
     675          s.activate(o, o.observe(1, procs, rrr))
     676          a = s.simulate(until = 10000)
     677          print a
     678          print 'Input sequence: ', Aa.sequIn
     679          print 'Output sequence: ', Aa.sequOut
     680
     681          print '\n****Second case == priority queue, resource service preemptable'
     682          s=Simulation()
     683          s.initialize()
     684          rrr = Resource(5, name = 'Parking', unitName = 'space(s)', qType = PriorityQ,
     685                       preemptable = 1,sim=s)
     686          procs = []
     687          for i in range(10):
     688              z = Aa(holdtime = i, name = 'Car ' + str(i),sim=s)
     689              procs.append(z)
     690              s.activate(z, z.life(priority = i))
     691          o = Observer(sim=s)
     692          s.activate(o, o.observe(1, procs, rrr))
     693          Aa.sequIn = []
     694          Aa.sequOut = []
     695          a = s.simulate(until = 10000)
     696          print a
     697          print 'Input sequence: ', Aa.sequIn
     698          print 'Output sequence: ', Aa.sequOut
     699
     700 -    def test_interrupt():
     701          class Bus(Process):
     702              def __init__(self, **vars):
     703                  Process.__init__(self, **vars)
     704
     705              def operate(self, repairduration = 0):
     706                  print self.sim.now(),'>> %s starts' % (self.name)
     707                  tripleft = 1000
     708                  while tripleft > 0:
     709                      yield hold, self, tripleft
     710                      if self.interrupted():
     711                          print 'interrupted by %s' %self.interruptCause.name
     712                          print '%s: %s breaks down ' %(now(),self.name)
     713                          tripleft = self.interruptLeft
     714                          self.interruptReset()
     715                          print 'tripleft ', tripleft
     716                          s.reactivate(br, delay = repairduration) # breakdowns only during operation
     717                          yield hold, self, repairduration
     718                          print self.sim.now(),' repaired'
     719                      else:
     720                          break # no breakdown, ergo bus arrived
     721                  print self.sim.now(),'<< %s done' % (self.name)
     722
     723          class Breakdown(Process):
     724              def __init__(self, myBus,sim=None):
     725                  Process.__init__(self, name = 'Breakdown ' + myBus.name,sim=sim)
     726                  self.bus = myBus
     727
     728              def breakBus(self, interval):
     729
     730                  while True:
     731                      yield hold, self, interval
     732                      if self.bus.terminated(): break
     733                      self.interrupt(self.bus)
     734
     735          print'\n\n+++test_interrupt'
     736          s=Simulation()
     737          s.initialize()
     738          b = Bus(name='Bus 1',sim=s)
     739          s.activate(b, b.operate(repairduration = 20))
     740          br = Breakdown(b,sim=s)
     741          s.activate(br, br.breakBus(200))
     742          print s.simulate(until = 4000)
     743
     744 -    def testSimEvents():
     745          class Waiter(Process):
     746              def __init__(self,**vars):
     747                  Process.__init__(self,**vars)
     748              def waiting(self, theSignal):
     749                  while True:
     750                      yield waitevent, self, theSignal
     751                      print '%s: process \'%s\' continued after waiting for %s' %\
     752                            (self.sim.now(),self.name, theSignal.name)
     753                      yield queueevent, self, theSignal
     754                      print '%s: process \'%s\' continued after queueing for %s' % (now(),self.name, theSignal.name)
     755
     756          class ORWaiter(Process):
     757              def __init__(self,**vars):
     758                  Process.__init__(self,**vars)
     759              def waiting(self, signals):
     760                  while True:
     761                      yield waitevent, self, signals
     762                      print self.sim.now(),'one of %s signals occurred' %\
     763                            [x.name for x in signals]
     764                      print '\t%s (fired / param)'%\
     765                            [(x.name, x.signalparam) for x in self.eventsFired]
     766                      yield hold, self, 1
     767
     768          class Caller(Process):
     769              def __init__(self,**vars):
     770                  Process.__init__(self,**vars)
     771              def calling(self):
     772                  while True:
     773                      signal1.signal('wake up!')
     774                      print '%s: signal 1 has occurred'%now()
     775                      yield hold, self, 10
     776                      signal2.signal('and again')
     777                      signal2.signal('sig 2 again')
     778                      print '%s: signal1, signal2 have occurred'%now()
     779                      yield hold, self, 10
     780          print'\n+++testSimEvents output'
     781          s=Simulation()
     782          s.initialize()
     783          signal1 = SimEvent('signal 1',sim=s)
     784          signal2 = SimEvent('signal 2',sim=s)
     785          signal1.signal('startup1')
     786          signal2.signal('startup2')
     787          w1 = Waiter(name='waiting for signal 1',sim=s)
     788          s.activate(w1, w1.waiting(signal1))
     789          w2 = Waiter(name='waiting for signal 2',sim=s)
     790          s.activate(w2, w2.waiting(signal2))
     791          w3 = Waiter(name='also waiting for signal 2',sim=s)
     792          s.activate(w3, w3.waiting(signal2))
     793          w4 = ORWaiter(name='waiting for either signal 1 or signal 2',sim=s)
     794          s.activate(w4, w4.waiting([signal1, signal2]),prior = True)
     795          c = Caller(name='Caller',sim=s)
     796          s.activate(c, c.calling())
     797          print s.simulate(until = 100)
     798
     799 -    def testwaituntil():
     800          """
     801          Demo of waitUntil capability.
     802
     803          Scenario:
     804          Three workers require sets of tools to do their jobs. Tools are shared,
     805          scarce resources for which they compete.
     806          """
     807          class Worker(Process):
     808              def __init__(self, name, heNeeds = [],sim=None):
     809                  Process.__init__(self, name,sim=sim)
     810                  self.heNeeds = heNeeds
     811              def work(self):
     812                  def workerNeeds():
     813                      for item in self.heNeeds:
     814                          if item.n == 0:
     815                              return False
     816                      return True
     817
     818                  while self.sim.now() < 8 * 60:
     819                      yield waituntil, self, workerNeeds
     820                      for item in self.heNeeds:
     821                          yield request, self, item
     822                      print '%s %s has %s and starts job' % (self.sim.now(),self.name,
     823                          [x.name for x in self.heNeeds])
     824                      yield hold, self, random.uniform(10, 30)
     825                      for item in self.heNeeds:
     826                          yield release, self, item
     827                      yield hold, self, 2 #rest
     828
     829          print '\n+++ nwaituntil demo output'
     830          random.seed(12345)
     831          s=Simulation()
     832          s.initialize()
     833          brush = Resource(capacity = 1, name = 'brush',sim=s)
     834          ladder = Resource(capacity = 2, name = 'ladder',sim=s)
     835          hammer = Resource(capacity = 1, name = 'hammer',sim=s)
     836          saw = Resource(capacity = 1, name = 'saw',sim=s)
     837          painter = Worker('painter',[brush, ladder],sim=s)
     838          s.activate(painter, painter.work())
     839          roofer = Worker('roofer',[hammer, ladder, ladder],sim=s)
     840          s.activate(roofer, roofer.work())
     841          treeguy = Worker('treeguy',[saw, ladder],sim=s)
     842          s.activate(treeguy, treeguy.work())
     843          for who in (painter, roofer, treeguy):
     844              print '%s needs %s for his job' %\
     845                    (who.name,[x.name for x in who.heNeeds])
     846          print
     847          print s.simulate(until = 9 * 60)
     848
     849      ## -------------------------------------------------------------
     850      ##                    TEST COMPOUND 'YIELD REQUEST' COMMANDS
     851      ## -------------------------------------------------------------
     852
     853      ## -------------------------------------------------------------
     854      ##             TEST 'yield (request, self, res),(hold, self, delay)'
     855      ##                   == timeout renege
     856      ## -------------------------------------------------------------
     857
     858 -    class JobTO(Process):
     859         """ Job class for testing timeout reneging
     860         """
     861 -       def __init__(self, server = None, name = '',sim=None):
     862              Process.__init__(self, name,sim=sim)
     863              self.res = server
     864              self.gotResource = None
     865
     866 -       def execute(self, timeout, usetime):
     867              yield (request, self, self.res),(hold, self, timeout)
     868              if self.acquired(self.res):
     869                  self.gotResource = True
     870                  yield hold, self, usetime
     871                  yield release, self, self.res
     872              else:
     873                  self.gotResource = False
     874
     875
     876 -    def testNoTimeout():
     877          """Test that resource gets acquired without timeout
     878          """
     879          s=Simulation()
     880          s.initialize()
     881          res = Resource(name = 'Server', capacity = 1,sim=s)
     882          usetime = 5
     883          timeout = 1000000
     884          j1 = JobTO(server = res, name = 'Job_1',sim=s)
     885          s.activate(j1, j1.execute(timeout = timeout, usetime = usetime))
     886          j2 = JobTO(server = res, name = 'Job_2',sim=s)
     887          s.activate(j2, j2.execute(timeout = timeout, usetime = usetime))
     888          s.simulate(until = 2 * usetime)
     889          assert s.now() == 2 * usetime, 'time not == 2 * usetime'
     890          assert j1.gotResource and j2.gotResource,\
     891              'at least one job failed to get resource'
     892          assert not (res.waitQ or res.activeQ),\
     893              'job waiting or using resource'
     894
     895 -    def testTimeout1():
     896          """Test that timeout occurs when resource busy
     897          """
     898          s=Simulation()
     899          s.initialize()
     900          res = Resource(name = 'Server', capacity = 1, monitored = True,sim=s)
     901          usetime = 5
     902          timeout = 3
     903          j1 = JobTO(server = res, name = 'Job_1',sim=s)
     904          s.activate(j1, j1.execute(timeout = timeout, usetime = usetime))
     905          j2 = JobTO(server = res, name = 'Job_2',sim=s)
     906          s.activate(j2, j2.execute(timeout = timeout, usetime = usetime))
     907          s.simulate(until = 2 * usetime)
     908          assert(s.now() == usetime),'time not == usetime'
     909          assert(j1.gotResource),'Job_1 did not get resource'
     910          assert(not j2.gotResource),'Job_2 did not renege'
     911          assert not (res.waitQ or res.activeQ),\
     912              'job waiting or using resource'
     913
     914 -    def testTimeout2():
     915          """Test that timeout occurs when resource has no capacity free
     916          """
     917          s=Simulation()
     918          s.initialize()
     919          res = Resource(name = 'Server', capacity = 0,sim=s)
     920          usetime = 5
     921          timeout = 3
     922          j1 = JobTO(server = res, name = 'Job_1',sim=s)
     923          s.activate(j1, j1.execute(timeout = timeout, usetime = usetime))
     924          j2 = JobTO(server = res, name = 'Job_2',sim=s)
     925          s.activate(j2, j2.execute(timeout = timeout, usetime = usetime))
     926          s.simulate(until = 2 * usetime)
     927          assert s.now() == timeout, 'time %s not == timeout'%now()
     928          assert not j1.gotResource, 'Job_1 got resource'
     929          assert not j2.gotResource, 'Job_2 got resource'
     930          assert not (res.waitQ or res.activeQ),\
     931              'job waiting or using resource'
     932
     933      ## ------------------------------------------------------------------
     934      ##             TEST 'yield (request, self, res),(waitevent, self, event)'
     935      ##                   == event renege
     936      ## ------------------------------------------------------------------
     937 -    class JobEvt(Process):
     938         """ Job class for testing event reneging
     939         """
     940 -       def __init__(self, server = None, name = '',sim=None):
     941              Process.__init__(self, name,sim=sim)
     942              self.res = server
     943              self.gotResource = None
     944
     945 -       def execute(self, event, usetime):
     946              yield (request, self, self.res),(waitevent, self, event)
     947              if self.acquired(self.res):
     948                  self.gotResource = True
     949                  yield hold, self, usetime
     950                  yield release, self, self.res
     951              else:
     952                  self.gotResource = False
     953
     954 -    class JobEvtMulti(Process):
     955         """ Job class for testing event reneging with multi - event lists
     956         """
     957 -       def __init__(self, server = None, name = '',sim=None):
     958              Process.__init__(self, name,sim=sim)
     959              self.res = server
     960              self.gotResource = None
     961
     962 -       def execute(self, eventlist, usetime):
     963              yield (request, self, self.res),(waitevent, self, eventlist)
     964              if self.acquired(self.res):
     965                  self.gotResource = True
     966                  yield hold, self, usetime
     967                  yield release, self, self.res
     968              else:
     969                  self.gotResource = False
     970
     971 -    class FireEvent(Process):
     972          """Fires reneging event
     973          """
     974 -        def __init__(self,**vars):
     975              Process.__init__(self,**vars)
     976 -        def fire(self, fireDelay, event):
     977              yield hold, self, fireDelay
     978              event.signal()
     979
     980 -    def testNoEvent():
     981          """Test that processes acquire resource normally if no event fires
     982          """
     983          s=Simulation()
     984          s.initialize()
     985          res = Resource(name = 'Server', capacity = 1,sim=s)
     986          event = SimEvent(name='Renege_trigger',sim=s) #never gets fired
     987          usetime = 5
     988          j1 = JobEvt(server = res, name = 'Job_1',sim=s)
     989          s.activate(j1, j1.execute(event = event, usetime = usetime))
     990          j2 = JobEvt(server = res, name = 'Job_2',sim=s)
     991          s.activate(j2, j2.execute(event = event, usetime = usetime))
     992          s.simulate(until = 2 * usetime)
     993          # Both jobs should get server (in sequence)
     994          assert s.now() == 2 * usetime, 'time not == 2 * usetime'
     995          assert j1.gotResource and j2.gotResource,\
     996              'at least one job failed to get resource'
     997          assert not (res.waitQ or res.activeQ),\
     998              'job waiting or using resource'
     999
    1000 -    def testWaitEvent1():
    1001          """Test that signalled event leads to renege when resource busy
    1002          """
    1003          s=Simulation()
    1004          s.initialize()
    1005          res = Resource(name = 'Server', capacity = 1,sim=s)
    1006          event = SimEvent('Renege_trigger',sim=s)
    1007          usetime = 5
    1008          eventtime = 1
    1009          j1 = JobEvt(server = res, name = 'Job_1',sim=s)
    1010          s.activate(j1, j1.execute(event = event, usetime = usetime))
    1011          j2 = JobEvt(server = res, name = 'Job_2',sim=s)
    1012          s.activate(j2, j2.execute(event = event, usetime = usetime))
    1013          f = FireEvent(name = 'FireEvent',sim=s)
    1014          s.activate(f, f.fire(fireDelay = eventtime, event = event))
    1015          s.simulate(until = 2 * usetime)
    1016          # Job_1 should get server, Job_2 renege
    1017          assert(s.now() == usetime),'time not == usetime'
    1018          assert(j1.gotResource),'Job_1 did not get resource'
    1019          assert(not j2.gotResource),'Job_2 did not renege'
    1020          assert not (res.waitQ or res.activeQ),\
    1021              'job waiting or using resource'
    1022
    1023 -    def testWaitEvent2():
    1024          """Test that renege - triggering event can be one of an event list
    1025          """
    1026          s=Simulation()
    1027          s.initialize()
    1028          res = Resource(name = 'Server', capacity = 1,sim=s)
    1029          event1 = SimEvent('Renege_trigger_1',sim=s)
    1030          event2 = SimEvent('Renege_trigger_2',sim=s)
    1031          usetime = 5
    1032          eventtime = 1 #for both events
    1033          j1 = JobEvtMulti(server = res, name = 'Job_1',sim=s)
    1034          s.activate(j1, j1.execute(eventlist = [event1, event2],usetime = usetime))
    1035          j2 = JobEvtMulti(server = res, name = 'Job_2',sim=s)
    1036          s.activate(j2, j2.execute(eventlist = [event1, event2],usetime = usetime))
    1037          f1 = FireEvent(name = 'FireEvent_1',sim=s)
    1038          s.activate(f1, f1.fire(fireDelay = eventtime, event = event1))
    1039          f2 = FireEvent(name = 'FireEvent_2',sim=s)
    1040          s.activate(f2, f2.fire(fireDelay = eventtime, event = event2))
    1041          s.simulate(until = 2 * usetime)
    1042          # Job_1 should get server, Job_2 should renege
    1043          assert(s.now() == usetime),'time not == usetime'
    1044          assert(j1.gotResource),'Job_1 did not get resource'
    1045          assert(not j2.gotResource),'Job_2 did not renege'
    1046          assert not (res.waitQ or res.activeQ),\
    1047              'job waiting or using resource'
    1048
    1049      testNoTimeout()
    1050      testTimeout1()
    1051      testTimeout2()
    1052      testNoEvent()
    1053      testWaitEvent1()
    1054      testWaitEvent2()
    1055      test_demo()
    1056      test_interrupt()
    1057      testSimEvents()
    1058      testwaituntil()
    1059