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

Source Code for Module SimPy.SimulationRT

  1  #!/usr / bin / env python 
  2  # $Revision: 271 $ $Date: 2009-03-25 13:27:06 +0100 (Mi, 25 Mrz 2009) $ 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 imported it from Simulation.py 
140                    
141      19 Mar 2009: - Repair of wallclock synchronisation algorithm (again) 
142       
143  """ 
144  import time 
145   
146  from SimPy.Simulation import * 
147   
148   
149  __TESTING = False 
150  version = __version__ = '2.0 $Revision: 271 $ $Date: 2009-03-25 13:27:06 +0100 (Mi, 25 Mrz 2009) $' 
151  if __TESTING:  
152      print 'SimPy.SimulationRT %s' %__version__, 
153      if __debug__: 
154          print '__debug__ on' 
155      else: 
156          print 
157   
158           
159 -class EvlistRT(Evlist):
160 """Defines event list and operations on it"""
161 - def __init__(self, sim):
162 # always sorted list of events (sorted by time, priority) 163 # make heapq 164 self.sim = sim 165 self.timestamps = [] 166 self.sortpr = 0 167 self.real_time = False 168 self.rel_speed = 1 169 self.rtlast = self.sim.wallclock() 170 self.stlast = 0
171
172 - def _nextev(self):
173 """Retrieve next event from event list""" 174 noActiveNotice = True 175 ## Find next event notice which is not marked cancelled 176 while noActiveNotice: 177 if self.timestamps: 178 ## ignore priority value 179 (_tnotice, p,nextEvent, cancelled) = hq.heappop(self.timestamps) 180 noActiveNotice = cancelled 181 else: 182 raise Simerror('No more events at time %s' % self.sim._t) 183 nextEvent._rec = None 184 _tsimold = self.sim._t 185 self.sim._t = _tnotice 186 ## Calculate any wait time 187 ## (tRTnow - tRTold + delay)/(tsim - tsimold) = 1/rel_speed 188 if self.real_time: 189 delay = float(_tnotice-_tsimold)/self.rel_speed-self.sim.rtnow()+self.rtlast 190 if delay > 0: 191 time.sleep(delay) 192 self.rtlast = self.sim.wallclock()-self.sim.rtstart 193 self.stlast = self.sim._t 194 if self.sim._t > self.sim._endtime: 195 self.sim._t = self.sim._endtime 196 self.sim._stop = True 197 return (None,) 198 try: 199 resultTuple = nextEvent._nextpoint.next() 200 except StopIteration: 201 nextEvent._nextpoint = None 202 nextEvent._terminated = True 203 nextEvent._nextTime = None 204 resultTuple = None 205 return (resultTuple, nextEvent)
206 207
208 -class SimulationRT(Simulation):
209
210 - def __init__(self):
211 if sys.platform == 'win32': #take care of differences in clock accuracy 212 self.wallclock = time.clock 213 else: 214 self.wallclock = time.time 215 self.rtstart = self.wallclock() 216 Simulation.__init__(self) 217 self.initialize()
218
219 - def initialize(self):
220 self.rtstart = self.wallclock() 221 Simulation.initialize(self) 222 self._e = EvlistRT(self) 223 self._e.rtlast = 0
224
225 - def rtnow(self):
226 return self.wallclock() - self.rtstart
227
228 - def rtset(self, rel_speed = 1):
229 """resets the the ratio simulation time over clock time(seconds). 230 """ 231 if self._e is None: 232 raise FatalSimerror('Fatal SimPy error: Simulation not initialized') 233 self._e.rel_speed = float(rel_speed)
234
235 - def simulate(self, until = 0, real_time = False, rel_speed = 1):
236 """Schedules Processes / semi - coroutines until time 'until'""" 237 238 """Gets called once. Afterwards, co - routines (generators) return by 239 'yield' with a cargo: 240 yield hold, self, <delay>: schedules the 'self' process for activation 241 after < delay > time units.If <,delay > missing, 242 same as 'yield hold, self, 0' 243 244 yield passivate, self : makes the 'self' process wait to be re - activated 245 246 yield request, self,<Resource > [,<priority>]: request 1 unit from < Resource> 247 with < priority > pos integer (default = 0) 248 249 yield release, self,<Resource> : release 1 unit to < Resource> 250 251 yield waitevent, self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]: 252 wait for one or more of several events 253 254 255 yield queueevent, self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]: 256 queue for one or more of several events 257 258 yield waituntil, self, cond : wait for arbitrary condition 259 260 yield get, self,<buffer > [,<WhatToGet > [,<priority>]] 261 get < WhatToGet > items from buffer (default = 1); 262 <WhatToGet > can be a pos integer or a filter function 263 (Store only) 264 265 yield put, self,<buffer > [,<WhatToPut > [,priority]] 266 put < WhatToPut > items into buffer (default = 1); 267 <WhatToPut > can be a pos integer (Level) or a list of objects 268 (Store) 269 270 EXTENSIONS: 271 Request with timeout reneging: 272 yield (request, self,<Resource>),(hold, self,<patience>) : 273 requests 1 unit from < Resource>. If unit not acquired in time period 274 <patience>, self leaves waitQ (reneges). 275 276 Request with event - based reneging: 277 yield (request, self,<Resource>),(waitevent, self,<eventlist>): 278 requests 1 unit from < Resource>. If one of the events in < eventlist > occurs before unit 279 acquired, self leaves waitQ (reneges). 280 281 Get with timeout reneging (for Store and Level): 282 yield (get, self,<buffer>,nrToGet etc.),(hold, self,<patience>) 283 requests < nrToGet > items / units from < buffer>. If not acquired < nrToGet > in time period 284 <patience>, self leaves < buffer>.getQ (reneges). 285 286 Get with event - based reneging (for Store and Level): 287 yield (get, self,<buffer>,nrToGet etc.),(waitevent, self,<eventlist>) 288 requests < nrToGet > items / units from < buffer>. If not acquired < nrToGet > before one of 289 the events in < eventlist > occurs, self leaves < buffer>.getQ (reneges). 290 291 292 293 Event notices get posted in event - list by scheduler after 'yield' or by 294 'activate' / 'reactivate' functions. 295 296 if real_time == True, the simulation time and real (clock) time get 297 synchronized as much as possible. rel_speed is the ratio simulation time 298 over clock time(seconds). Example: rel_speed == 100: 100 simulation time units take 299 1 second clock time. 300 301 """ 302 global _endtime, _e, _stop, _t, _wustep 303 self._stop = False 304 305 if self._e is None: 306 raise FatalSimerror('Simulation not initialized') 307 self._e.real_time = real_time 308 self._e.rel_speed = rel_speed 309 #~ self._e.rtlast = self.wallclock() 310 #~ self._e.stlast = 0 311 if self._e._isEmpty(): 312 message = 'SimPy: No activities scheduled' 313 return message 314 315 self._endtime = until 316 message = 'SimPy: Normal exit' 317 dispatch={hold:holdfunc, request:requestfunc, release:releasefunc, 318 passivate:passivatefunc, waitevent:waitevfunc, queueevent:queueevfunc, 319 waituntil:waituntilfunc, get:getfunc, put:putfunc} 320 commandcodes = dispatch.keys() 321 commandwords={hold:'hold', request:'request', release:'release', passivate:'passivate', 322 waitevent:'waitevent', queueevent:'queueevent', waituntil:'waituntil', 323 get:'get', put:'put'} 324 nextev = self._e._nextev ## just a timesaver 325 while not self._stop and self._t <= self._endtime: 326 try: 327 a = nextev() 328 if not a[0] is None: 329 ## 'a' is tuple '(<yield command>, <action>)' 330 if type(a[0][0]) == tuple: 331 ##allowing for yield (request, self, res),(waituntil, self, cond) 332 command = a[0][0][0] 333 else: 334 command = a[0][0] 335 if __debug__: 336 if not command in commandcodes: 337 raise FatalSimerror('Illegal command: yield %s'%command) 338 dispatch[command](a) 339 except FatalSimerror, error: 340 print 'SimPy: ' + error.value 341 sys.exit(1) 342 except Simerror, error: 343 message = 'SimPy: ' + error.value 344 self._stop = True 345 if self._wustep: 346 self._test() 347 self._stopWUStepping() 348 self._e = None 349 return message
350 351 # For backward compatibility 352 Globals.sim = SimulationRT() 353
354 -def rtnow():
355 return Globals.sim.rtnow()
356 357 rtset = Globals.sim.rtset 358
359 -def simulate(until = 0, real_time = False, rel_speed = 1):
360 return Globals.sim.simulate(until = until, real_time = real_time, rel_speed = rel_speed)
361 362 wallclock = Globals.sim.wallclock 363 # End backward compatibility 364 365 if __name__ == '__main__': 366 print 'SimPy.SimulationRT %s' %__version__ 367 ############# Test / demo functions #############
368 - def test_demo():
369 class Aa(Process): 370 sequIn = [] 371 sequOut = [] 372 def __init__(self, holdtime, name): 373 Process.__init__(self, name) 374 self.holdtime = holdtime
375 376 def life(self, priority): 377 for i in range(1): 378 Aa.sequIn.append(self.name) 379 print now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\ 380 len(rrr.activeQ) 381 print 'waitQ: ',[(k.name, k._priority[rrr]) for k in rrr.waitQ] 382 print 'activeQ: ',[(k.name, k._priority[rrr]) \ 383 for k in rrr.activeQ] 384 assert rrr.n + len(rrr.activeQ) == rrr.capacity, \ 385 'Inconsistent resource unit numbers' 386 print now(),self.name, 'requests 1 ', rrr.unitName 387 yield request, self, rrr, priority 388 print now(),self.name, 'has 1 ', rrr.unitName 389 print now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\ 390 len(rrr.activeQ) 391 print now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\ 392 len(rrr.activeQ) 393 assert rrr.n + len(rrr.activeQ) == rrr.capacity, \ 394 'Inconsistent resource unit numbers' 395 yield hold, self, self.holdtime 396 print now(),self.name, 'gives up 1', rrr.unitName 397 yield release, self, rrr 398 Aa.sequOut.append(self.name) 399 print now(),self.name, 'has released 1 ', rrr.unitName 400 print 'waitQ: ',[(k.name, k._priority[rrr]) for k in rrr.waitQ] 401 print now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\ 402 len(rrr.activeQ) 403 assert rrr.n + len(rrr.activeQ) == rrr.capacity, \ 404 'Inconsistent resource unit numbers' 405 406 class Observer(Process): 407 def __init__(self): 408 Process.__init__(self) 409 410 def observe(self, step, processes, res): 411 while now() < 11: 412 for i in processes: 413 print ' %s %s: act:%s, pass:%s, term: %s, interr:%s, qu:%s'\ 414 %(now(),i.name, i.active(),i.passive(),i.terminated()\ 415 ,i.interrupted(),i.queuing(res)) 416 print 417 yield hold, self, step 418 419 print'\n+++test_demo output' 420 print '****First case == priority queue, resource service not preemptable' 421 initialize() 422 rrr = Resource(5, name = 'Parking', unitName = 'space(s)', qType = PriorityQ, 423 preemptable = 0) 424 procs = [] 425 for i in range(10): 426 z = Aa(holdtime = i, name = 'Car ' + str(i)) 427 procs.append(z) 428 activate(z, z.life(priority = i)) 429 o = Observer() 430 activate(o, o.observe(1, procs, rrr)) 431 a = simulate(until = 10000, real_time = True, rel_speed = 1) 432 print a 433 print 'Input sequence: ', Aa.sequIn 434 print 'Output sequence: ', Aa.sequOut 435 436 print '\n****Second case == priority queue, resource service preemptable' 437 initialize() 438 rrr = Resource(5, name = 'Parking', unitName = 'space(s)', qType = PriorityQ, 439 preemptable = 1) 440 procs = [] 441 for i in range(10): 442 z = Aa(holdtime = i, name = 'Car ' + str(i)) 443 procs.append(z) 444 activate(z, z.life(priority = i)) 445 o = Observer() 446 activate(o, o.observe(1, procs, rrr)) 447 Aa.sequIn = [] 448 Aa.sequOut = [] 449 a = simulate(until = 10000) 450 print a 451 print 'Input sequence: ', Aa.sequIn 452 print 'Output sequence: ', Aa.sequOut 453
454 - def test_interrupt():
455 class Bus(Process): 456 def __init__(self, name): 457 Process.__init__(self, name)
458 459 def operate(self, repairduration = 0): 460 print now(),rtnow(),'>> %s starts' % (self.name) 461 tripleft = 1000 462 while tripleft > 0: 463 yield hold, self, tripleft 464 if self.interrupted(): 465 print 'interrupted by %s' %self.interruptCause.name 466 print '%s(%s): %s breaks down ' %(now(),rtnow(),self.name) 467 tripleft = self.interruptLeft 468 self.interruptReset() 469 print 'tripleft ', tripleft 470 reactivate(br, delay = repairduration) # breakdowns only during operation 471 yield hold, self, repairduration 472 print now(),rtnow(),' repaired' 473 else: 474 break # no breakdown, ergo bus arrived 475 print now(),'<< %s done' % (self.name) 476 477 class Breakdown(Process): 478 def __init__(self, myBus): 479 Process.__init__(self, name = 'Breakdown ' + myBus.name) 480 self.bus = myBus 481 482 def breakBus(self, interval): 483 484 while True: 485 yield hold, self, interval 486 if self.bus.terminated(): break 487 self.interrupt(self.bus) 488 489 print'\n\n+++test_interrupt' 490 initialize() 491 b = Bus('Bus 1') 492 activate(b, b.operate(repairduration = 20)) 493 br = Breakdown(b) 494 activate(br, br.breakBus(200)) 495 print simulate(until = 4000, real_time = True, rel_speed = 200) 496
497 - def testSimEvents():
498 class Waiter(Process): 499 def waiting(self, theSignal): 500 while True: 501 yield waitevent, self, theSignal 502 print '%s: process \'%s\' continued after waiting for %s' % (now(),self.name, theSignal.name) 503 yield queueevent, self, theSignal 504 print '%s: process \'%s\' continued after queueing for %s' % (now(),self.name, theSignal.name)
505 506 class ORWaiter(Process): 507 def waiting(self, signals): 508 while True: 509 yield waitevent, self, signals 510 print now(),'one of %s signals occurred' % [x.name for x in signals] 511 print '\t%s (fired / param)'%[(x.name, x.signalparam) for x in self.eventsFired] 512 yield hold, self, 1 513 514 class Caller(Process): 515 def calling(self): 516 while True: 517 signal1.signal('wake up!') 518 print '%s: signal 1 has occurred'%now() 519 yield hold, self, 10 520 signal2.signal('and again') 521 signal2.signal('sig 2 again') 522 print '%s: signal1, signal2 have occurred'%now() 523 yield hold, self, 10 524 print'\n\n+++testSimEvents output' 525 initialize() 526 signal1 = SimEvent('signal 1') 527 signal2 = SimEvent('signal 2') 528 signal1.signal('startup1') 529 signal2.signal('startup2') 530 w1 = Waiter('waiting for signal 1') 531 activate(w1, w1.waiting(signal1)) 532 w2 = Waiter('waiting for signal 2') 533 activate(w2, w2.waiting(signal2)) 534 w3 = Waiter('also waiting for signal 2') 535 activate(w3, w3.waiting(signal2)) 536 w4 = ORWaiter('waiting for either signal 1 or signal 2') 537 activate(w4, w4.waiting([signal1, signal2]),prior = True) 538 c = Caller('Caller') 539 activate(c, c.calling()) 540 print simulate(until = 100) 541
542 - def testwaituntil():
543 """ 544 Demo of waitUntil capability. 545 546 Scenario: 547 Three workers require sets of tools to do their jobs. Tools are shared, scarce 548 resources for which they compete. 549 """ 550 551 552 class Worker(Process): 553 def __init__(self, name, heNeeds = []): 554 Process.__init__(self, name) 555 self.heNeeds = heNeeds
556 def work(self): 557 558 def workerNeeds(): 559 for item in self.heNeeds: 560 if item.n == 0: 561 return False 562 return True 563 564 while now() < 8 * 60: 565 yield waituntil, self, workerNeeds 566 for item in self.heNeeds: 567 yield request, self, item 568 print '%s %s has %s and starts job' % (now(),self.name, 569 [x.name for x in self.heNeeds]) 570 yield hold, self, random.uniform(10, 30) 571 for item in self.heNeeds: 572 yield release, self, item 573 yield hold, self, 2 #rest 574 575 print '\n+++\nwaituntil demo output' 576 initialize() 577 brush = Resource(capacity = 1, name = 'brush') 578 ladder = Resource(capacity = 2, name = 'ladder') 579 hammer = Resource(capacity = 1, name = 'hammer') 580 saw = Resource(capacity = 1, name = 'saw') 581 painter = Worker('painter',[brush, ladder]) 582 activate(painter, painter.work()) 583 roofer = Worker('roofer',[hammer, ladder, ladder]) 584 activate(roofer, roofer.work()) 585 treeguy = Worker('treeguy',[saw, ladder]) 586 activate(treeguy, treeguy.work()) 587 for who in (painter, roofer, treeguy): 588 print '%s needs %s for his job' % (who.name,[x.name for x in who.heNeeds]) 589 print 590 print simulate(until = 9 * 60) 591 test_demo() 592 # Run tests 593 test_interrupt() 594 #testSimEvents() 595 #testwaituntil() 596