Package SimPy :: Module SimulationRT

[hide private]
[frames] | no frames]

# Source Code for Module SimPy.SimulationRT

       1  #!/usr/bin/env python
       2  from SimPy.Lister import *
       3  import bisect
       4  import types
       5  import time
       6  import sys
       7  import new
       8  import random
       9  import inspect
      10
      11  # $Revision: 1.1.1.24 $ $Date: 2007/01/18 14:43:37 $ kgm
      12
      13  """SimulationRT 1.8 Provides synchronization of real time and SimPy simulation time.
      14  Implements SimPy Processes, resources, and the backbone simulation scheduling
      15  by coroutine calls.
      16  Based on generators (Python 2.3 and later)
      17
      18  LICENSE:
      19  Copyright (C) 2002,2005,2006,2007  Klaus G. Muller, Tony Vignaux
      20  mailto: kgmuller@xs4all.nl and Tony.Vignaux@vuw.ac.nz
      21
      22      This library is free software; you can redistribute it and/or
      23      modify it under the terms of the GNU Lesser General Public
      24      License as published by the Free Software Foundation; either
      25      version 2.1 of the License, or (at your option) any later version.
      26
      27      This library is distributed in the hope that it will be useful,
      28      but WITHOUT ANY WARRANTY; without even the implied warranty of
      29      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      30      Lesser General Public License for more details.
      31
      32      You should have received a copy of the GNU Lesser General Public
      33      License along with this library; if not, write to the Free Software
      34      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
      35  END OF LICENSE
      36
      37
      38  **Change history:**
      39      4/8/2003: - Experimental introduction of synchronization of simulation
      40                  time and real time (idea of Geoff Jarrad of CSIRO -- thanks,
      41                  Geoff!).
      42                  * Changes made to class __Evlist, _nextev(), simulate()
      43
      44      Dec 11, 2003:
      45              - Updated to Simulation 1.4alpha API
      46
      47      13 Dec 2003: Merged in Monitor and Histogram
      48
      49      27 Feb 2004: Repaired bug in activeQ monitor of class Resource. Now actMon
      50                   correctly records departures from activeQ.
      51
      52      19 May 2004: Added erroneously omitted Histogram class.
      53
      54      5 Sep 2004: Added SimEvents synchronization constructs
      55
      56      17 Sep 2004: Added waituntil synchronization construct
      57
      58      28 Sep 2004: Changed handling of real time -- now uses time.clock for Win32, and
      59                   time.time for all other OS (works better on Linux, Unix).
      60
      61      01 Dec 2004: SimPy version 1.5
      62                   Changes in this module: Repaired SimEvents bug re proc.eventsFired
      63
      64      12 Jan 2005: SimPy version 1.5.1
      65                   Changes in this module: Monitor objects now have a default name
      66                                           'a_Monitor'
      67
      68      29 Mar 2005: Start SimPy 1.6: compound "yield request" statements
      69
      70      05 Jun 2005: Fixed bug in _request method -- waitMon did not work properly in
      71                   preemption case
      72
      73      09 Jun 2005: Added test in 'activate' to see whether 'initialize()' was called first.
      74
      75      23 Aug 2005: - Added Tally data collection class
      76                   - Adjusted Resource to work with Tally
      77                   - Redid function allEventNotices() (returns prettyprinted string with event
      78                     times and names of process instances
      79                   - Added function allEventTimes (returns event times of all scheduled events)
      80
      81      16 Mar 2006: - Added Store and Level classes
      82                   - Added 'yield get' and 'yield put'
      83
      84      10 May 2006: - Repaired bug in Store._get method
      85                   - Repaired Level to allow initialBuffered have float value
      86                   - Added type test for Level get parameter 'nrToGet'
      87
      88      06 Jun 2006: - To improve pretty-printed output of 'Level' objects, changed attribute
      89                     _nrBuffered to nrBuffered (synonym for amount property)
      90                   - To improve pretty-printed output of 'Store' objects, added attribute
      91                     buffered (which refers to _theBuffer)
      92
      93      25 Aug 2006: - Start of version 1.8
      94                   - made 'version' public
      95                   - corrected condQ initialization bug
      96
      97      30 Sep 2006: - Introduced checks to ensure capacity of a Buffer > 0
      98                   - Removed from __future__ import (so Python 2.3 or later needed)
      99
     100      15 Oct 2006: - Added code to register all Monitors and all Tallies in variables
     101                     'allMonitors' and 'allTallies'
     102                   - Added function 'startCollection' to activate Monitors and Tallies at a
     103                     specified time (e.g. after warmup period)
     104                   - Moved all test/demo programs to after 'if __name__=="__main__":'.
     105
     106      17 Oct 2006: - Added compound 'put' and 'get' statements for Level and Store.
     107
     108      18 Oct 2006: - Repaired bug: self.eventsFired now gets set after an event fires
     109                     in a compound yield get/put with a waitevent clause (reneging case).
     110
     111      21 Oct 2006: - Introduced Store 'yield get' with a filter function.
     112
     113      22 Oct 2006: - Repaired bug in prettyprinting of Store objects (the buffer
     114                     content==._theBuffer was not shown) by changing ._theBuffer
     115                     to .theBuffer.
     116
     117      04 Dec 2006: - Added printHistogram method to Tally and Monitor (generates
     118                     table-form histogram)
     119
     120      07 Dec 2006: - Changed the __str__ method of Histogram to print a table (like printHistogram)
     121
     122      12 Dec 2006: - Added change to allow setting of simulation to real time ratio during
     123                     simulation (contributed by Robert C. Ramsdell III -- thanks, Bob!)
     124  """
     125  __TESTING=False
     126  version=__version__="1.8 $Revision: 1.1.1.24 $ $Date: 2007/01/18 14:43:37 $"
     127  if __TESTING:
     128      print "SimPy.SimulationRT %s" %__version__,
     129      if __debug__:
     130          print "__debug__ on"
     131      else:
     132          print
     133
     134  # yield keywords
     135  hold=1
     136  passivate=2
     137  request=3
     138  release=4
     139  waitevent=5
     140  queueevent=6
     141  waituntil=7
     142  get=8
     143  put=9
     144
     145  _endtime=0
     146  _t=0
     147  _e=None
     148  _stop=True
     149  _wustep=False #controls per event stepping for waituntil construct; not for user API
     150  True=1
     151  False=0
     152  condQ=[]
     153  allMonitors=[]
     154  allTallies=[]
     155
     156  if sys.platform=="win32":  #take care of differences in clock accuracy
     157      wallclock=time.clock
     158  else:
     159      wallclock=time.time
     160  rtstart=wallclock()
     161
     162 -def rtnow():
     163      return wallclock()-rtstart
     164
     165 -def rtset(rel_speed=1):
     166      """resets the the ratio simulation time over clock time(seconds).
     167      """
     168      if _e is None:
     169          raise FatalSimerror("Fatal SimPy error: Simulation not initialized")
     170      _e.rel_speed=rel_speed
     171
     172 -def initialize():
     173      global _e,_t,_stop,condQ,allMonitors,allTallies
     174      _e=__Evlist()
     175      _t=0
     176      _stop=False
     177      condQ=[]
     178      allMonitors=[]
     179      allTallies=[]
     180
     181 -def now():
     182      return _t
     183
     184 -def stopSimulation():
     185      """Application function to stop simulation run"""
     186      global _stop
     187      _stop=True
     188
     189 -def _startWUStepping():
     190      """Application function to start stepping through simulation for waituntil construct."""
     191      global _wustep
     192      _wustep=True
     193
     194 -def _stopWUStepping():
     195      """Application function to stop stepping through simulation."""
     196      global _wustep
     197      _wustep=False
     198
     199 -class Simerror(Exception):
     200 -    def __init__(self,value):
     201          self.value=value
     202
     203 -    def __str__(self):
     204          return `self.value`
     205
     206 -class FatalSimerror(Simerror):
     207 -    def __init__(self,value):
     208          Simerror.__init__(self,value)
     209          self.value=value
     210
     211 -class Process(Lister):
     212      """Superclass of classes which may use generator functions"""
     213 -    def __init__(self,name="a_process"):
     214          #the reference to this Process instances single process (==generator)
     215          self._nextpoint=None
     216          self.name=name
     217          self._nextTime=None #next activation time
     218          self._remainService=0
     219          self._preempted=0
     220          self._priority={}
     221          self._getpriority={}
     222          self._putpriority={}
     223          self._terminated= False
     224          self._inInterrupt= False
     225          self.eventsFired=[] #events waited/queued for occurred
     226
     227 -    def active(self):
     228          return self._nextTime <> None and not self._inInterrupt
     229
     230 -    def passive(self):
     231          return self._nextTime is None and not self._terminated
     232
     233 -    def terminated(self):
     234          return self._terminated
     235
     236 -    def interrupted(self):
     237          return self._inInterrupt and not self._terminated
     238
     239 -    def queuing(self,resource):
     240          return self in resource.waitQ
     241  # TODO: Review whether this should include waiting for a buffer; or throw away?
     242 -    def cancel(self,victim):
     243          """Application function to cancel all event notices for this Process instance;(should be all event
     244          notices for the _generator_)."""
     245          _e._unpost(whom=victim)
     246
     247 -    def _hold(self,a):
     248          if len(a[0]) == 3:
     249              delay=abs(a[0][2])
     250          else:
     251              delay=0
     252          who=a[1]
     253          self.interruptLeft=delay
     254          self._inInterrupt=False
     255          self.interruptCause=None
     256          _e._post(what=who,at=_t+delay)
     257
     258 -    def _passivate(self,a):
     259          a[0][1]._nextTime=None
     260
     261 -    def interrupt(self,victim):
     262          """Application function to interrupt active processes"""
     263          # can't interrupt terminated/passive/interrupted process
     264          if victim.active():
     265              victim.interruptCause=self  # self causes interrupt
     266              left=victim._nextTime-_t
     267              victim.interruptLeft=left   # time left in current 'hold'
     268              victim._inInterrupt=True
     269              reactivate(victim)
     270              return left
     271          else: #victim not active -- can't interrupt
     272              return None
     273
     274 -    def interruptReset(self):
     275          """
     276          Application function for an interrupt victim to get out of
     277          'interrupted' state.
     278          """
     279          self._inInterrupt= False
     280
     281 -    def acquired(self,res):
     282          """Multi-functional test for reneging for 'request' and 'get':
     283          (1)If res of type Resource:
     284              Tests whether resource res was acquired when proces reactivated.
     285              If yes, the parallel wakeup process is killed.
     286              If not, process is removed from res.waitQ (reneging).
     287          (2)If res of type Store:
     288              Tests whether item(s) gotten from Store res.
     289              If yes, the parallel wakeup process is killed.
     290              If no, process is removed from res.getQ
     291          (3)If res of type Level:
     292              Tests whether units gotten from Level res.
     293              If yes, the parallel wakeup process is killed.
     294              If no, process is removed from res.getQ.
     295          """
     296          if isinstance(res,Resource):
     297              test=self in res.activeQ
     298              if test:
     299                  self.cancel(self._holder)
     300              else:
     301                  res.waitQ.remove(self)
     302                  if res.monitored:
     303                      res.waitMon.observe(len(res.waitQ),t=now())
     304              return test
     305          elif isinstance(res,Store):
     306              test=len(self.got)
     307              if test:
     308                  self.cancel(self._holder)
     309                  self.cancel(self._holder)
     310              else:
     311                  res.getQ.remove(self)
     312                  if res.monitored:
     313                      res.getQMon.observe(len(res.getQ),t=now())
     314              return test
     315          elif isinstance(res,Level):
     316              test=not (self.got is None)
     317              if test:
     318                  self.cancel(self._holder)
     319              else:
     320                  res.getQ.remove(self)
     321                  if res.monitored:
     322                      res.getQMon.observe(len(res.getQ),t=now())
     323              return test
     324
     325 -    def stored(self,buffer):
     326          """Test for reneging for 'yield put . . .' compound statement (Level and
     327          Store. Returns True if not reneged.
     328          If self not in buffer.putQ, kill wakeup process, else take self out of
     329          buffer.putQ (reneged)"""
     330          test=self in buffer.putQ
     331          if test:    #reneged
     332              buffer.putQ.remove(self)
     333              if buffer.monitored:
     334                  buffer.putQMon.observe(len(buffer.putQ),t=now())
     335          else:
     336              self.cancel(self._holder)
     337          return not test
     338
     339 -def allEventNotices():
     340      """Returns string with eventlist as
     341              t1: [procname,procname2]
     342              t2: [procname4,procname5, . . . ]
     343              . . .  .
     344      """
     345      ret=""
     346      for t in _e.timestamps:
     347          ret+="%s:%s\n"%(t,[ev.who.name for ev in _e.events[t]])
     348      return ret[:-1]
     349
     350 -def allEventTimes():
     351      """Returns list of all times for which events are scheduled.
     352      """
     353      return _e.timestamps
     354
     355
     356 -class __Evlist:
     357      """Defines event list and operations on it"""
     358 -    def __init__(self):
     359          #has the structure {<time1>:(ev notice1, ev notice2, . . ),
     360          #                   <time2>:(ev notice 3,...),..}
     361          self.events={}
     362          #always sorted list of event times (keys for self.events)
     363          self.timestamps=[]
     364          self.real_time=False
     365          self.rel_speed=1
     366          self.rtlast = wallclock()
     367          self.stlast = 0
     368
     369 -    def _post(self,what,at,prior=False):
     370          """Post an event notice for process what for time at"""
     371          # event notices are _Action instances
     372          if at < _t:
     373              raise Simerror("Attempt to schedule event in the past")
     374          if at in self.events:
     375              if prior:
     376                  #before all other event notices at this time
     377                  self.events[at][0:0]= [what]
     378              else:
     379                  self.events[at].append(what)
     380          else: # first event notice at this time
     381              self.events[at]=[what]
     382              bisect.insort(self.timestamps,at)
     383          what.who._nextTime=at
     384
     385 -    def _unpost(self,whom):
     386          """
     387          Search through all event notices at whom's event time and remove whom's
     388          event notice if whom is a suspended process
     389          """
     390          thistime=whom._nextTime
     391          if thistime is None: #check if whom was actually active
     392              return
     393          else:
     394              thislist=self.events[thistime]
     395              for n in thislist:
     396                  if n.who==whom:
     397                      self.events[thistime].remove(n)
     398                      whom._nextTime = None
     399                      if not self.events[thistime]:
     400                          # no other ev notices for thistime
     401                          del(self.events[thistime])
     402                          item_delete_point = bisect.bisect(self.timestamps,
     403                                                thistime)
     404                          del self.timestamps[item_delete_point-1]
     405
     406 -    def _nextev(self):
     407          """Retrieve next event from event list"""
     408          global _t, _stop
     409          if not self.events: raise Simerror("No more events at time %s" %now())
     410          earliest=self.timestamps[0]
     411          ## Calculate any wait time
     412          ## event clock time = rtlast + (sim_time - stlast)/rel_speed
     413          if self.real_time and _t < earliest:
     414              #~ delay=(1.0*earliest/self.rel_speed)-rtnow()
     415              #~ if delay > 0: time.sleep(delay)
     416              next_time = 1.0*(earliest - self.stlast)/self.rel_speed
     417              next_time += self.rtlast
     418              delay=next_time - wallclock()
     419              if delay > 0:time.sleep(delay)
     420              self.rtlast = wallclock()
     421              self.stlast = earliest
     422          _t=max(earliest,_t)
     423          temp=self.events[earliest] #list of actions for this time _t
     424          tempev=temp[0] #first action
     425          del(self.events[earliest][0])
     426          if self.events[earliest]==[]:
     427              del(self.events[earliest]) #delete empty list of actions
     428              del(self.timestamps[0])
     429          if _t > _endtime:
     430              _t = _endtime
     431              _stop = True
     432              return (None,)
     433          tempwho=tempev.who
     434          try:
     435              tt=tempwho._nextpoint.next()
     436          except StopIteration:
     437              tempwho._nextpoint=None
     438              tempwho._terminated=True
     439              tempwho._nextTime=None
     440              tt=None
     441              tempev=tempwho
     442          return tt,tempev
     443
     444 -class _Action:
     445      """Structure (who=process owner, generator=process)"""
     446 -    def __init__(self,who,generator):
     447          self.who=who
     448          self.process=generator
     449
     450 -    def __str__(self):
     451          return "Action %s %s" %((self.who.name,self.process))
     452
     453 -def activate(object,process,at="undefined",delay="undefined",prior=False):
     454      """Application function to activate passive process."""
     455      if _e is None:
     456          raise FatalSimerror\
     457         ("Fatal error: simulation is not initialized (call initialize() first)")
     458      if not (type(process) == types.GeneratorType):
     459          raise FatalSimerror("Activating function which"+
     460                         " is not a generator (contains no 'yield')")
     461      if not object._terminated and not object._nextTime:
     462          #store generator reference in object; needed for reactivation
     463          object._nextpoint=process
     464          if at=="undefined":
     465              at=_t
     466          if delay=="undefined":
     467              zeit=max(_t,at)
     468          else:
     469              zeit=max(_t,_t+delay)
     470          _e._post(_Action(who=object,generator=process),at=zeit,prior=prior)
     471
     472 -def reactivate(obj,at="undefined",delay="undefined",prior=False):
     473      """Application function to reactivate a process which is active,
     474      suspended or passive."""
     475      # Object may be active, suspended or passive
     476      if not obj._terminated:
     477          a=Process("SimPysystem")
     478          a.cancel(obj)
     479          # object now passive
     480          if at=="undefined":
     481              at=_t
     482          if delay=="undefined":
     483              zeit=max(_t,at)
     484          else:
     485              zeit=max(_t,_t+delay)
     486          _e._post(_Action(who=obj,generator=obj._nextpoint),at=zeit,
     487                   prior=prior)
     488
     489 -class Histogram(list):
     490      """ A histogram gathering and sampling class"""
     491
     492 -    def __init__(self,name = '',low=0.0,high=100.0,nbins=10):
     493          list.__init__(self)
     494          self.name  = name
     495          self.low   = float(low)
     496          self.high  = float(high)
     497          self.nbins = nbins
     498          self.binsize=(self.high-self.low)/nbins
     499          self._nrObs=0
     500          self._sum=0
     501          self[:] =[[low+(i-1)*self.binsize,0] for i in range(self.nbins+2)]
     502
     503 -    def addIn(self,y):
     504          """ add a value into the correct bin"""
     505          self._nrObs+=1
     506          self._sum+=y
     507          b = int((y-self.low+self.binsize)/self.binsize)
     508          if b < 0: b = 0
     509          if b > self.nbins+1: b = self.nbins+1
     510          assert 0 <= b <=self.nbins+1,'Histogram.addIn: b out of range: %s'%b
     511          self[b][1]+=1
     512
     513 -    def __str__(self):
     514          histo=self
     515          ylab="value"
     516          nrObs=self._nrObs
     517          width=len(str(nrObs))
     518          res=[]
     519          res.append("<Histogram %s:"%self.name)
     520          res.append("\nNumber of observations: %s"%nrObs)
     521          if nrObs:
     522              su=self._sum
     523              cum=histo[0][1]
     524              fmt="%s"
     525              line="\n%s <= %s < %s: %s (cum: %s/%s%s)"\
     526                   %(fmt,"%s",fmt,"%s","%s","%5.1f","%s")
     527              line1="\n%s%s < %s: %s (cum: %s/%s%s)"\
     528                   %("%s","%s",fmt,"%s","%s","%5.1f","%s")
     529              l1width=len(("%s <= "%fmt)%histo[1][0])
     530              res.append(line1\
     531                         %(" "*l1width,ylab,histo[1][0],str(histo[0][1]).rjust(width),\
     532                           str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
     533                        )
     534              for i in range(1,len(histo)-1):
     535                  cum+=histo[i][1]
     536                  res.append(line\
     537                         %(histo[i][0],ylab,histo[i+1][0],str(histo[i][1]).rjust(width),\
     538                           str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
     539                            )
     540              cum+=histo[-1][1]
     541              linen="\n%s <= %s %s : %s (cum: %s/%s%s)"\
     542                    %(fmt,"%s","%s","%s","%s","%5.1f","%s")
     543              lnwidth=len(("<%s"%fmt)%histo[1][0])
     544              res.append(linen\
     545                         %(histo[-1][0],ylab," "*lnwidth,str(histo[-1][1]).rjust(width),\
     546                         str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
     547                         )
     548          res.append("\n>")
     549          return " ".join(res)
     550
     551 -def startCollection(when=0.0,monitors=None,tallies=None):
     552      """Starts data collection of all designated Monitor and Tally objects
     553      (default=all) at time 'when'.
     554      """
     555      class Starter(Process):
     556          def collect(self,monitors,tallies):
     557              for m in monitors:
     558                  print m.name
     559                  m.reset()
     560              for t in tallies:
     561                  t.reset()
     562              yield hold,self
     563      if monitors is None:
     564          monitors=allMonitors
     565      if tallies is None:
     566          tallies=allTallies
     567      s=Starter()
     568      activate(s,s.collect(monitors=monitors,tallies=tallies),at=when)
     569
     570 -class Monitor(list):
     571      """ Monitored variables
     572
     573      A Class for monitored variables, that is, variables that allow one
     574      to gather simple statistics.  A Monitor is a subclass of list and
     575      list operations can be performed on it. An object is established
     576      using m= Monitor(name = '..'). It can be given a
     577      unique name for use in debugging and in tracing and ylab and tlab
     578      strings for labelling graphs.
     579      """
     580 -    def __init__(self,name='a_Monitor',ylab='y',tlab='t'):
     581          list.__init__(self)
     582          self.startTime = 0.0
     583          self.name = name
     584          self.ylab = ylab
     585          self.tlab = tlab
     586          allMonitors.append(self)
     587
     588 -    def setHistogram(self,name = '',low=0.0,high=100.0,nbins=10):
     589          """Sets histogram parameters.
     590          Must be called before call to getHistogram"""
     591          if name=='':
     592              histname=self.name
     593          else:
     594              histname=name
     595          self.histo=Histogram(name=histname,low=low,high=high,nbins=nbins)
     596
     597 -    def observe(self,y,t=None):
     598          """record y and t"""
     599          if t is  None: t = now()
     600          self.append([t,y])
     601
     602 -    def tally(self,y):
     603          """ deprecated: tally for backward compatibility"""
     604          self.observe(y,0)
     605
     606 -    def accum(self,y,t=None):
     607          """ deprecated:  accum for backward compatibility"""
     608          self.observe(y,t)
     609
     610 -    def reset(self,t=None):
     611          """reset the sums and counts for the monitored variable """
     612          self[:]=[]
     613          if t is None: t = now()
     614          self.startTime = t
     615
     616 -    def tseries(self):
     617          """ the series of measured times"""
     618          return list(zip(*self)[0])
     619
     620 -    def yseries(self):
     621          """ the series of measured values"""
     622          return list(zip(*self)[1])
     623
     624 -    def count(self):
     625          """ deprecated: the number of observations made """
     626          return self.__len__()
     627
     628 -    def total(self):
     629          """ the sum of the y"""
     630          if self.__len__()==0:  return 0
     631          else:
     632              sum = 0.0
     633              for i in range(self.__len__()):
     634                  sum += self[i][1]
     635              return sum # replace by sum() later
     636
     637 -    def mean(self):
     638          """ the simple average of the monitored variable"""
     639          try: return 1.0*self.total()/self.__len__()
     640          except:  print 'SimPy: No observations  for mean'
     641
     642 -    def var(self):
     643          """ the sample variance of the monitored variable """
     644          n = len(self)
     645          tot = self.total()
     646          ssq=0.0
     647          ##yy = self.yseries()
     648          for i in range(self.__len__()):
     649              ssq += self[i][1]**2 # replace by sum() eventually
     650          try: return (ssq - float(tot*tot)/n)/n
     651          except: print 'SimPy: No observations for sample variance'
     652
     653 -    def timeAverage(self,t=None):
     654          """ the time-average of the monitored variable.
     655
     656              If t is used it is assumed to be the current time,
     657              otherwise t =  now()
     658          """
     659          N = self.__len__()
     660          if N  == 0:
     661              print 'SimPy: No observations for timeAverage'
     662              return None
     663
     664          if t is None: t = now()
     665          sum = 0.0
     666          tlast = self.startTime
     667          #print 'DEBUG: timave ',t,tlast
     668          ylast = 0.0
     669          for i in range(N):
     670              ti,yi = self[i]
     671              sum += ylast*(ti-tlast)
     672              tlast = ti
     673              ylast = yi
     674          sum += ylast*(t-tlast)
     675          T = t - self.startTime
     676          if T == 0:
     677               print 'SimPy: No elapsed time for timeAverage'
     678               return None
     679          #print 'DEBUG: timave ',sum,t,T
     680          return sum/float(T)
     681
     682 -    def histogram(self,low=0.0,high=100.0,nbins=10):
     683          """ A histogram of the monitored y data values.
     684          """
     685          h = Histogram(name=self.name,low=low,high=high,nbins=nbins)
     686          ys = self.yseries()
     687          for y in ys: h.addIn(y)
     688          return h
     689
     690 -    def getHistogram(self):
     691          """Returns a histogram based on the parameters provided in
     692          preceding call to setHistogram.
     693          """
     694          ys = self.yseries()
     695          h=self.histo
     696          for y in ys: h.addIn(y)
     697          return h
     698
     699 -    def printHistogram(self,fmt="%s"):
     700          """Returns formatted frequency distribution table string from Monitor.
     701          Precondition: setHistogram must have been called.
     702          fmt==format of bin range values
     703          """
     704          try:
     705              histo=self.getHistogram()
     706          except:
     707              raise FatalSimerror("histogramTable: call setHistogram first"\
     708                                  " for Monitor %s"%self.name)
     709          ylab=self.ylab
     710          nrObs=self.count()
     711          width=len(str(nrObs))
     712          res=[]
     713          res.append("\nHistogram for %s:"%histo.name)
     714          res.append("\nNumber of observations: %s"%nrObs)
     715          su=sum(self.yseries())
     716          cum=histo[0][1]
     717          line="\n%s <= %s < %s: %s (cum: %s/%s%s)"\
     718               %(fmt,"%s",fmt,"%s","%s","%5.1f","%s")
     719          line1="\n%s%s < %s: %s (cum: %s/%s%s)"\
     720               %("%s","%s",fmt,"%s","%s","%5.1f","%s")
     721          l1width=len(("%s <= "%fmt)%histo[1][0])
     722          res.append(line1\
     723                     %(" "*l1width,ylab,histo[1][0],str(histo[0][1]).rjust(width),\
     724                       str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
     725                    )
     726          for i in range(1,len(histo)-1):
     727              cum+=histo[i][1]
     728              res.append(line\
     729                     %(histo[i][0],ylab,histo[i+1][0],str(histo[i][1]).rjust(width),\
     730                       str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
     731                        )
     732          cum+=histo[-1][1]
     733          linen="\n%s <= %s %s : %s (cum: %s/%s%s)"\
     734                %(fmt,"%s","%s","%s","%s","%5.1f","%s")
     735          lnwidth=len(("<%s"%fmt)%histo[1][0])
     736          res.append(linen\
     737                     %(histo[-1][0],ylab," "*lnwidth,str(histo[-1][1]).rjust(width),\
     738                     str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
     739                     )
     740          return " ".join(res)
     741
     742 -class Tally:
     743 -    def __init__(self, name="a_Tally", ylab="y",tlab="t"):
     744          self.name = name
     745          self.ylab = ylab
     746          self.tlab = tlab
     747          self.reset()
     748          self.startTime=0.0
     749          self.histo=None
     750          self._sum_of_squares=0
     751          allTallies.append(self)
     752
     753 -    def setHistogram(self,name = '',low=0.0,high=100.0,nbins=10):
     754          """Sets histogram parameters.
     755          Must be called to prior to observations initiate data collection
     756          for histogram.
     757          """
     758          if name=='':
     759              hname=self.name
     760          else:
     761              hname=name
     762          self.histo=Histogram(name=hname,low=low,high=high,nbins=nbins)
     763
     764 -    def observe(self, y, t=None):
     765          if t is None:
     766              t = now()
     767          self._total += y
     768          self._count += 1
     769          self._integral += (t - self._last_timestamp) * self._last_observation
     770          self._last_timestamp = t
     771          self._last_observation = y
     772          self._sum += y
     773          self._sum_of_squares += y * y
     774          if self.histo:
     775              self.histo.addIn(y)
     776
     777 -    def reset(self, t=None):
     778          if t is None:
     779              t = now()
     780          self.startTime = t
     781          self._last_timestamp = t
     782          self._last_observation = 0.0
     783          self._count = 0
     784          self._total = 0.0
     785          self._integral = 0.0
     786          self._sum = 0.0
     787          self._sum_of_squares = 0.0
     788
     789 -    def count(self):
     790          return self._count
     791
     792 -    def total(self):
     793          return self._total
     794
     795 -    def mean(self):
     796          return 1.0 * self._total / self._count
     797
     798 -    def timeAverage(self,t=None):
     799          if t is None:
     800              t=now()
     801          integ=self._integral+(t - self._last_timestamp) * self._last_observation
     802          if (t > self.startTime):
     803              return 1.0 * integ/(t - self.startTime)
     804          else:
     805              print 'SimPy: No elapsed time for timeAverage'
     806              return None
     807
     808 -    def var(self):
     809          return 1.0 * (self._sum_of_squares - (1.0 * (self._sum * self._sum)\
     810                 / self._count)) / (self._count)
     811
     812 -    def __len__(self):
     813          return self._count
     814
     815 -    def __eq__(self, l):
     816          return len(l) == self._count
     817
     818 -    def getHistogram(self):
     819          return self.histo
     820
     821 -    def printHistogram(self,fmt="%s"):
     822          """Returns formatted frequency distribution table string from Tally.
     823          Precondition: setHistogram must have been called.
     824          fmt==format of bin range values
     825          """
     826          try:
     827              histo=self.getHistogram()
     828          except:
     829              raise FatalSimerror("histogramTable: call setHistogram first"\
     830                                  " for Tally %s"%self.name)
     831          ylab=self.ylab
     832          nrObs=self.count()
     833          width=len(str(nrObs))
     834          res=[]
     835          res.append("\nHistogram for %s:"%histo.name)
     836          res.append("\nNumber of observations: %s"%nrObs)
     837          su=self.total()
     838          cum=histo[0][1]
     839          line="\n%s <= %s < %s: %s (cum: %s/%s%s)"\
     840               %(fmt,"%s",fmt,"%s","%s","%5.1f","%s")
     841          line1="\n%s%s < %s: %s (cum: %s/%s%s)"\
     842               %("%s","%s",fmt,"%s","%s","%5.1f","%s")
     843          l1width=len(("%s <= "%fmt)%histo[1][0])
     844          res.append(line1\
     845                     %(" "*l1width,ylab,histo[1][0],str(histo[0][1]).rjust(width),\
     846                       str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
     847                    )
     848          for i in range(1,len(histo)-1):
     849              cum+=histo[i][1]
     850              res.append(line\
     851                     %(histo[i][0],ylab,histo[i+1][0],str(histo[i][1]).rjust(width),\
     852                       str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
     853                        )
     854          cum+=histo[-1][1]
     855          linen="\n%s <= %s %s : %s (cum: %s/%s%s)"\
     856                %(fmt,"%s","%s","%s","%s","%5.1f","%s")
     857          lnwidth=len(("<%s"%fmt)%histo[1][0])
     858          res.append(linen\
     859                     %(histo[-1][0],ylab," "*lnwidth,str(histo[-1][1]).rjust(width),\
     860                     str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
     861                     )
     862          return " ".join(res)
     863
     864 -class Queue(list):
     865 -    def __init__(self,res,moni):
     866          if not moni is None: #moni==[]:
     867              self.monit=True # True if a type of Monitor/Tally attached
     868          else:
     869              self.monit=False
     870          self.moni=moni # The Monitor/Tally
     871          self.resource=res # the resource/buffer this queue belongs to
     872
     873 -    def enter(self,obj):
     874          pass
     875
     876 -    def leave(self):
     877          pass
     878
     879 -    def takeout(self,obj):
     880          self.remove(obj)
     881          if self.monit:
     882              self.moni.observe(len(self),t=now())
     883
     884 -class FIFO(Queue):
     885 -    def __init__(self,res,moni):
     886          Queue.__init__(self,res,moni)
     887
     888 -    def enter(self,obj):
     889          self.append(obj)
     890          if self.monit:
     891              self.moni.observe(len(self),t=now())
     892
     893 -    def enterGet(self,obj):
     894          self.enter(obj)
     895
     896 -    def enterPut(self,obj):
     897          self.enter(obj)
     898
     899 -    def leave(self):
     900          a= self.pop(0)
     901          if self.monit:
     902              self.moni.observe(len(self),t=now())
     903          return a
     904
     905 -class PriorityQ(FIFO):
     906      """Queue is always ordered according to priority.
     907      Higher value of priority attribute == higher priority.
     908      """
     909 -    def __init__(self,res,moni):
     910          FIFO.__init__(self,res,moni)
     911
     912 -    def enter(self,obj):
     913          """Handles request queue for Resource"""
     914          if len(self):
     915              ix=self.resource
     916              if self[-1]._priority[ix] >= obj._priority[ix]:
     917                  self.append(obj)
     918              else:
     919                  z=0
     920                  while self[z]._priority[ix] >= obj._priority[ix]:
     921                      z += 1
     922                  self.insert(z,obj)
     923          else:
     924              self.append(obj)
     925          if self.monit:
     926              self.moni.observe(len(self),t=now())
     927
     928 -    def enterGet(self,obj):
     929          """Handles getQ in Buffer"""
     930          if len(self):
     931              ix=self.resource
     932              #print "priority:",[x._priority[ix] for x in self]
     933              if self[-1]._getpriority[ix] >= obj._getpriority[ix]:
     934                  self.append(obj)
     935              else:
     936                  z=0
     937                  while self[z]._getpriority[ix] >= obj._getpriority[ix]:
     938                      z += 1
     939                  self.insert(z,obj)
     940          else:
     941              self.append(obj)
     942          if self.monit:
     943              self.moni.observe(len(self),t=now())
     944
     945 -    def enterPut(self,obj):
     946          """Handles putQ in Buffer"""
     947          if len(self):
     948              ix=self.resource
     949              #print "priority:",[x._priority[ix] for x in self]
     950              if self[-1]._putpriority[ix] >= obj._putpriority[ix]:
     951                  self.append(obj)
     952              else:
     953                  z=0
     954                  while self[z]._putpriority[ix] >= obj._putpriority[ix]:
     955                      z += 1
     956                  self.insert(z,obj)
     957          else:
     958              self.append(obj)
     959          if self.monit:
     960              self.moni.observe(len(self),t=now())
     961
     962 -class Resource(Lister):
     963      """Models shared, limited capacity resources with queuing;
     964      FIFO is default queuing discipline.
     965      """
     966
     967 -    def __init__(self,capacity=1,name="a_resource",unitName="units",
     968                   qType=FIFO,preemptable=0,monitored=False,monitorType=Monitor):
     969          """
     970          monitorType={Monitor(default)|Tally}
     971          """
     972          self.name=name          # resource name
     973          self.capacity=capacity  # resource units in this resource
     974          self.unitName=unitName  # type name of resource units
     975          self.n=capacity         # uncommitted resource units
     976          self.monitored=monitored
     977
     978          if self.monitored:           # Monitor waitQ, activeQ
     979              self.actMon=monitorType(name="Active Queue Monitor %s"%self.name,
     980                                   ylab="nr in queue",tlab="time")
     981              monact=self.actMon
     982              self.waitMon=monitorType(name="Wait Queue Monitor %s"%self.name,
     983                                   ylab="nr in queue",tlab="time")
     984              monwait=self.waitMon
     985          else:
     986              monwait=None
     987              monact=None
     988          self.waitQ=qType(self,monwait)
     989          self.preemptable=preemptable
     990          self.activeQ=qType(self,monact)
     991          self.priority_default=0
     992
     993 -    def _request(self,arg):
     994          """Process request event for this resource"""
     995          object=arg[1].who
     996          if len(arg[0]) == 4:        # yield request,self,resource,priority
     997              object._priority[self]=arg[0][3]
     998          else:                       # yield request,self,resource
     999              object._priority[self]=self.priority_default
    1000          if self.preemptable and self.n == 0: # No free resource
    1001              # test for preemption condition
    1002              preempt=object._priority[self] > self.activeQ[-1]._priority[self]
    1003              # If yes:
    1004              if preempt:
    1005                  z=self.activeQ[-1]
    1006                  # suspend lowest priority process being served
    1007                  ##suspended = z
    1008                  # record remaining service time
    1009                  z._remainService = z._nextTime - _t
    1010                  Process().cancel(z)
    1011                  # remove from activeQ
    1012                  self.activeQ.remove(z)
    1013                  # put into front of waitQ
    1014                  self.waitQ.insert(0,z)
    1015                  # if self is monitored, update waitQ monitor
    1016                  if self.monitored:
    1017                      self.waitMon.observe(len(self.waitQ),now())
    1018                  # record that it has been preempted
    1019                  z._preempted = 1
    1020                  # passivate re-queued process
    1021                  z._nextTime=None
    1022                  # assign resource unit to preemptor
    1023                  self.activeQ.enter(object)
    1024                  # post event notice for preempting process
    1025                  _e._post(_Action(who=object,generator=object._nextpoint),
    1026                           at=_t,prior=1)
    1027              else:
    1028                  self.waitQ.enter(object)
    1029                  # passivate queuing process
    1030                  object._nextTime=None
    1031          else: # treat non-preemption case
    1032              if self.n == 0:
    1033                  self.waitQ.enter(object)
    1034                  # passivate queuing process
    1035                  object._nextTime=None
    1036              else:
    1037                  self.n -= 1
    1038                  self.activeQ.enter(object)
    1039                  _e._post(_Action(who=object,generator=object._nextpoint),
    1040                  at=_t,prior=1)
    1041
    1042 -    def _release(self,arg):
    1043          """Process release request for this resource"""
    1044          self.n += 1
    1045          self.activeQ.remove(arg[1].who)
    1046          if self.monitored:
    1047              self.actMon.observe(len(self.activeQ),t=now())
    1048          #reactivate first waiting requestor if any; assign Resource to it
    1049          if self.waitQ:
    1050              object=self.waitQ.leave()
    1051              self.n -= 1             #assign 1 resource unit to object
    1052              self.activeQ.enter(object)
    1053              # if resource preemptable:
    1054              if self.preemptable:
    1055                  # if object had been preempted:
    1056                  if object._preempted:
    1057                      object._preempted = 0
    1058                      # reactivate object delay= remaining service time
    1059                      reactivate(object,delay=object._remainService)
    1060                  # else reactivate right away
    1061                  else:
    1062                      reactivate(object,delay=0,prior=1)
    1063              # else:
    1064              else:
    1065                  reactivate(object,delay=0,prior=1)
    1066          _e._post(_Action(who=arg[1].who,generator=arg[1].who._nextpoint),
    1067                  at=_t,prior=1)
    1068
    1069 -class Buffer(Lister):
    1070      """Abstract class for buffers
    1071      Blocks a process when a put would cause buffer overflow or a get would cause
    1072      buffer underflow.
    1073      Default queuing discipline for blocked processes is FIFO."""
    1074
    1075      priorityDefault=0
    1076 -    def __init__(self,name=None,capacity="unbounded",unitName="units",
    1077                  putQType=FIFO,getQType=FIFO,
    1078                  monitored=False,monitorType=Monitor,initialBuffered=None):
    1079          if capacity=="unbounded": capacity=sys.maxint
    1080          self.capacity=capacity
    1081          self.name=name
    1082          self.putQType=putQType
    1083          self.getQType=getQType
    1084          self.monitored=monitored
    1085          self.initialBuffered=initialBuffered
    1086          self.unitName=unitName
    1087          if self.monitored:
    1088              ## monitor for Producer processes' queue
    1089              self.putQMon=monitorType(name="Producer Queue Monitor %s"%self.name,
    1090                                      ylab="nr in queue",tlab="time")
    1091              ## monitor for Consumer processes' queue
    1092              self.getQMon=monitorType(name="Consumer Queue Monitor %s"%self.name,
    1093                                      ylab="nr in queue",tlab="time")
    1094              ## monitor for nr items in buffer
    1095              self.bufferMon=monitorType(name="Buffer Monitor %s"%self.name,
    1096                                      ylab="nr in buffer",tlab="time")
    1097          else:
    1098              self.putQMon=None
    1099              self.getQMon=None
    1100              self.bufferMon=None
    1101          self.putQ=self.putQType(res=self,moni=self.putQMon)
    1102          self.getQ=self.getQType(res=self,moni=self.getQMon)
    1103          if self.monitored:
    1104              self.putQMon.observe(y=len(self.putQ),t=now())
    1105              self.getQMon.observe(y=len(self.getQ),t=now())
    1106          self._putpriority={}
    1107          self._getpriority={}
    1108
    1109          def _put(self):
    1110              pass
    1111          def _get(self):
    1112              pass
    1113
    1114 -class Level(Buffer):
    1115      """Models buffers for processes putting/getting un-distinguishable items.
    1116      """
    1117 -    def getamount(self):
    1118          return self.nrBuffered
    1119
    1120 -    def gettheBuffer(self):
    1121          return self.nrBuffered
    1122
    1123      theBuffer=property(gettheBuffer)
    1124
    1125 -    def __init__(self,**pars):
    1126          Buffer.__init__(self,**pars)
    1127          if self.name is None:
    1128              self.name="a_level"   ## default name
    1129
    1130          if (type(self.capacity)!=type(1.0) and\
    1131                  type(self.capacity)!=type(1)) or\
    1132                  self.capacity<=0:
    1133                  raise FatalSimerror\
    1134                      ("Level: capacity parameter not a positive number > 0: %s"\
    1135                      %self.initialBuffered)
    1136
    1137          if type(self.initialBuffered)==type(1.0) or\
    1138                  type(self.initialBuffered)==type(1):
    1139              if self.initialBuffered>self.capacity:
    1140                  raise FatalSimerror("initialBuffered exceeds capacity")
    1141              if self.initialBuffered>=0:
    1142                  self.nrBuffered=self.initialBuffered ## nr items initially in buffer
    1143                                          ## buffer is just a counter (int type)
    1144              else:
    1145                  raise FatalSimerror\
    1146                  ("initialBuffered param of Level negative: %s"\
    1147                  %self.initialBuffered)
    1148          elif self.initialBuffered is None:
    1149              self.initialBuffered=0
    1150              self.nrBuffered=0
    1151          else:
    1152              raise FatalSimerror\
    1153                  ("Level: wrong type of initialBuffered (parameter=%s)"\
    1154                  %self.initialBuffered)
    1155          if self.monitored:
    1156              self.bufferMon.observe(y=self.amount,t=now())
    1157      amount=property(getamount)
    1158
    1159 -    def _put(self,arg):
    1160          """Handles put requests for Level instances"""
    1161          obj=arg[1].who
    1162          if len(arg[0]) == 5:        # yield put,self,buff,whattoput,priority
    1163              obj._putpriority[self]=arg[0][4]
    1164              whatToPut=arg[0][3]
    1165          elif len(arg[0]) == 4:      # yield get,self,buff,whattoput
    1166              obj._putpriority[self]=Buffer.priorityDefault #default
    1167              whatToPut=arg[0][3]
    1168          else:                       # yield get,self,buff
    1169              obj._putpriority[self]=Buffer.priorityDefault #default
    1170              whatToPut=1
    1171          if type(whatToPut)!=type(1) and type(whatToPut)!=type(1.0):
    1172              raise FatalSimerror("Level: put parameter not a number")
    1173          if not whatToPut>=0.0:
    1174              raise FatalSimerror("Level: put parameter not positive number")
    1175          whatToPutNr=whatToPut
    1176          if whatToPutNr+self.amount>self.capacity:
    1177              obj._nextTime=None      #passivate put requestor
    1178              obj._whatToPut=whatToPutNr
    1179              self.putQ.enterPut(obj)    #and queue, with size of put
    1180          else:
    1181              self.nrBuffered+=whatToPutNr
    1182              if self.monitored:
    1183                  self.bufferMon.observe(y=self.amount,t=now())
    1184              # service any getters waiting
    1185              # service in queue-order; do not serve second in queue before first
    1186              # has been served
    1187              while len(self.getQ) and self.amount>0:
    1188                  proc=self.getQ[0]
    1189                  if proc._nrToGet<=self.amount:
    1190                      proc.got=proc._nrToGet
    1191                      self.nrBuffered-=proc.got
    1192                      if self.monitored:
    1193                          self.bufferMon.observe(y=self.amount,t=now())
    1194                      self.getQ.takeout(proc) # get requestor's record out of queue
    1195                      _e._post(_Action(who=proc,generator=proc._nextpoint),
    1196                              at=_t) # continue a blocked get requestor
    1197                  else:
    1198                      break
    1199              _e._post(_Action(who=obj,generator=obj._nextpoint),
    1200                      at=_t,prior=1) # continue the put requestor
    1201
    1202 -    def _get(self,arg):
    1203          """Handles get requests for Level instances"""
    1204          obj=arg[1].who
    1205          obj.got=None
    1206          if len(arg[0]) == 5:        # yield get,self,buff,whattoget,priority
    1207              obj._getpriority[self]=arg[0][4]
    1208              nrToGet=arg[0][3]
    1209          elif len(arg[0]) == 4:      # yield get,self,buff,whattoget
    1210              obj._getpriority[self]=Buffer.priorityDefault #default
    1211              nrToGet=arg[0][3]
    1212          else:                       # yield get,self,buff
    1213              obj._getpriority[self]=Buffer.priorityDefault
    1214              nrToGet=1
    1215          if type(nrToGet)!=type(1.0) and type(nrToGet)!=type(1):
    1216              raise FatalSimerror\
    1217                  ("Level: get parameter not a number: %s"%nrToGet)
    1218          if nrToGet<0:
    1219              raise FatalSimerror\
    1220                  ("Level: get parameter not positive number: %s"%nrToGet)
    1221          if self.amount < nrToGet:
    1222              obj._nrToGet=nrToGet
    1223              self.getQ.enterGet(obj)
    1224              # passivate queuing process
    1225              obj._nextTime=None
    1226          else:
    1227              obj.got=nrToGet
    1228              self.nrBuffered-=nrToGet
    1229              if self.monitored:
    1230                  self.bufferMon.observe(y=self.amount,t=now())
    1231              _e._post(_Action(who=obj,generator=obj._nextpoint),
    1232                  at=_t,prior=1)
    1233              # reactivate any put requestors for which space is now available
    1234              # service in queue-order; do not serve second in queue before first
    1235              # has been served
    1236              while len(self.putQ): #test for queued producers
    1237                  proc=self.putQ[0]
    1238                  if proc._whatToPut+self.amount<=self.capacity:
    1239                      self.nrBuffered+=proc._whatToPut
    1240                      if self.monitored:
    1241                          self.bufferMon.observe(y=self.amount,t=now())
    1242                      self.putQ.takeout(proc)#requestor's record out of queue
    1243                      _e._post(_Action(who=proc,generator=proc._nextpoint),
    1244                              at=_t) # continue a blocked put requestor
    1245                  else:
    1246                      break
    1247
    1248 -class Store(Buffer):
    1249      """Models buffers for processes coupled by putting/getting distinguishable
    1250      items.
    1251      Blocks a process when a put would cause buffer overflow or a get would cause
    1252      buffer underflow.
    1253      Default queuing discipline for blocked processes is priority FIFO.
    1254      """
    1255 -    def getnrBuffered(self):
    1256          return len(self.theBuffer)
    1257      nrBuffered=property(getnrBuffered)
    1258
    1259 -    def getbuffered(self):
    1260          return self.theBuffer
    1261      buffered=property(getbuffered)
    1262
    1263 -    def __init__(self,**pars):
    1264          Buffer.__init__(self,**pars)
    1265          self.theBuffer=[]
    1266          if self.name is None:
    1267              self.name="a_store" ## default name
    1268          if type(self.capacity)!=type(1) or self.capacity<=0:
    1269              raise FatalSimerror\
    1270                  ("Store: capacity parameter not a positive integer > 0: %s"\
    1271                      %self.initialBuffered)
    1272          if type(self.initialBuffered)==type([]):
    1273              if len(self.initialBuffered)>self.capacity:
    1274                  raise FatalSimerror("initialBuffered exceeds capacity")
    1275              else:
    1276                  self.theBuffer[:]=self.initialBuffered##buffer==list of objects
    1277          elif self.initialBuffered is None:
    1278              self.theBuffer=[]
    1279          else:
    1280              raise FatalSimerror\
    1281                  ("Store: initialBuffered not a list")
    1282          if self.monitored:
    1283              self.bufferMon.observe(y=self.nrBuffered,t=now())
    1284          self._sort=None
    1285
    1286
    1287
    1288 -    def addSort(self,sortFunc):
    1289          """Adds buffer sorting to this instance of Store. It maintains
    1290          theBuffer sorted by the sortAttr attribute of the objects in the
    1291          buffer.
    1292          The user-provided 'sortFunc' must look like this:
    1293
    1294          def mySort(self,par):
    1295              tmplist=[(x.sortAttr,x) for x in par]
    1296              tmplist.sort()
    1297              return [x for (key,x) in tmplist]
    1298
    1299          """
    1300
    1301          self._sort=new.instancemethod(sortFunc,self,self.__class__)
    1302          self.theBuffer=self._sort(self.theBuffer)
    1303
    1304 -    def _put(self,arg):
    1305          """Handles put requests for Store instances"""
    1306          obj=arg[1].who
    1307          if len(arg[0]) == 5:        # yield put,self,buff,whattoput,priority
    1308              obj._putpriority[self]=arg[0][4]
    1309              whatToPut=arg[0][3]
    1310          elif len(arg[0]) == 4:      # yield put,self,buff,whattoput
    1311              obj._putpriority[self]=Buffer.priorityDefault #default
    1312              whatToPut=arg[0][3]
    1313          else:                       # error, whattoput missing
    1314              raise FatalSimerror("Item to put missing in yield put stmt")
    1315          if type(whatToPut)!=type([]):
    1316              raise FatalSimerror("put parameter is not a list")
    1317          whatToPutNr=len(whatToPut)
    1318          if whatToPutNr+self.nrBuffered>self.capacity:
    1319              obj._nextTime=None      #passivate put requestor
    1320              obj._whatToPut=whatToPut
    1321              self.putQ.enterPut(obj) #and queue, with items to put
    1322          else:
    1323              self.theBuffer.extend(whatToPut)
    1324              if not(self._sort is None):
    1325                  self.theBuffer=self._sort(self.theBuffer)
    1326              if self.monitored:
    1327                  self.bufferMon.observe(y=self.nrBuffered,t=now())
    1328              _e._post(_Action(who=obj,generator=obj._nextpoint),
    1329                  at=_t,prior=1) # continue the put requestor
    1330
    1331              # service any waiting getters
    1332              # service in queue order: do not serve second in queue before first
    1333              # has been served
    1334              block=False
    1335              buffQ=self.getQ
    1336              for getter in buffQ:
    1337                  if self.nrBuffered>0 and len(self.getQ):
    1338                      proc=getter
    1339                      if inspect.isfunction(proc._nrToGet):
    1340                          movCand=proc._nrToGet(self.theBuffer) #predicate parameter
    1341                          if movCand:
    1342                              proc.got=movCand[:]
    1343                              for i in movCand:
    1344                                  self.theBuffer.remove(i)
    1345                              self.getQ.takeout(proc)
    1346                              if self.monitored:
    1347                                  self.bufferMon.observe(y=self.nrBuffered,t=now())
    1348                              _e._post(_Action(who=proc,generator=proc._nextpoint),
    1349                                   at=_t) # continue a blocked get requestor
    1350                      else: #numerical parameter
    1351                          if not block and proc._nrToGet<=self.nrBuffered:
    1352                              nrToGet=proc._nrToGet
    1353                              proc.got=[]
    1354                              proc.got[:]=self.theBuffer[0:nrToGet]
    1355                              self.theBuffer[:]=self.theBuffer[nrToGet:]
    1356                              if self.monitored:
    1357                                  self.bufferMon.observe(y=self.nrBuffered,t=now())
    1358                              # take this get requestor's record out of queue:
    1359                              self.getQ.takeout(proc)
    1360                              _e._post(_Action(who=proc,generator=proc._nextpoint),
    1361                                      at=_t) # continue a blocked get requestor
    1362                          else:
    1363                              # block subsequent numerically specified get's in getQ
    1364                              # to prevent starvation of larger gets by smaller ones
    1365                              block=True
    1366                  else:
    1367                      break # either out of items in buffer or out of getters in getQ
    1368
    1369 -    def _get(self,arg):
    1370          """Handles get requests"""
    1371          filtfunc=None
    1372          obj=arg[1].who
    1373          obj.got=[]                  # the list of items retrieved by 'get'
    1374          if len(arg[0]) == 5:        # yield get,self,buff,whattoget,priority
    1375              obj._getpriority[self]=arg[0][4]
    1376              if inspect.isfunction(arg[0][3]):
    1377                  filtfunc=arg[0][3]
    1378              else:
    1379                  nrToGet=arg[0][3]
    1380          elif len(arg[0]) == 4:      # yield get,self,buff,whattoget
    1381              obj._getpriority[self]=Buffer.priorityDefault #default
    1382              if inspect.isfunction(arg[0][3]):
    1383                  filtfunc=arg[0][3]
    1384              else:
    1385                  nrToGet=arg[0][3]
    1386          else:                       # yield get,self,buff
    1387              obj._getpriority[self]=Buffer.priorityDefault
    1388              nrToGet=1
    1389          if not filtfunc: #number specifies nr items to get
    1390              if nrToGet<0:
    1391                  raise FatalSimerror\
    1392                      ("Store: get parameter not positive number: %s"%nrToGet)
    1393              if self.nrBuffered < nrToGet:
    1394                  obj._nrToGet=nrToGet
    1395                  self.getQ.enterGet(obj)
    1396                  # passivate/block queuing 'get' process
    1397                  obj._nextTime=None
    1398              else:
    1399                  for i in range(nrToGet):
    1400                      obj.got.append(self.theBuffer.pop(0)) # move items from
    1401                                                  # buffer to requesting process
    1402                  if self.monitored:
    1403                      self.bufferMon.observe(y=self.nrBuffered,t=now())
    1404                  _e._post(_Action(who=obj,generator=obj._nextpoint),
    1405                      at=_t,prior=1)
    1406                  # reactivate any put requestors for which space is now available
    1407                  # serve in queue order: do not serve second in queue before first
    1408                  # has been served
    1409                  while len(self.putQ):
    1410                      proc=self.putQ[0]
    1411                      if len(proc._whatToPut)+self.nrBuffered<=self.capacity:
    1412                          for i in proc._whatToPut:
    1413                              self.theBuffer.append(i) #move items to buffer
    1414                          if not(self._sort is None):
    1415                              self.theBuffer=self._sort(self.theBuffer)
    1416                          if self.monitored:
    1417                              self.bufferMon.observe(y=self.nrBuffered,t=now())
    1418                          self.putQ.takeout(proc) # dequeue requestor's record
    1419                          _e._post(_Action(who=proc,generator=proc._nextpoint),
    1420                                  at=_t) # continue a blocked put requestor
    1421                      else:
    1422                          break
    1423          else: # items to get determined by filtfunc
    1424              movCand=filtfunc(self.theBuffer)
    1425              if movCand: # get succeded
    1426                  _e._post(_Action(who=obj,generator=obj._nextpoint),
    1427                      at=_t,prior=1)
    1428                  obj.got=movCand[:]
    1429                  for item in movCand:
    1430                      self.theBuffer.remove(item)
    1431                  if self.monitored:
    1432                      self.bufferMon.observe(y=self.nrBuffered,t=now())
    1433                  # reactivate any put requestors for which space is now available
    1434                  # serve in queue order: do not serve second in queue before first
    1435                  # has been served
    1436                  while len(self.putQ):
    1437                      proc=self.putQ[0]
    1438                      if len(proc._whatToPut)+self.nrBuffered<=self.capacity:
    1439                          for i in proc._whatToPut:
    1440                              self.theBuffer.append(i) #move items to buffer
    1441                          if not(self._sort is None):
    1442                              self.theBuffer=self._sort(self.theBuffer)
    1443                          if self.monitored:
    1444                              self.bufferMon.observe(y=self.nrBuffered,t=now())
    1445                          self.putQ.takeout(proc) # dequeue requestor's record
    1446                          _e._post(_Action(who=proc,generator=proc._nextpoint),
    1447                                  at=_t) # continue a blocked put requestor
    1448                      else:
    1449                          break
    1450              else: # get did not succeed, block
    1451                  obj._nrToGet=filtfunc
    1452                  self.getQ.enterGet(obj)
    1453                  # passivate/block queuing 'get' process
    1454                  obj._nextTime=None
    1455
    1456
    1457
    1458
    1459 -class SimEvent(Lister):
    1460      """Supports one-shot signalling between processes. All processes waiting for an event to occur
    1461      get activated when its occurrence is signalled. From the processes queuing for an event, only
    1462      the first gets activated.
    1463      """
    1464 -    def __init__(self,name="a_SimEvent"):
    1465          self.name=name
    1466          self.waits=[]
    1467          self.queues=[]
    1468          self.occurred=False
    1469          self.signalparam=None
    1470
    1471 -    def signal(self,param=None):
    1472          """Produces a signal to self;
    1473          Fires this event (makes it occur).
    1474          Reactivates ALL processes waiting for this event. (Cleanup waits lists
    1475          of other events if wait was for an event-group (OR).)
    1476          Reactivates the first process for which event(s) it is queuing for
    1477          have fired. (Cleanup queues of other events if wait was for an event-group (OR).)
    1478          """
    1479          self.signalparam=param
    1480          if not self.waits and not self.queues:
    1481              self.occurred=True
    1482          else:
    1483              #reactivate all waiting processes
    1484              for p in self.waits:
    1485                  p[0].eventsFired.append(self)
    1486                  reactivate(p[0],prior=True)
    1487                  #delete waits entries for this process in other events
    1488                  for ev in p[1]:
    1489                      if ev!=self:
    1490                          if ev.occurred:
    1491                              p[0].eventsFired.append(ev)
    1492                          for iev in ev.waits:
    1493                              if iev[0]==p[0]:
    1494                                  ev.waits.remove(iev)
    1495                                  break
    1496              self.waits=[]
    1497              if self.queues:
    1498                  proc=self.queues.pop(0)[0]
    1499                  proc.eventsFired.append(self)
    1500                  reactivate(proc,prior=True)
    1501
    1502 -    def _wait(self,par):
    1503          """Consumes a signal if it has occurred, otherwise process 'proc'
    1504          waits for this event.
    1505          """
    1506          proc=par[0][1] #the process issuing the yield waitevent command
    1507          proc.eventsFired=[]
    1508          if not self.occurred:
    1509              self.waits.append([proc,[self]])
    1510              proc._nextTime=None #passivate calling process
    1511          else:
    1512              proc.eventsFired.append(self)
    1513              self.occurred=False
    1514              _e._post(_Action(who=proc,generator=proc._nextpoint),
    1515                  at=_t,prior=1)
    1516
    1517 -    def _waitOR(self,par):
    1518          """Handles waiting for an OR of events in a tuple/list.
    1519          """
    1520          proc=par[0][1]
    1521          evlist=par[0][2]
    1522          proc.eventsFired=[]
    1523          anyoccur=False
    1524          for ev in evlist:
    1525              if ev.occurred:
    1526                  anyoccur=True
    1527                  proc.eventsFired.append(ev)
    1528                  ev.occurred=False
    1529          if anyoccur: #at least one event has fired; continue process
    1530              _e._post(_Action(who=proc,generator=proc._nextpoint),
    1531                  at=_t,prior=1)
    1532
    1533          else: #no event in list has fired, enter process in all 'waits' lists
    1534              proc.eventsFired=[]
    1535              proc._nextTime=None #passivate calling process
    1536              for ev in evlist:
    1537                  ev.waits.append([proc,evlist])
    1538
    1539 -    def _queue(self,par):
    1540          """Consumes a signal if it has occurred, otherwise process 'proc'
    1541          queues for this event.
    1542          """
    1543          proc=par[0][1] #the process issuing the yield queueevent command
    1544          proc.eventsFired=[]
    1545          if not self.occurred:
    1546              self.queues.append([proc,[self]])
    1547              proc._nextTime=None #passivate calling process
    1548          else:
    1549              proc.eventsFired.append(self)
    1550              self.occurred=False
    1551              _e._post(_Action(who=proc,generator=proc._nextpoint),
    1552                  at=_t,prior=1)
    1553
    1554 -    def _queueOR(self,par):
    1555          """Handles queueing for an OR of events in a tuple/list.
    1556          """
    1557          proc=par[0][1]
    1558          evlist=par[0][2]
    1559          proc.eventsFired=[]
    1560          anyoccur=False
    1561          for ev in evlist:
    1562              if ev.occurred:
    1563                  anyoccur=True
    1564                  proc.eventsFired.append(ev)
    1565                  ev.occurred=False
    1566          if anyoccur: #at least one event has fired; continue process
    1567              _e._post(_Action(who=proc,generator=proc._nextpoint),
    1568                  at=_t,prior=1)
    1569
    1570          else: #no event in list has fired, enter process in all 'waits' lists
    1571              proc.eventsFired=[]
    1572              proc._nextTime=None #passivate calling process
    1573              for ev in evlist:
    1574                  ev.queues.append([proc,evlist])
    1575
    1576  ## begin waituntil functionality
    1577 -def _test():
    1578      """
    1579      Gets called by simulate after every event, as long as there are processes
    1580      waiting in condQ for a condition to be satisfied.
    1581      Tests the conditions for all waiting processes. Where condition satisfied,
    1582      reactivates that process immediately and removes it from queue.
    1583      """
    1584      global condQ
    1585      rList=[]
    1586      for el in condQ:
    1587          if el.cond():
    1588              rList.append(el)
    1589              reactivate(el)
    1590      for i in rList:
    1591          condQ.remove(i)
    1592
    1593      if not condQ:
    1594          _stopWUStepping()
    1595
    1596 -def _waitUntilFunc(proc,cond):
    1597      global condQ
    1598      """
    1599      Puts a process 'proc' waiting for a condition into a waiting queue.
    1600      'cond' is a predicate function which returns True if the condition is
    1601      satisfied.
    1602      """
    1603      if not cond():
    1604          condQ.append(proc)
    1605          proc.cond=cond
    1606          _startWUStepping()         #signal 'simulate' that a process is waiting
    1607          # passivate calling process
    1608          proc._nextTime=None
    1609      else:
    1610          #schedule continuation of calling process
    1611          _e._post(_Action(who=proc,generator=proc._nextpoint),
    1612                  at=_t,prior=1)
    1613
    1614
    1615  ##end waituntil functionality
    1616
    1617 -def scheduler(till=0):
    1618      """Schedules Processes/semi-coroutines until time 'till'.
    1619      Deprecated since version 0.5.
    1620      """
    1621      simulate(until=till)
    1622
    1623 -def holdfunc(a):
    1624      a[0][1]._hold(a)
    1625
    1626 -def requestfunc(a):
    1627      """Handles 'yield request,self,res' and 'yield (request,self,res),(<code>,self,par)'.
    1628      <code> can be 'hold' or 'waitevent'.
    1629      """
    1630      if type(a[0][0])==tuple:
    1631          ## Compound yield request statement
    1632          ## first tuple in ((request,self,res),(xx,self,yy))
    1633          b=a[0][0]
    1634          ## b[2]==res (the resource requested)
    1635          ##process the first part of the compound yield statement
    1636          ##a[1] is the _Action instance (a[1].who==process owner, a[1].process==generator)
    1637          b[2]._request(arg=(b,a[1]))
    1638          ##deal with add-on condition to command
    1639          ##Trigger processes for reneging
    1640          class _Holder(Process):
    1641              """Provides timeout process"""
    1642              def trigger(self,delay):
    1643                  yield hold,self,delay
    1644                  if not proc in b[2].activeQ:
    1645                      reactivate(proc)
    1646
    1647          class _EventWait(Process):
    1648              """Provides event waiting process"""
    1649              def trigger(self,event):
    1650                  yield waitevent,self,event
    1651                  if not proc in b[2].activeQ:
    1652                      a[1].who.eventsFired=self.eventsFired
    1653                      reactivate(proc)
    1654
    1655          #activate it
    1656          proc=a[0][0][1] # the process to be woken up
    1657          actCode=a[0][1][0]
    1658          if actCode==hold:
    1659              proc._holder=_Holder(name="RENEGE-hold for %s"%proc.name)
    1660              ##                                          the timeout delay
    1661              activate(proc._holder,proc._holder.trigger(a[0][1][2]))
    1662          elif actCode==waituntil:
    1663              raise FatalSimerror("Illegal code for reneging: waituntil")
    1664          elif actCode==waitevent:
    1665              proc._holder=_EventWait(name="RENEGE-waitevent for %s"%proc.name)
    1666              ##                                          the event
    1667              activate(proc._holder,proc._holder.trigger(a[0][1][2]))
    1668          elif actCode==queueevent:
    1669              raise FatalSimerror("Illegal code for reneging: queueevent")
    1670          else:
    1671              raise FatalSimerror("Illegal code for reneging %s"%actCode)
    1672      else:
    1673          ## Simple yield request command
    1674          a[0][2]._request(a)
    1675
    1676 -def releasefunc(a):
    1677      a[0][2]._release(a)
    1678
    1679 -def passivatefunc(a):
    1680      a[0][1]._passivate(a)
    1681
    1682 -def waitevfunc(a):
    1683      #if waiting for one event only (not a tuple or list)
    1684      evtpar=a[0][2]
    1685      if isinstance(evtpar,SimEvent):
    1686          a[0][2]._wait(a)
    1687      # else, if waiting for an OR of events (list/tuple):
    1688      else: #it should be a list/tuple of events
    1689          # call _waitOR for first event
    1690          evtpar[0]._waitOR(a)
    1691
    1692 -def queueevfunc(a):
    1693      #if queueing for one event only (not a tuple or list)
    1694      evtpar=a[0][2]
    1695      if isinstance(evtpar,SimEvent):
    1696          a[0][2]._queue(a)
    1697      #else, if queueing for an OR of events (list/tuple):
    1698      else: #it should be a list/tuple of events
    1699          # call _queueOR for first event
    1700          evtpar[0]._queueOR(a)
    1701
    1702 -def waituntilfunc(par):
    1703      _waitUntilFunc(par[0][1],par[0][2])
    1704
    1705 -def getfunc(a):
    1706      """Handles 'yield get,self,buffer,what,priority' and
    1707      'yield (get,self,buffer,what,priority),(<code>,self,par)'.
    1708      <code> can be 'hold' or 'waitevent'.
    1709      """
    1710      if type(a[0][0])==tuple:
    1711          ## Compound yield request statement
    1712          ## first tuple in ((request,self,res),(xx,self,yy))
    1713          b=a[0][0]
    1714          ## b[2]==res (the resource requested)
    1715          ##process the first part of the compound yield statement
    1716          ##a[1] is the _Action instance (a[1].who==process owner, a[1].process==generator)
    1717          b[2]._get(arg=(b,a[1]))
    1718          ##deal with add-on condition to command
    1719          ##Trigger processes for reneging
    1720          class _Holder(Process):
    1721              """Provides timeout process"""
    1722              def trigger(self,delay):
    1723                  yield hold,self,delay
    1724                  #if not proc in b[2].activeQ:
    1725                  if proc in b[2].getQ:
    1726                      reactivate(proc)
    1727
    1728          class _EventWait(Process):
    1729              """Provides event waiting process"""
    1730              def trigger(self,event):
    1731                  yield waitevent,self,event
    1732                  if proc in b[2].getQ:
    1733                      a[1].who.eventsFired=self.eventsFired
    1734                      reactivate(proc)
    1735
    1736          #activate it
    1737          proc=a[0][0][1] # the process to be woken up
    1738          actCode=a[0][1][0]
    1739          if actCode==hold:
    1740              proc._holder=_Holder("RENEGE-hold for %s"%proc.name)
    1741              ##                                          the timeout delay
    1742              activate(proc._holder,proc._holder.trigger(a[0][1][2]))
    1743          elif actCode==waituntil:
    1744              raise FatalSimerror("Illegal code for reneging: waituntil")
    1745          elif actCode==waitevent:
    1746              proc._holder=_EventWait(proc.name)
    1747              ##                                          the event
    1748              activate(proc._holder,proc._holder.trigger(a[0][1][2]))
    1749          elif actCode==queueevent:
    1750              raise FatalSimerror("Illegal code for reneging: queueevent")
    1751          else:
    1752              raise FatalSimerror("Illegal code for reneging %s"%actCode)
    1753      else:
    1754          ## Simple yield request command
    1755          a[0][2]._get(a)
    1756
    1757
    1758 -def putfunc(a):
    1759      """Handles 'yield put' (simple and compound hold/waitevent)
    1760      """
    1761      if type(a[0][0])==tuple:
    1762          ## Compound yield request statement
    1763          ## first tuple in ((request,self,res),(xx,self,yy))
    1764          b=a[0][0]
    1765          ## b[2]==res (the resource requested)
    1766          ##process the first part of the compound yield statement
    1767          ##a[1] is the _Action instance (a[1].who==process owner, a[1].process==generator)
    1768          b[2]._put(arg=(b,a[1]))
    1769          ##deal with add-on condition to command
    1770          ##Trigger processes for reneging
    1771          class _Holder(Process):
    1772              """Provides timeout process"""
    1773              def trigger(self,delay):
    1774                  yield hold,self,delay
    1775                  #if not proc in b[2].activeQ:
    1776                  if proc in b[2].putQ:
    1777                      reactivate(proc)
    1778
    1779          class _EventWait(Process):
    1780              """Provides event waiting process"""
    1781              def trigger(self,event):
    1782                  yield waitevent,self,event
    1783                  if proc in b[2].putQ:
    1784                      a[1].who.eventsFired=self.eventsFired
    1785                      reactivate(proc)
    1786
    1787          #activate it
    1788          proc=a[0][0][1] # the process to be woken up
    1789          actCode=a[0][1][0]
    1790          if actCode==hold:
    1791              proc._holder=_Holder("RENEGE-hold for %s"%proc.name)
    1792              ##                                          the timeout delay
    1793              activate(proc._holder,proc._holder.trigger(a[0][1][2]))
    1794          elif actCode==waituntil:
    1795              raise FatalSimerror("Illegal code for reneging: waituntil")
    1796          elif actCode==waitevent:
    1797              proc._holder=_EventWait("RENEGE-waitevent for %s"%proc.name)
    1798              ##                                          the event
    1799              activate(proc._holder,proc._holder.trigger(a[0][1][2]))
    1800          elif actCode==queueevent:
    1801              raise FatalSimerror("Illegal code for reneging: queueevent")
    1802          else:
    1803              raise FatalSimerror("Illegal code for reneging %s"%actCode)
    1804      else:
    1805          ## Simple yield request command
    1806          a[0][2]._put(a)
    1807
    1808 -def simulate(until=0,real_time=False,rel_speed=1):
    1809      """Schedules Processes/semi-coroutines until time 'until'"""
    1810
    1811      """Gets called once. Afterwards, co-routines (generators) return by
    1812      'yield' with a cargo:
    1813      yield hold, self, <delay>: schedules the "self" process for activation
    1814                                 after <delay> time units.If <,delay> missing,
    1815                                 same as "yield hold,self,0"
    1816
    1817      yield passivate,self    :  makes the "self" process wait to be re-activated
    1818
    1819      yield request,self,<Resource>[,<priority>]: request 1 unit from <Resource>
    1820          with <priority> pos integer (default=0)
    1821
    1822      yield release,self,<Resource> : release 1 unit to <Resource>
    1823
    1824      yield waitevent,self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]:
    1825          wait for one or more of several events
    1826
    1827
    1828      yield queueevent,self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]:
    1829          queue for one or more of several events
    1830
    1831      yield waituntil,self,cond : wait for arbitrary condition
    1832
    1833      yield get,self,<buffer>[,<WhatToGet>[,<priority>]]
    1834          get <WhatToGet> items from buffer (default=1);
    1835          <WhatToGet> can be a pos integer or a filter function
    1836          (Store only)
    1837
    1838      yield put,self,<buffer>[,<WhatToPut>[,priority]]
    1839          put <WhatToPut> items into buffer (default=1);
    1840          <WhatToPut> can be a pos integer (Level) or a list of objects
    1841          (Store)
    1842
    1843      EXTENSIONS:
    1844      Request with timeout reneging:
    1845      yield (request,self,<Resource>),(hold,self,<patience>) :
    1846          requests 1 unit from <Resource>. If unit not acquired in time period
    1847          <patience>, self leaves waitQ (reneges).
    1848
    1849      Request with event-based reneging:
    1850      yield (request,self,<Resource>),(waitevent,self,<eventlist>):
    1851          requests 1 unit from <Resource>. If one of the events in <eventlist> occurs before unit
    1852          acquired, self leaves waitQ (reneges).
    1853
    1854      Get with timeout reneging (for Store and Level):
    1855      yield (get,self,<buffer>,nrToGet etc.),(hold,self,<patience>)
    1856          requests <nrToGet> items/units from <buffer>. If not acquired <nrToGet> in time period
    1857          <patience>, self leaves <buffer>.getQ (reneges).
    1858
    1859      Get with event-based reneging (for Store and Level):
    1860      yield (get,self,<buffer>,nrToGet etc.),(waitevent,self,<eventlist>)
    1861          requests <nrToGet> items/units from <buffer>. If not acquired <nrToGet> before one of
    1862          the events in <eventlist> occurs, self leaves <buffer>.getQ (reneges).
    1863
    1864
    1865
    1866      Event notices get posted in event-list by scheduler after "yield" or by
    1867      "activate"/"reactivate" functions.
    1868
    1869      if real_time==True, the simulation time and real (clock) time get
    1870      synchronized as much as possible. rel_speed is the ratio simulation time
    1871      over clock time(seconds). Example: rel_speed==100: 100 simulation time units take
    1872      1 second clock time.
    1873
    1874      """
    1875      global _endtime,_e,_stop,_t,_wustep
    1876      _stop=False
    1877
    1878      if _e is None:
    1879          raise FatalSimerror("Simulation not initialized")
    1880      _e.real_time=real_time
    1881      _e.rel_speed=rel_speed
    1882      _e.rtlast = wallclock()
    1883      _e.stlast = 0
    1884      if _e.events == {}:
    1885          message="SimPy: No activities scheduled"
    1886          return message
    1887
    1888      _endtime=until
    1889      message="SimPy: Normal exit"
    1890      dispatch={hold:holdfunc,request:requestfunc,release:releasefunc,
    1891                passivate:passivatefunc,waitevent:waitevfunc,queueevent:queueevfunc,
    1892                waituntil:waituntilfunc,get:getfunc,put:putfunc}
    1893      commandcodes=dispatch.keys()
    1894      commandwords={hold:"hold",request:"request",release:"release",passivate:"passivate",
    1895          waitevent:"waitevent",queueevent:"queueevent",waituntil:"waituntil",
    1896          get:"get",put:"put"}
    1897      while not _stop and _t<=_endtime:
    1898          try:
    1899              a=_e._nextev()
    1900              if not a[0] is None:
    1901                  ## 'a' is tuple "(<yield command>, <action>)"
    1902                  if type(a[0][0])==tuple:
    1903                      ##allowing for yield (request,self,res),(waituntil,self,cond)
    1904                      command=a[0][0][0]
    1905                  else:
    1906                      command = a[0][0]
    1907                  if __debug__:
    1908                      if not command in commandcodes:
    1909                          raise FatalSimerror("Illegal command: yield %s"%command)
    1910                  dispatch[command](a)
    1911          except FatalSimerror,error:
    1912              print "SimPy: "+error.value
    1913              sys.exit(1)
    1914          except Simerror,error:
    1915              message="SimPy: "+error.value
    1916              _stop = True
    1917          if _wustep:
    1918              _test()
    1919      _stopWUStepping()
    1920      _e=None
    1921      return message
    1922
    1923
    1924  if __name__ == "__main__":
    1925      print "SimPy.SimulationRT %s" %__version__
    1926      ############# Test/demo functions #############
    1927 -    def test_demo():
    1928          class Aa(Process):
    1929              sequIn=[]
    1930              sequOut=[]
    1931              def __init__(self,holdtime,name):
    1932                  Process.__init__(self,name)
    1933                  self.holdtime=holdtime
    1934
    1935              def life(self,priority):
    1936                  for i in range(1):
    1937                      Aa.sequIn.append(self.name)
    1938                      print now(),rrr.name,"waitQ:",len(rrr.waitQ),"activeQ:",\
    1939                            len(rrr.activeQ)
    1940                      print "waitQ: ",[(k.name,k._priority[rrr]) for k in rrr.waitQ]
    1941                      print "activeQ: ",[(k.name,k._priority[rrr]) \
    1942                                 for k in rrr.activeQ]
    1943                      assert rrr.n+len(rrr.activeQ)==rrr.capacity, \
    1944                     "Inconsistent resource unit numbers"
    1945                      print now(),self.name,"requests 1 ", rrr.unitName
    1946                      yield request,self,rrr,priority
    1947                      print now(),self.name,"has 1 ",rrr.unitName
    1948                      print now(),rrr.name,"waitQ:",len(rrr.waitQ),"activeQ:",\
    1949                            len(rrr.activeQ)
    1950                      print now(),rrr.name,"waitQ:",len(rrr.waitQ),"activeQ:",\
    1951                            len(rrr.activeQ)
    1952                      assert rrr.n+len(rrr.activeQ)==rrr.capacity, \
    1953                     "Inconsistent resource unit numbers"
    1954                      yield hold,self,self.holdtime
    1955                      print now(),self.name,"gives up 1",rrr.unitName
    1956                      yield release,self,rrr
    1957                      Aa.sequOut.append(self.name)
    1958                      print now(),self.name,"has released 1 ",rrr.unitName
    1959                      print "waitQ: ",[(k.name,k._priority[rrr]) for k in rrr.waitQ]
    1960                      print now(),rrr.name,"waitQ:",len(rrr.waitQ),"activeQ:",\
    1961                            len(rrr.activeQ)
    1962                      assert rrr.n+len(rrr.activeQ)==rrr.capacity, \
    1963                             "Inconsistent resource unit numbers"
    1964
    1965          class Observer(Process):
    1966              def __init__(self):
    1967                  Process.__init__(self)
    1968
    1969              def observe(self,step,processes,res):
    1970                  while now()<11:
    1971                      for i in processes:
    1972                          print " %s %s: act:%s, pass:%s, term: %s,interr:%s, qu:%s"\
    1973                                %(now(),i.name,i.active(),i.passive(),i.terminated()\
    1974                              ,i.interrupted(),i.queuing(res))
    1975                      print
    1976                      yield hold,self,step
    1977
    1978          print"\n+++test_demo output"
    1979          print "****First case == priority queue, resource service not preemptable"
    1980          initialize()
    1981          rrr=Resource(5,name="Parking",unitName="space(s)", qType=PriorityQ,
    1982                       preemptable=0)
    1983          procs=[]
    1984          for i in range(10):
    1985              z=Aa(holdtime=i,name="Car "+str(i))
    1986              procs.append(z)
    1987              activate(z,z.life(priority=i))
    1988          o=Observer()
    1989          activate(o,o.observe(1,procs,rrr))
    1990          a=simulate(until=10000,real_time=True,rel_speed=1)
    1991          print a
    1992          print "Input sequence: ",Aa.sequIn
    1993          print "Output sequence: ",Aa.sequOut
    1994
    1995          print "\n****Second case == priority queue, resource service preemptable"
    1996          initialize()
    1997          rrr=Resource(5,name="Parking",unitName="space(s)", qType=PriorityQ,
    1998                       preemptable=1)
    1999          procs=[]
    2000          for i in range(10):
    2001              z=Aa(holdtime=i,name="Car "+str(i))
    2002              procs.append(z)
    2003              activate(z,z.life(priority=i))
    2004          o=Observer()
    2005          activate(o,o.observe(1,procs,rrr))
    2006          Aa.sequIn=[]
    2007          Aa.sequOut=[]
    2008          a=simulate(until=10000)
    2009          print a
    2010          print "Input sequence: ",Aa.sequIn
    2011          print "Output sequence: ",Aa.sequOut
    2012
    2013 -    def test_interrupt():
    2014          class Bus(Process):
    2015              def __init__(self,name):
    2016                  Process.__init__(self,name)
    2017
    2018              def operate(self,repairduration=0):
    2019                  print now(),rtnow(),">> %s starts" %(self.name)
    2020                  tripleft = 1000
    2021                  while tripleft > 0:
    2022                      yield hold,self,tripleft
    2023                      if self.interrupted():
    2024                          print "interrupted by %s" %self.interruptCause.name
    2025                          print "%s(%s): %s breaks down " %(now(),rtnow(),self.name)
    2026                          tripleft=self.interruptLeft
    2027                          self.interruptReset()
    2028                          print "tripleft ",tripleft
    2029                          reactivate(br,delay=repairduration) # breakdowns only during operation
    2030                          yield hold,self,repairduration
    2031                          print now(),rtnow()," repaired"
    2032                      else:
    2033                          break # no breakdown, ergo bus arrived
    2034                  print now(),"<< %s done" %(self.name)
    2035
    2036          class Breakdown(Process):
    2037              def __init__(self,myBus):
    2038                  Process.__init__(self,name="Breakdown "+myBus.name)
    2039                  self.bus=myBus
    2040
    2041              def breakBus(self,interval):
    2042
    2043                  while True:
    2044                      yield hold,self,interval
    2045                      if self.bus.terminated(): break
    2046                      self.interrupt(self.bus)
    2047
    2048          print"\n\n+++test_interrupt"
    2049          initialize()
    2050          b=Bus("Bus 1")
    2051          activate(b,b.operate(repairduration=20))
    2052          br=Breakdown(b)
    2053          activate(br,br.breakBus(200))
    2054          print simulate(until=4000,real_time=True,rel_speed=200)
    2055
    2056 -    def testSimEvents():
    2057          class Waiter(Process):
    2058              def waiting(self,theSignal):
    2059                  while True:
    2060                      yield waitevent,self,theSignal
    2061                      print "%s: process '%s' continued after waiting for %s"%(now(),self.name,theSignal.name)
    2062                      yield queueevent,self,theSignal
    2063                      print "%s: process '%s' continued after queueing for %s"%(now(),self.name,theSignal.name)
    2064
    2065          class ORWaiter(Process):
    2066              def waiting(self,signals):
    2067                  while True:
    2068                      yield waitevent,self,signals
    2069                      print now(),"one of %s signals occurred"%[x.name for x in signals]
    2070                      print "\t%s (fired/param)"%[(x.name,x.signalparam) for x in self.eventsFired]
    2071                      yield hold,self,1
    2072
    2073          class Caller(Process):
    2074              def calling(self):
    2075                  while True:
    2076                      signal1.signal("wake up!")
    2077                      print "%s: signal 1 has occurred"%now()
    2078                      yield hold,self,10
    2079                      signal2.signal("and again")
    2080                      signal2.signal("sig 2 again")
    2081                      print "%s: signal1, signal2 have occurred"%now()
    2082                      yield hold,self,10
    2083          print"\n\n+++testSimEvents output"
    2084          initialize()
    2085          signal1=SimEvent("signal 1")
    2086          signal2=SimEvent("signal 2")
    2087          signal1.signal("startup1")
    2088          signal2.signal("startup2")
    2089          w1=Waiter("waiting for signal 1")
    2090          activate(w1,w1.waiting(signal1))
    2091          w2=Waiter("waiting for signal 2")
    2092          activate(w2,w2.waiting(signal2))
    2093          w3=Waiter("also waiting for signal 2")
    2094          activate(w3,w3.waiting(signal2))
    2095          w4=ORWaiter("waiting for either signal 1 or signal 2")
    2096          activate(w4,w4.waiting([signal1,signal2]),prior=True)
    2097          c=Caller("Caller")
    2098          activate(c,c.calling())
    2099          print simulate(until=100)
    2100
    2101 -    def testwaituntil():
    2102          """
    2103          Demo of waitUntil capability.
    2104
    2105          Scenario:
    2106          Three workers require sets of tools to do their jobs. Tools are shared, scarce
    2107          resources for which they compete.
    2108          """
    2109
    2110
    2111          class Worker(Process):
    2112              def __init__(self,name,heNeeds=[]):
    2113                  Process.__init__(self,name)
    2114                  self.heNeeds=heNeeds
    2115              def work(self):
    2116
    2117                  def workerNeeds():
    2118                      for item in self.heNeeds:
    2119                          if item.n==0:
    2120                              return False
    2121                      return True
    2122
    2123                  while now()<8*60:
    2124                      yield waituntil,self,workerNeeds
    2125                      for item in self.heNeeds:
    2126                          yield request,self,item
    2127                      print "%s %s has %s and starts job" %(now(),self.name,
    2128                          [x.name for x in self.heNeeds])
    2129                      yield hold,self,random.uniform(10,30)
    2130                      for item in self.heNeeds:
    2131                          yield release,self,item
    2132                      yield hold,self,2 #rest
    2133
    2134          print "\n+++\nwaituntil demo output"
    2135          initialize()
    2136          brush=Resource(capacity=1,name="brush")
    2137          ladder=Resource(capacity=2,name="ladder")
    2138          hammer=Resource(capacity=1,name="hammer")
    2139          saw=Resource(capacity=1,name="saw")
    2140          painter=Worker("painter",[brush,ladder])
    2141          activate(painter,painter.work())
    2142          roofer=Worker("roofer",[hammer,ladder,ladder])
    2143          activate(roofer,roofer.work())
    2144          treeguy=Worker("treeguy",[saw,ladder])
    2145          activate(treeguy,treeguy.work())
    2146          for who in (painter,roofer,treeguy):
    2147              print "%s needs %s for his job" %(who.name,[x.name for x in who.heNeeds])
    2148          print
    2149          print simulate(until=9*60)
    2150      test_demo()
    2151      # Run tests
    2152      test_interrupt()
    2153      testSimEvents()
    2154      testwaituntil()
    2155