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