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

Source Code for Module SimPy.SimulationRT

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