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

Source Code for Module SimPy.SimulationRT

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