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

Source Code for Module SimPy.SimulationRT

  1  #!/usr / bin / env python 
  2  # $Revision: 163 $ $Date: 2008-12-15 12:47:44 +0100 (Mo, 15 Dez 2008) $ kgm 
  3  """SimulationRT 2.0 Provides synchronization of real time and SimPy simulation time. 
  4  Implements SimPy Processes, resources, and the backbone simulation scheduling 
  5  by coroutine calls.  
  6  Based on generators (Python 2.3 and later; not 3.0) 
  7   
  8  LICENSE: 
  9  Copyright (C) 2002, 2005, 2006, 2007, 2008  Klaus G. Muller, Tony Vignaux 
 10  mailto: kgmuller@xs4all.nl and Tony.Vignaux@vuw.ac.nz 
 11   
 12      This library is free software; you can redistribute it and / or 
 13      modify it under the terms of the GNU Lesser General Public 
 14      License as published by the Free Software Foundation; either 
 15      version 2.1 of the License, or (at your option) any later version. 
 16   
 17      This library is distributed in the hope that it will be useful, 
 18      but WITHOUT ANY WARRANTY; without even the implied warranty of 
 19      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 20      Lesser General Public License for more details. 
 21   
 22      You should have received a copy of the GNU Lesser General Public 
 23      License along with this library; if not, write to the Free Software 
 24      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111 - 1307  USA 
 25  END OF LICENSE 
 26   
 27   
 28  **Change history:** 
 29      4 / 8/2003: - Experimental introduction of synchronization of simulation 
 30                  time and real time (idea of Geoff Jarrad of CSIRO -- thanks, 
 31                  Geoff!). 
 32                  * Changes made to class EvlistRT, _nextev(), simulate() 
 33   
 34      Dec 11, 2003: 
 35              - Updated to Simulation 1.4alpha API 
 36   
 37      13 Dec 2003: Merged in Monitor and Histogram 
 38   
 39      27 Feb 2004: Repaired bug in activeQ monitor of class Resource. Now actMon 
 40                   correctly records departures from activeQ. 
 41   
 42      19 May 2004: Added erroneously omitted Histogram class. 
 43   
 44      5 Sep 2004: Added SimEvents synchronization constructs 
 45       
 46      17 Sep 2004: Added waituntil synchronization construct 
 47       
 48      28 Sep 2004: Changed handling of real time -- now uses time.clock for Win32, and 
 49                   time.time for all other OS (works better on Linux, Unix). 
 50   
 51      01 Dec 2004: SimPy version 1.5 
 52                   Changes in this module: Repaired SimEvents bug re proc.eventsFired 
 53   
 54      12 Jan 2005: SimPy version 1.5.1 
 55                   Changes in this module: Monitor objects now have a default name 
 56                                           'a_Monitor' 
 57                                            
 58      29 Mar 2005: Start SimPy 1.6: compound 'yield request' statements 
 59       
 60      05 Jun 2005: Fixed bug in _request method -- waitMon did not work properly in 
 61                   preemption case 
 62                    
 63      09 Jun 2005: Added test in 'activate' to see whether 'initialize()' was called first. 
 64       
 65      23 Aug 2005: - Added Tally data collection class 
 66                   - Adjusted Resource to work with Tally 
 67                   - Redid function allEventNotices() (returns prettyprinted string with event 
 68                     times and names of process instances 
 69                   - Added function allEventTimes (returns event times of all scheduled events) 
 70                    
 71      16 Mar 2006: - Added Store and Level classes 
 72                   - Added 'yield get' and 'yield put' 
 73                    
 74      10 May 2006: - Repaired bug in Store._get method 
 75                   - Repaired Level to allow initialBuffered have float value 
 76                   - Added type test for Level get parameter 'nrToGet' 
 77                    
 78      06 Jun 2006: - To improve pretty - printed output of 'Level' objects, changed attribute 
 79                     _nrBuffered to nrBuffered (synonym for amount property) 
 80                   - To improve pretty - printed output of 'Store' objects, added attribute 
 81                     buffered (which refers to _theBuffer) 
 82                      
 83      25 Aug 2006: - Start of version 1.8 
 84                   - made 'version' public 
 85                   - corrected condQ initialization bug 
 86                    
 87      30 Sep 2006: - Introduced checks to ensure capacity of a Buffer > 0 
 88                   - Removed from __future__ import (so Python 2.3 or later needed) 
 89                   
 90      15 Oct 2006: - Added code to register all Monitors and all Tallies in variables 
 91                     'allMonitors' and 'allTallies' 
 92                   - Added function 'startCollection' to activate Monitors and Tallies at a 
 93                     specified time (e.g. after warmup period) 
 94                   - Moved all test / demo programs to after 'if __name__ == '__main__':'. 
 95                   
 96      17 Oct 2006: - Added compound 'put' and 'get' statements for Level and Store. 
 97       
 98      18 Oct 2006: - Repaired bug: self.eventsFired now gets set after an event fires 
 99                     in a compound yield get / put with a waitevent clause (reneging case). 
100                      
101      21 Oct 2006: - Introduced Store 'yield get' with a filter function. 
102                   
103      22 Oct 2006: - Repaired bug in prettyprinting of Store objects (the buffer  
104                     content==._theBuffer was not shown) by changing ._theBuffer  
105                     to .theBuffer. 
106                   
107      04 Dec 2006: - Added printHistogram method to Tally and Monitor (generates 
108                     table - form histogram) 
109                       
110      07 Dec 2006: - Changed the __str__ method of Histogram to print a table  
111                     (like printHistogram). 
112       
113      18 Dec 2006: - Added trace printing of Buffers' 'unitName' for yield get and put. 
114       
115      09 Jun 2007: - Cleaned out all uses of 'object' to prevent name clash. 
116       
117      18 Nov 2007: - Start of 1.9 development 
118                   - Added 'start' method (alternative to activate) to Process 
119                    
120      22 Nov 2007: - Major change to event list handling to speed up larger models: 
121                      * Drop dictionary 
122                      * Replace bisect by heapq 
123                      * Mark cancelled event notices in unpost and skip them in 
124                        nextev (great idea of Tony Vignaux)) 
125                         
126      4 Dec 2007: - Added twVariance calculation for both Monitor and Tally (gav) 
127       
128      5 Dec 2007: - Changed name back to timeVariance (gav) 
129       
130      1 Mar 2008: - Start of 1.9.1 bugfix release 
131                  - Delete circular reference in Process instances when event  
132                    notice has been processed (caused much circular garbage) 
133                     
134      2 Apr 2008: - Repair of wallclock synchronisation algorithm 
135       
136      10 Aug 2008: - Renamed __Evlist to EvlistRT and let it inherit from 
137                     Evlist (from Simulation.py) (Stefan Scherfke) 
138                   - New class SimulationRT contains the old method simulate 
139                   - Removed everything else and import it from Simulation.py 
140       
141  """ 
142  import time 
143   
144  from SimPy.Simulation import * 
145   
146   
147  __TESTING = False 
148  version = __version__ = '2.0 $Revision: 163 $ $Date: 2008-12-15 12:47:44 +0100 (Mo, 15 Dez 2008) $' 
149  if __TESTING:  
150      print 'SimPy.SimulationRT %s' %__version__, 
151      if __debug__: 
152          print '__debug__ on' 
153      else: 
154          print 
155   
156           
157 -class EvlistRT(Evlist):
158 """Defines event list and operations on it"""
159 - def __init__(self, sim):
160 # always sorted list of events (sorted by time, priority) 161 # make heapq 162 self.sim = sim 163 self.timestamps = [] 164 self.sortpr = 0 165 self.real_time = False 166 self.rel_speed = 1 167 self.rtlast = self.sim.wallclock() 168 self.stlast = 0
169
170 - def _nextev(self):
171 """Retrieve next event from event list""" 172 noActiveNotice = True 173 ## Find next event notice which is not marked cancelled 174 while noActiveNotice: 175 if self.timestamps: 176 ## ignore priority value 177 (_tnotice, p,nextEvent, cancelled) = hq.heappop(self.timestamps) 178 noActiveNotice = cancelled 179 else: 180 raise Simerror('No more events at time %s' % self.sim._t) 181 nextEvent._rec = None 182 self.sim._t = _tnotice 183 ## Calculate any wait time 184 ## event clock time = rtlast + (sim_time - stlast) / rel_speed 185 ## delay = (1.0 * earliest / self.rel_speed) - self.sim.rtnow() 186 if self.real_time: 187 ## delay = next_time - self.sim.wallclock() 188 delay = (1.0 * self.sim._t / self.rel_speed) - self.sim.rtnow() 189 if delay > 0: 190 time.sleep(delay) 191 self.rtlast = self.sim.wallclock() 192 self.stlast = self.sim._t 193 if self.sim._t > self.sim._endtime: 194 self.sim._t = self.sim._endtime 195 self.sim._stop = True 196 return (None,) 197 try: 198 resultTuple = nextEvent._nextpoint.next() 199 except StopIteration: 200 nextEvent._nextpoint = None 201 nextEvent._terminated = True 202 nextEvent._nextTime = None 203 resultTuple = None 204 return (resultTuple, nextEvent)
205 206
207 -class SimulationRT(Simulation):
208
209 - def __init__(self):
210 if sys.platform == 'win32': #take care of differences in clock accuracy 211 self.wallclock = time.clock 212 else: 213 self.wallclock = time.time 214 self.rtstart = self.wallclock() 215 Simulation.__init__(self) 216 self.initialize()
217
218 - def initialize(self):
219 Simulation.initialize(self) 220 self._e = EvlistRT(self)
221
222 - def rtnow(self):
223 return self.wallclock() - self.rtstart
224
225 - def rtset(self, rel_speed = 1):
226 """resets the the ratio simulation time over clock time(seconds). 227 """ 228 if _e is None: 229 raise FatalSimerror('Fatal SimPy error: Simulation not initialized') 230 _e.rel_speed = rel_speed
231
232 - def simulate(self, until = 0, real_time = False, rel_speed = 1):
233 """Schedules Processes / semi - coroutines until time 'until'""" 234 235 """Gets called once. Afterwards, co - routines (generators) return by 236 'yield' with a cargo: 237 yield hold, self, <delay>: schedules the 'self' process for activation 238 after < delay > time units.If <,delay > missing, 239 same as 'yield hold, self, 0' 240 241 yield passivate, self : makes the 'self' process wait to be re - activated 242 243 yield request, self,<Resource > [,<priority>]: request 1 unit from < Resource> 244 with < priority > pos integer (default = 0) 245 246 yield release, self,<Resource> : release 1 unit to < Resource> 247 248 yield waitevent, self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]: 249 wait for one or more of several events 250 251 252 yield queueevent, self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]: 253 queue for one or more of several events 254 255 yield waituntil, self, cond : wait for arbitrary condition 256 257 yield get, self,<buffer > [,<WhatToGet > [,<priority>]] 258 get < WhatToGet > items from buffer (default = 1); 259 <WhatToGet > can be a pos integer or a filter function 260 (Store only) 261 262 yield put, self,<buffer > [,<WhatToPut > [,priority]] 263 put < WhatToPut > items into buffer (default = 1); 264 <WhatToPut > can be a pos integer (Level) or a list of objects 265 (Store) 266 267 EXTENSIONS: 268 Request with timeout reneging: 269 yield (request, self,<Resource>),(hold, self,<patience>) : 270 requests 1 unit from < Resource>. If unit not acquired in time period 271 <patience>, self leaves waitQ (reneges). 272 273 Request with event - based reneging: 274 yield (request, self,<Resource>),(waitevent, self,<eventlist>): 275 requests 1 unit from < Resource>. If one of the events in < eventlist > occurs before unit 276 acquired, self leaves waitQ (reneges). 277 278 Get with timeout reneging (for Store and Level): 279 yield (get, self,<buffer>,nrToGet etc.),(hold, self,<patience>) 280 requests < nrToGet > items / units from < buffer>. If not acquired < nrToGet > in time period 281 <patience>, self leaves < buffer>.getQ (reneges). 282 283 Get with event - based reneging (for Store and Level): 284 yield (get, self,<buffer>,nrToGet etc.),(waitevent, self,<eventlist>) 285 requests < nrToGet > items / units from < buffer>. If not acquired < nrToGet > before one of 286 the events in < eventlist > occurs, self leaves < buffer>.getQ (reneges). 287 288 289 290 Event notices get posted in event - list by scheduler after 'yield' or by 291 'activate' / 'reactivate' functions. 292 293 if real_time == True, the simulation time and real (clock) time get 294 synchronized as much as possible. rel_speed is the ratio simulation time 295 over clock time(seconds). Example: rel_speed == 100: 100 simulation time units take 296 1 second clock time. 297 298 """ 299 global _endtime, _e, _stop, _t, _wustep 300 self._stop = False 301 302 if self._e is None: 303 raise FatalSimerror('Simulation not initialized') 304 self._e.real_time = real_time 305 self._e.rel_speed = rel_speed 306 self._e.rtlast = self.wallclock() 307 self._e.stlast = 0 308 if self._e._isEmpty(): 309 message = 'SimPy: No activities scheduled' 310 return message 311 312 self._endtime = until 313 message = 'SimPy: Normal exit' 314 dispatch={hold:holdfunc, request:requestfunc, release:releasefunc, 315 passivate:passivatefunc, waitevent:waitevfunc, queueevent:queueevfunc, 316 waituntil:waituntilfunc, get:getfunc, put:putfunc} 317 commandcodes = dispatch.keys() 318 commandwords={hold:'hold', request:'request', release:'release', passivate:'passivate', 319 waitevent:'waitevent', queueevent:'queueevent', waituntil:'waituntil', 320 get:'get', put:'put'} 321 nextev = self._e._nextev ## just a timesaver 322 while not self._stop and self._t <= self._endtime: 323 try: 324 a = nextev() 325 if not a[0] is None: 326 ## 'a' is tuple '(<yield command>, <action>)' 327 if type(a[0][0]) == tuple: 328 ##allowing for yield (request, self, res),(waituntil, self, cond) 329 command = a[0][0][0] 330 else: 331 command = a[0][0] 332 if __debug__: 333 if not command in commandcodes: 334 raise FatalSimerror('Illegal command: yield %s'%command) 335 dispatch[command](a) 336 except FatalSimerror, error: 337 print 'SimPy: ' + error.value 338 sys.exit(1) 339 except Simerror, error: 340 message = 'SimPy: ' + error.value 341 self._stop = True 342 if self._wustep: 343 self._test() 344 self._stopWUStepping() 345 self._e = None 346 return message
347 348 # For backward compatibility 349 Globals.sim = SimulationRT()
350 -def rtnow():
351 return Globals.sim.rtnow()
352
353 -def rtset(rel_speed = 1):
354 Globals.sim.rtset()
355
356 -def simulate(until = 0, real_time = False, rel_speed = 1):
357 return Globals.sim.simulate(until = until, real_time = real_time, rel_speed = rel_speed)
358 # End backward compatibility 359 360 if __name__ == '__main__': 361 print 'SimPy.SimulationRT %s' %__version__ 362 ############# Test / demo functions #############
363 - def test_demo():
364 class Aa(Process): 365 sequIn = [] 366 sequOut = [] 367 def __init__(self, holdtime, name): 368 Process.__init__(self, name) 369 self.holdtime = holdtime
370 371 def life(self, priority): 372 for i in range(1): 373 Aa.sequIn.append(self.name) 374 print now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\ 375 len(rrr.activeQ) 376 print 'waitQ: ',[(k.name, k._priority[rrr]) for k in rrr.waitQ] 377 print 'activeQ: ',[(k.name, k._priority[rrr]) \ 378 for k in rrr.activeQ] 379 assert rrr.n + len(rrr.activeQ) == rrr.capacity, \ 380 'Inconsistent resource unit numbers' 381 print now(),self.name, 'requests 1 ', rrr.unitName 382 yield request, self, rrr, priority 383 print now(),self.name, 'has 1 ', rrr.unitName 384 print now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\ 385 len(rrr.activeQ) 386 print now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\ 387 len(rrr.activeQ) 388 assert rrr.n + len(rrr.activeQ) == rrr.capacity, \ 389 'Inconsistent resource unit numbers' 390 yield hold, self, self.holdtime 391 print now(),self.name, 'gives up 1', rrr.unitName 392 yield release, self, rrr 393 Aa.sequOut.append(self.name) 394 print now(),self.name, 'has released 1 ', rrr.unitName 395 print 'waitQ: ',[(k.name, k._priority[rrr]) for k in rrr.waitQ] 396 print now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\ 397 len(rrr.activeQ) 398 assert rrr.n + len(rrr.activeQ) == rrr.capacity, \ 399 'Inconsistent resource unit numbers' 400 401 class Observer(Process): 402 def __init__(self): 403 Process.__init__(self) 404 405 def observe(self, step, processes, res): 406 while now() < 11: 407 for i in processes: 408 print ' %s %s: act:%s, pass:%s, term: %s, interr:%s, qu:%s'\ 409 %(now(),i.name, i.active(),i.passive(),i.terminated()\ 410 ,i.interrupted(),i.queuing(res)) 411 print 412 yield hold, self, step 413 414 print'\n+++test_demo output' 415 print '****First case == priority queue, resource service not preemptable' 416 initialize() 417 rrr = Resource(5, name = 'Parking', unitName = 'space(s)', qType = PriorityQ, 418 preemptable = 0) 419 procs = [] 420 for i in range(10): 421 z = Aa(holdtime = i, name = 'Car ' + str(i)) 422 procs.append(z) 423 activate(z, z.life(priority = i)) 424 o = Observer() 425 activate(o, o.observe(1, procs, rrr)) 426 a = simulate(until = 10000, real_time = True, rel_speed = 1) 427 print a 428 print 'Input sequence: ', Aa.sequIn 429 print 'Output sequence: ', Aa.sequOut 430 431 print '\n****Second case == priority queue, resource service preemptable' 432 initialize() 433 rrr = Resource(5, name = 'Parking', unitName = 'space(s)', qType = PriorityQ, 434 preemptable = 1) 435 procs = [] 436 for i in range(10): 437 z = Aa(holdtime = i, name = 'Car ' + str(i)) 438 procs.append(z) 439 activate(z, z.life(priority = i)) 440 o = Observer() 441 activate(o, o.observe(1, procs, rrr)) 442 Aa.sequIn = [] 443 Aa.sequOut = [] 444 a = simulate(until = 10000) 445 print a 446 print 'Input sequence: ', Aa.sequIn 447 print 'Output sequence: ', Aa.sequOut 448
449 - def test_interrupt():
450 class Bus(Process): 451 def __init__(self, name): 452 Process.__init__(self, name)
453 454 def operate(self, repairduration = 0): 455 print now(),rtnow(),'>> %s starts' % (self.name) 456 tripleft = 1000 457 while tripleft > 0: 458 yield hold, self, tripleft 459 if self.interrupted(): 460 print 'interrupted by %s' %self.interruptCause.name 461 print '%s(%s): %s breaks down ' %(now(),rtnow(),self.name) 462 tripleft = self.interruptLeft 463 self.interruptReset() 464 print 'tripleft ', tripleft 465 reactivate(br, delay = repairduration) # breakdowns only during operation 466 yield hold, self, repairduration 467 print now(),rtnow(),' repaired' 468 else: 469 break # no breakdown, ergo bus arrived 470 print now(),'<< %s done' % (self.name) 471 472 class Breakdown(Process): 473 def __init__(self, myBus): 474 Process.__init__(self, name = 'Breakdown ' + myBus.name) 475 self.bus = myBus 476 477 def breakBus(self, interval): 478 479 while True: 480 yield hold, self, interval 481 if self.bus.terminated(): break 482 self.interrupt(self.bus) 483 484 print'\n\n+++test_interrupt' 485 initialize() 486 b = Bus('Bus 1') 487 activate(b, b.operate(repairduration = 20)) 488 br = Breakdown(b) 489 activate(br, br.breakBus(200)) 490 print simulate(until = 4000, real_time = True, rel_speed = 200) 491
492 - def testSimEvents():
493 class Waiter(Process): 494 def waiting(self, theSignal): 495 while True: 496 yield waitevent, self, theSignal 497 print '%s: process \'%s\' continued after waiting for %s' % (now(),self.name, theSignal.name) 498 yield queueevent, self, theSignal 499 print '%s: process \'%s\' continued after queueing for %s' % (now(),self.name, theSignal.name)
500 501 class ORWaiter(Process): 502 def waiting(self, signals): 503 while True: 504 yield waitevent, self, signals 505 print now(),'one of %s signals occurred' % [x.name for x in signals] 506 print '\t%s (fired / param)'%[(x.name, x.signalparam) for x in self.eventsFired] 507 yield hold, self, 1 508 509 class Caller(Process): 510 def calling(self): 511 while True: 512 signal1.signal('wake up!') 513 print '%s: signal 1 has occurred'%now() 514 yield hold, self, 10 515 signal2.signal('and again') 516 signal2.signal('sig 2 again') 517 print '%s: signal1, signal2 have occurred'%now() 518 yield hold, self, 10 519 print'\n\n+++testSimEvents output' 520 initialize() 521 signal1 = SimEvent('signal 1') 522 signal2 = SimEvent('signal 2') 523 signal1.signal('startup1') 524 signal2.signal('startup2') 525 w1 = Waiter('waiting for signal 1') 526 activate(w1, w1.waiting(signal1)) 527 w2 = Waiter('waiting for signal 2') 528 activate(w2, w2.waiting(signal2)) 529 w3 = Waiter('also waiting for signal 2') 530 activate(w3, w3.waiting(signal2)) 531 w4 = ORWaiter('waiting for either signal 1 or signal 2') 532 activate(w4, w4.waiting([signal1, signal2]),prior = True) 533 c = Caller('Caller') 534 activate(c, c.calling()) 535 print simulate(until = 100) 536
537 - def testwaituntil():
538 """ 539 Demo of waitUntil capability. 540 541 Scenario: 542 Three workers require sets of tools to do their jobs. Tools are shared, scarce 543 resources for which they compete. 544 """ 545 546 547 class Worker(Process): 548 def __init__(self, name, heNeeds = []): 549 Process.__init__(self, name) 550 self.heNeeds = heNeeds
551 def work(self): 552 553 def workerNeeds(): 554 for item in self.heNeeds: 555 if item.n == 0: 556 return False 557 return True 558 559 while now() < 8 * 60: 560 yield waituntil, self, workerNeeds 561 for item in self.heNeeds: 562 yield request, self, item 563 print '%s %s has %s and starts job' % (now(),self.name, 564 [x.name for x in self.heNeeds]) 565 yield hold, self, random.uniform(10, 30) 566 for item in self.heNeeds: 567 yield release, self, item 568 yield hold, self, 2 #rest 569 570 print '\n+++\nwaituntil demo output' 571 initialize() 572 brush = Resource(capacity = 1, name = 'brush') 573 ladder = Resource(capacity = 2, name = 'ladder') 574 hammer = Resource(capacity = 1, name = 'hammer') 575 saw = Resource(capacity = 1, name = 'saw') 576 painter = Worker('painter',[brush, ladder]) 577 activate(painter, painter.work()) 578 roofer = Worker('roofer',[hammer, ladder, ladder]) 579 activate(roofer, roofer.work()) 580 treeguy = Worker('treeguy',[saw, ladder]) 581 activate(treeguy, treeguy.work()) 582 for who in (painter, roofer, treeguy): 583 print '%s needs %s for his job' % (who.name,[x.name for x in who.heNeeds]) 584 print 585 print simulate(until = 9 * 60) 586 test_demo() 587 # Run tests 588 test_interrupt() 589 testSimEvents() 590 testwaituntil() 591