1
2
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
158 """Defines event list and operations on it"""
160
161
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
171 """Retrieve next event from event list"""
172 noActiveNotice = True
173
174 while noActiveNotice:
175 if self.timestamps:
176
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
184
185
186 if self.real_time:
187
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
208
210 if sys.platform == 'win32':
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
221
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
322 while not self._stop and self._t <= self._endtime:
323 try:
324 a = nextev()
325 if not a[0] is None:
326
327 if type(a[0][0]) == tuple:
328
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
349 Globals.sim = SimulationRT()
352
353 -def rtset(rel_speed = 1):
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
359
360 if __name__ == '__main__':
361 print 'SimPy.SimulationRT %s' %__version__
362
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
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)
466 yield hold, self, repairduration
467 print now(),rtnow(),' repaired'
468 else:
469 break
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
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
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
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
588 test_interrupt()
589 testSimEvents()
590 testwaituntil()
591