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

Source Code for Module SimPy.SimulationStep

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