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

Source Code for Module SimPy.Simulation

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