1
2
3 """Simulation 2.0 Implements SimPy Processes, Resources, Buffers, and the backbone simulation
4 scheduling by coroutine calls. Provides data collection through classes
5 Monitor and Tally.
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 **Change history:**
28
29 Started out as SiPy 0.9
30
31 5 / 9/2002: SiPy 0.9.1
32
33 - Addition of '_cancel' method in class Process and supporting '_unpost' method in
34 class _Evlist.
35
36 - Removal of redundant 'Action' method in class Process.
37
38 12 / 9/2002:
39
40 - Addition of resource class
41
42 - Addition of '_request' and '_release' coroutine calls
43
44 15 / 9/2002: moved into SimPy package
45
46 16 / 9/2002:
47 - Resource attributes fully implemented (resources can now have more
48 than 1 shareable resource units)
49
50 17 / 9/2002:
51
52 - corrected removal from waitQ (Vignaux)
53
54 17 / 9/2002:
55
56 - added test for queue discipline in 'test_demo()'. Must be FIFO
57
58 26 / 9/02: Version 0.2.0
59
60 - cleaned up code; more consistent naming
61
62 - prefixed all Simulation - private variable names with '_'.
63
64 - prefixed all class - private variable names with '__'.
65
66 - made normal exit quiet (but return message from scheduler()
67
68 28 / 9/02:
69
70 - included stopSimulation()
71
72 15 / 10 / 02: Simulation version 0.3
73
74 - Version printout now only if __TESTING
75
76 - '_stop' initialized to True by module load, and set to False in
77 initialize()
78
79 - Introduced 'simulate(until = 0)' instead of 'scheduler(till = 0)'.
80 Left 'scheduler()' in for backward compatibility, but marked
81 as deprecated.
82
83 - Added attribute 'name' to class Process; default == 'a_process'
84
85 - Changed Resource constructor to
86 'def __init__(self, capacity = 1, name = 'a_resource', unitName = 'units''.
87
88 13 / 11 / 02: Simulation version 0.6
89
90 - Major changes to class Resource:
91
92 - Added two queue types for resources, FIFO (default) and PriorityQ
93
94 - Changed constructor to allow selection of queue type.
95
96 - Introduced preemption of resources (to be used with PriorityQ
97 queue type)
98
99 - Changed constructor of class Resource to allow selection of preemption
100
101 - Changes to class Process to support preemption of service
102
103 - Cleaned up 'simulate' by replacing series of if - statements by dispatch table.
104
105 19 / 11 / 02: Simulation version 0.6.1
106 - Changed priority schemes so that higher values of Process
107 attribute 'priority' represent higher priority.
108
109 20 / 11 / 02: Simulation version 0.7
110 - Major change of priority approach:
111
112 - Priority set by 'yield request, self, res, priority'
113
114 - Priority of a Process instance associated with a specific
115 resource
116
117 25 / 11 / 02: Simulation version 0.7.1
118
119 - Code cleanup and optimization
120
121 - Made process attributes remainService and preempted private
122 (_remainService and _preempted)
123
124 11 / 12 / 2002: First process interrupt implementation
125
126 - Addition of user methods 'interrupt' and 'resume'
127
128 - Significant code cleanup to maintain process state
129
130 20 / 12 / 2002: Changes to 'interrupt'; addition of boolean methods to show
131 process states
132
133 16 / 3/2003: Changed hold (allowing posting events past _endtime)
134
135 18 / 3/2003: Changed _nextev to prevent _t going past _endtime
136
137 23 / 3/2003: Introduced new interrupt construct; deleted 'resume' method
138
139 25 / 3/2003: Expanded interrupt construct:
140
141 - Made 'interrupt' a method of Process
142
143 - Added 'interruptCause' as an attribute of an interrupted process
144
145 - Changed definition of 'active' to
146 'self._nextTime <> None and not self._inInterrupt'
147
148 - Cleaned up test_interrupt function
149
150 30 / 3/2003: Modification of 'simulate':
151
152 - error message if 'initialize' not called (fatal)
153
154 - error message if no process scheduled (warning)
155
156 - Ensured that upon exit from 'simulate', now() == _endtime is
157 always valid
158
159 2 / 04 / 2003:
160
161 - Modification of 'simulate': leave _endtime alone (undid change
162 of 30 Mar 03)
163
164 - faster '_unpost'
165
166 3 / 04 / 2003: Made 'priority' private ('_priority')
167
168 4 / 04 / 2003: Catch activation of non - generator error
169
170 5 / 04 / 2003: Added 'interruptReset()' function to Process.
171
172 7 / 04 / 2003: Changed '_unpost' to ensure that process has
173 _nextTime == None (is passive) afterwards.
174
175 8 / 04 / 2003: Changed _hold to allow for 'yield hold, self'
176 (equiv to 'yield hold, self, 0')
177
178 10 / 04 / 2003: Changed 'cancel' syntax to 'Process().cancel(victim)'
179
180 12 / 5/2003: Changed eventlist handling from dictionary to bisect
181
182 9 / 6/2003: - Changed eventlist handling from pure dictionary to bisect-
183 sorted 'timestamps' list of keys, resulting in greatly
184 improved performance for models with large
185 numbers of event notices with differing event times.
186 =========================================================
187 This great change was suggested by Prof. Simon Frost.
188 Thank you, Simon! This version 1.3 is dedicated to you!
189 =========================================================
190 - Added import of Lister which supports well - structured
191 printing of all attributes of Process and Resource instances.
192
193 Oct 2003: Added monitored Resource instances (Monitors for activeQ and waitQ)
194
195 13 Dec 2003: Merged in Monitor and Histogram
196
197 27 Feb 2004: Repaired bug in activeQ monitor of class Resource. Now actMon
198 correctly records departures from activeQ.
199
200 19 May 2004: Added erroneously omitted Histogram class.
201
202 5 Sep 2004: Added SimEvents synchronization constructs
203
204 17 Sep 2004: Added waituntil synchronization construct
205
206 01 Dec 2004: SimPy version 1.5
207 Changes in this module: Repaired SimEvents bug re proc.eventsFired
208
209 12 Jan 2005: SimPy version 1.5.1
210 Changes in this module: Monitor objects now have a default name
211 'a_Monitor'
212
213 29 Mar 2005: Start SimPy 1.6: compound 'yield request' statements
214
215 05 Jun 2005: Fixed bug in _request method -- waitMon did not work properly in
216 preemption case
217
218 09 Jun 2005: Added test in 'activate' to see whether 'initialize()' was called first.
219
220 23 Aug 2005: - Added Tally data collection class
221 - Adjusted Resource to work with Tally
222 - Redid function allEventNotices() (returns prettyprinted string with event
223 times and names of process instances
224 - Added function allEventTimes (returns event times of all scheduled events)
225
226 16 Mar 2006: - Added Store and Level classes
227 - Added 'yield get' and 'yield put'
228
229 10 May 2006: - Repaired bug in Store._get method
230 - Repaired Level to allow initialBuffered have float value
231 - Added type test for Level get parameter 'nrToGet'
232
233 06 Jun 2006: - To improve pretty - printed output of 'Level' objects, changed attribute
234 _nrBuffered to nrBuffered (synonym for amount property)
235 - To improve pretty - printed output of 'Store' objects, added attribute
236 buffered (which refers to _theBuffer)
237
238 25 Aug 2006: - Start of version 1.8
239 - made 'version' public
240 - corrected condQ initialization bug
241
242 30 Sep 2006: - Introduced checks to ensure capacity of a Buffer > 0
243 - Removed from __future__ import (so Python 2.3 or later needed)
244
245 15 Oct 2006: - Added code to register all Monitors and all Tallies in variables
246 'allMonitors' and 'allTallies'
247 - Added function 'startCollection' to activate Monitors and Tallies at a
248 specified time (e.g. after warmup period)
249 - Moved all test / demo programs to after 'if __name__ == '__main__':'.
250
251 17 Oct 2006: - Added compound 'put' and 'get' statements for Level and Store.
252
253 18 Oct 2006: - Repaired bug: self.eventsFired now gets set after an event fires
254 in a compound yield get / put with a waitevent clause (reneging case).
255
256 21 Oct 2006: - Introduced Store 'yield get' with a filter function.
257
258 22 Oct 2006: - Repaired bug in prettyprinting of Store objects (the buffer
259 content==._theBuffer was not shown) by changing ._theBuffer
260 to .theBuffer.
261
262 04 Dec 2006: - Added printHistogram method to Tally and Monitor (generates
263 table - form histogram)
264
265 07 Dec 2006: - Changed the __str__ method of Histogram to print a table
266 (like printHistogram).
267
268 18 Dec 2006: - Added trace printing of Buffers' 'unitName' for yield get and put.
269
270 09 Jun 2007: - Cleaned out all uses of 'object' to prevent name clash.
271
272 18 Nov 2007: - Start of 1.9 development
273 - Added 'start' method (alternative to activate) to Process
274
275 22 Nov 2007: - Major change to event list handling to speed up larger models:
276 * Drop dictionary
277 * Replace bisect by heapq
278 * Mark cancelled event notices in unpost and skip them in
279 nextev (great idea of Tony Vignaux))
280
281 4 Dec 2007: - Added twVariance calculation for both Monitor and Tally (gav)
282
283 5 Dec 2007: - Changed name back to timeVariance (gav)
284
285 1 Mar 2008: - Start of 1.9.1 bugfix release
286 - Delete circular reference in Process instances when event
287 notice has been processed (caused much circular garbage)
288 - Added capability for multiple preempts of a process
289
290 10 Aug 2008: - Introduced a Simulation class that now contains all global
291 functions for the simulation.
292 * Globals.py contains dummies for them to obtain backward
293 compatibility (Ontje Luensdorf)
294 - Histogram, Monitor and Tally were moved to Recording.py
295 (Stefan Scherfke)
296 - Process, SimEvent, Queue, FIFO, PriorityQ, Resource, Buffer,
297 Level, Store moved to Resources.py
298 - Renamed __Evlist to Evlist
299
300 """
301
302 import heapq as hq
303 import random
304 import sys
305 import types
306
307 from SimPy.Lister import Lister
308 from SimPy.Recording import Monitor, Tally
309 from SimPy.Lib import Process, SimEvent, PriorityQ, Resource, Level, Store
310
311
312 import SimPy.Globals as Globals
313 from SimPy.Globals import initialize, simulate, now, stopSimulation, \
314 _startWUStepping, _stopWUStepping, activate, reactivate
315
316
317 __TESTING = False
318 version = __version__ = '2.0 $Revision: 163 $ $Date: 2008-12-15 12:47:44 +0100 (Mo, 15 Dez 2008) $'
319 if __TESTING:
320 print 'SimPy.Simulation %s' %__version__,
321 if __debug__:
322 print '__debug__ on'
323 else:
324 print
325
326
327 hold = 1
328 passivate = 2
329 request = 3
330 release = 4
331 waitevent = 5
332 queueevent = 6
333 waituntil = 7
334 get = 8
335 put = 9
336
337
344
345
350
352 """Defines event list and operations on it"""
354
355
356 self.sim = sim
357 self.timestamps = []
358 self.sortpr = 0
359
360 - def _post(self, what, at, prior = False):
361 """Post an event notice for process what for time at"""
362
363 if at < self.sim._t:
364 raise Simerror('Attempt to schedule event in the past')
365 what._nextTime = at
366 self.sortpr -= 1
367 if prior:
368
369
370
371
372 what._rec = [at, self.sortpr, what, False]
373
374 hq.heappush(self.timestamps, what._rec)
375 else:
376
377
378 what._rec = [at,-self.sortpr, what, False]
379
380 hq.heappush(self.timestamps, what._rec)
381
382 - def _unpost(self, whom):
383 """
384 Mark event notice for whom as cancelled if whom is a suspended process
385 """
386 if whom._nextTime is not None:
387 whom._rec[3] = True
388 whom._nextTime = None
389
391 """Retrieve next event from event list"""
392 noActiveNotice = True
393
394 while noActiveNotice:
395 if self.timestamps:
396
397 (_tnotice, p, nextEvent, cancelled) = hq.heappop(self.timestamps)
398 noActiveNotice = cancelled
399 else:
400 raise Simerror('No more events at time %s' % self.sim._t)
401 nextEvent._rec = None
402 self.sim._t = _tnotice
403 if self.sim._t > self.sim._endtime:
404 self.sim._t = self.sim._endtime
405 self.sim._stop = True
406 return (None,)
407 try:
408 resultTuple = nextEvent._nextpoint.next()
409 except StopIteration:
410 nextEvent._nextpoint = None
411 nextEvent._terminated = True
412 nextEvent._nextTime = None
413 resultTuple = None
414 return (resultTuple, nextEvent)
415
417 return not self.timestamps
418
420 """Returns string with eventlist as
421 t1: [procname, procname2]
422 t2: [procname4, procname5, . . . ]
423 . . . .
424 """
425 ret = ''
426 for t in self.timestamps:
427 ret += '%s:%s\n' % (t[1]._nextTime, t[1].name)
428 return ret[:-1]
429
431 """Returns list of all times for which events are scheduled.
432 """
433 return self.timestamps
434
435
439
441 self._endtime = 0
442 self._t = 0
443 self._e = Evlist(self)
444 self._stop = False
445 self._wustep = False
446 self.condQ = []
447 self.allMonitors = []
448 self.allTallies = []
449
452
454 """Application function to stop simulation run"""
455 self._stop = True
456
458 """Application function to start stepping through simulation for waituntil construct."""
459 self._wustep = True
460
462 """Application function to stop stepping through simulation."""
463 self._wustep = False
464
466 """Returns string with eventlist as;
467 t1: processname, processname2
468 t2: processname4, processname5, . . .
469 . . . .
470 """
471 ret = ''
472 tempList = []
473 tempList[:] = self._e.timestamps
474 tempList.sort()
475
476 tempList = [[x[0],x[2].name] for x in tempList if not x[3]]
477 tprev=-1
478 for t in tempList:
479
480 if t[0] == tprev:
481
482 ret += ',%s'%t[1]
483 else:
484
485 if tprev==-1:
486 ret = '%s: %s' % (t[0],t[1])
487 else:
488 ret += '\n%s: %s' % (t[0],t[1])
489 tprev = t[0]
490 return ret + '\n'
491
493 """Returns list of all times for which events are scheduled.
494 """
495 r = []
496 r[:] = self._e.timestamps
497 r.sort()
498
499 r1 = [x[0] for x in r if not r[3]]
500 tprev=-1
501 ret = []
502 for t in r1:
503 if t == tprev:
504
505 pass
506 else:
507 ret.append(t)
508 tprev = t
509 return ret
510
511 - def activate(self, obj, process, at = 'undefined', delay = 'undefined',
512 prior = False):
513 """Application function to activate passive process."""
514 if self._e is None:
515 raise FatalSimerror\
516 ('Fatal error: simulation is not initialized (call initialize() first)')
517 if not (type(process) == types.GeneratorType):
518 raise FatalSimerror('Activating function which'+
519 ' is not a generator (contains no \'yield\')')
520
521 if not obj._terminated and not obj._nextTime:
522
523 obj._nextpoint = process
524 if at == 'undefined':
525 at = self._t
526 if delay == 'undefined':
527 zeit = max(self._t, at)
528 else:
529 zeit = max(self._t, self._t + delay)
530 self._e._post(obj, at = zeit, prior = prior)
531
532 - def reactivate(self, obj, at = 'undefined', delay = 'undefined',
533 prior = False):
534 """Application function to reactivate a process which is active,
535 suspended or passive."""
536
537 if not obj._terminated:
538 a = Process('SimPysystem',sim=self)
539 a.cancel(obj)
540
541 if at == 'undefined':
542 at = self._t
543 if delay == 'undefined':
544 zeit = max(self._t, at)
545 else:
546 zeit = max(self._t, self._t + delay)
547 self._e._post(obj, at = zeit, prior = prior)
548
550 """Starts data collection of all designated Monitor and Tally objects
551 (default = all) at time 'when'.
552 """
553 class Starter(Process):
554 def collect(self, monitors, tallies):
555 for m in monitors:
556 print m.name
557 m.reset()
558 for t in tallies:
559 t.reset()
560 yield hold, self
561 if monitors is None:
562 monitors = self.allMonitors
563 if tallies is None:
564 tallies = self.allTallies
565 s = Starter()
566 self.activate(s, s.collect(monitors = monitors, tallies = tallies),at = when)
567
568
570 """
571 Gets called by simulate after every event, as long as there are processes
572 waiting in self.condQ for a condition to be satisfied.
573 Tests the conditions for all waiting processes. Where condition satisfied,
574 reactivates that process immediately and removes it from queue.
575 """
576 rList = []
577 for el in self.condQ:
578 if el.cond():
579 rList.append(el)
580 self.reactivate(el)
581 for i in rList:
582 self.condQ.remove(i)
583
584 if not self.condQ:
585 self._stopWUStepping()
586
588 """
589 Puts a process 'proc' waiting for a condition into a waiting queue.
590 'cond' is a predicate function which returns True if the condition is
591 satisfied.
592 """
593 if not cond():
594 self.condQ.append(proc)
595 proc.cond = cond
596 self._startWUStepping()
597
598 proc._nextTime = None
599 else:
600
601 self._e._post(proc, at = self._t, prior = 1)
602
603
604
605
607 """Schedules Processes / semi - coroutines until time 'until'"""
608
609 """Gets called once. Afterwards, co - routines (generators) return by
610 'yield' with a cargo:
611 yield hold, self, <delay>: schedules the 'self' process for activation
612 after < delay > time units.If <,delay > missing,
613 same as 'yield hold, self, 0'
614
615 yield passivate, self : makes the 'self' process wait to be re - activated
616
617 yield request, self,<Resource > [,<priority>]: request 1 unit from < Resource>
618 with < priority > pos integer (default = 0)
619
620 yield release, self,<Resource> : release 1 unit to < Resource>
621
622 yield waitevent, self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]:
623 wait for one or more of several events
624
625
626 yield queueevent, self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]:
627 queue for one or more of several events
628
629 yield waituntil, self, cond : wait for arbitrary condition
630
631 yield get, self,<buffer > [,<WhatToGet > [,<priority>]]
632 get < WhatToGet > items from buffer (default = 1);
633 <WhatToGet > can be a pos integer or a filter function
634 (Store only)
635
636 yield put, self,<buffer > [,<WhatToPut > [,priority]]
637 put < WhatToPut > items into buffer (default = 1);
638 <WhatToPut > can be a pos integer (Level) or a list of objects
639 (Store)
640
641 EXTENSIONS:
642 Request with timeout reneging:
643 yield (request, self,<Resource>),(hold, self,<patience>) :
644 requests 1 unit from < Resource>. If unit not acquired in time period
645 <patience>, self leaves waitQ (reneges).
646
647 Request with event - based reneging:
648 yield (request, self,<Resource>),(waitevent, self,<eventlist>):
649 requests 1 unit from < Resource>. If one of the events in < eventlist > occurs before unit
650 acquired, self leaves waitQ (reneges).
651
652 Get with timeout reneging (for Store and Level):
653 yield (get, self,<buffer>,nrToGet etc.),(hold, self,<patience>)
654 requests < nrToGet > items / units from < buffer>. If not acquired < nrToGet > in time period
655 <patience>, self leaves < buffer>.getQ (reneges).
656
657 Get with event - based reneging (for Store and Level):
658 yield (get, self,<buffer>,nrToGet etc.),(waitevent, self,<eventlist>)
659 requests < nrToGet > items / units from < buffer>. If not acquired < nrToGet > before one of
660 the events in < eventlist > occurs, self leaves < buffer>.getQ (reneges).
661
662
663
664 Event notices get posted in event - list by scheduler after 'yield' or by
665 'activate' / 'reactivate' functions.
666
667 """
668 self._stop = False
669
670 if self._e is None:
671 raise FatalSimerror('Simulation not initialized')
672 if self._e._isEmpty():
673 self._e = None
674 message="SimPy: No activities scheduled"
675 return message
676
677 self._endtime = until
678 message = 'SimPy: Normal exit'
679 dispatch={hold:holdfunc, request:requestfunc, release:releasefunc,
680 passivate:passivatefunc, waitevent:waitevfunc, queueevent:queueevfunc,
681 waituntil:waituntilfunc, get:getfunc, put:putfunc}
682 commandcodes = dispatch.keys()
683 commandwords={hold:'hold', request:'request', release:'release', passivate:'passivate',
684 waitevent:'waitevent', queueevent:'queueevent', waituntil:'waituntil',
685 get:'get', put:'put'}
686 nextev = self._e._nextev
687 while not self._stop and self._t <= self._endtime:
688 try:
689 a = nextev()
690 if not a[0] is None:
691
692 if type(a[0][0]) == tuple:
693
694 command = a[0][0][0]
695 else:
696 command = a[0][0]
697 if __debug__:
698 if not command in commandcodes:
699 raise FatalSimerror('Illegal command: yield %s'%command)
700 dispatch[command](a)
701 except FatalSimerror, error:
702 print 'SimPy: ' + error.value
703 sys.exit(1)
704 except Simerror, error:
705 message = 'SimPy: ' + error.value
706 self._stop = True
707 if self._wustep:
708 self._test()
709 self._stopWUStepping()
710 self._e = None
711 return message
712
714 """Schedules Processes / semi - coroutines until time 'till'.
715 Deprecated since version 0.5.
716 """
717 simulate(until = till)
718
721
723 """Handles 'yield request, self, res' and 'yield (request, self, res),(<code>,self, par)'.
724 <code > can be 'hold' or 'waitevent'.
725 """
726 if type(a[0][0]) == tuple:
727
728
729 b = a[0][0]
730
731
732
733 b[2]._request(arg = (b, a[1]))
734
735
736 class _Holder(Process):
737 """Provides timeout process"""
738 def __init__(self,name,sim=None):
739 Process.__init__(self,name=name,sim=sim)
740 def trigger(self, delay):
741 yield hold, self, delay
742 if not proc in b[2].activeQ:
743 proc.sim.reactivate(proc)
744
745 class _EventWait(Process):
746 """Provides event waiting process"""
747 def __init__(self,name,sim=None):
748 Process.__init__(self,name=name,sim=sim)
749 def trigger(self, event):
750 yield waitevent, self, event
751 if not proc in b[2].activeQ:
752 proc.eventsFired = self.eventsFired
753 proc.sim.reactivate(proc)
754
755
756 proc = a[0][0][1]
757 actCode = a[0][1][0]
758 if actCode == hold:
759 proc._holder = _Holder(name = 'RENEGE - hold for %s'%proc.name,
760 sim=proc.sim)
761
762 proc.sim.activate(proc._holder, proc._holder.trigger(a[0][1][2]))
763 elif actCode == waituntil:
764 raise FatalSimerror('Illegal code for reneging: waituntil')
765 elif actCode == waitevent:
766 proc._holder = _EventWait(name = 'RENEGE - waitevent for %s'\
767 %proc.name,sim=proc.sim)
768
769 proc.sim.activate(proc._holder, proc._holder.trigger(a[0][1][2]))
770 elif actCode == queueevent:
771 raise FatalSimerror('Illegal code for reneging: queueevent')
772 else:
773 raise FatalSimerror('Illegal code for reneging %s'%actCode)
774 else:
775
776 a[0][2]._request(a)
777
780
783
785
786 evtpar = a[0][2]
787 if isinstance(evtpar, SimEvent):
788 a[0][2]._wait(a)
789
790 else:
791
792 evtpar[0]._waitOR(a)
793
795
796 evtpar = a[0][2]
797 if isinstance(evtpar, SimEvent):
798 a[0][2]._queue(a)
799
800 else:
801
802 evtpar[0]._queueOR(a)
803
806
808 """Handles 'yield get, self, buffer, what, priority' and
809 'yield (get, self, buffer, what, priority),(<code>,self, par)'.
810 <code > can be 'hold' or 'waitevent'.
811 """
812 if type(a[0][0]) == tuple:
813
814
815 b = a[0][0]
816
817
818
819 b[2]._get(arg = (b, a[1]))
820
821
822 class _Holder(Process):
823 """Provides timeout process"""
824 def __init__(self,**par):
825 Process.__init__(self,**par)
826 def trigger(self, delay):
827 yield hold, self, delay
828
829 if proc in b[2].getQ:
830 a[1].sim.reactivate(proc)
831
832 class _EventWait(Process):
833 """Provides event waiting process"""
834 def __init__(self,**par):
835 Process.__init__(self,**par)
836 def trigger(self, event):
837 yield waitevent, self, event
838 if proc in b[2].getQ:
839 a[1].eventsFired = self.eventsFired
840 a[1].sim.reactivate(proc)
841
842
843 proc = a[0][0][1]
844 actCode = a[0][1][0]
845 if actCode == hold:
846 proc._holder = _Holder(name='RENEGE - hold for %s'%proc.name,
847 sim=proc.sim)
848
849 a[1].sim.activate(proc._holder, proc._holder.trigger(a[0][1][2]))
850 elif actCode == waituntil:
851 raise FatalSimerror('Illegal code for reneging: waituntil')
852 elif actCode == waitevent:
853 proc._holder = _EventWait(name="RENEGE - waitevent for%s"\
854 %proc.name,sim=proc.sim)
855
856 a[1].sim.activate(proc._holder, proc._holder.trigger(a[0][1][2]))
857 elif actCode == queueevent:
858 raise FatalSimerror('Illegal code for reneging: queueevent')
859 else:
860 raise FatalSimerror('Illegal code for reneging %s'%actCode)
861 else:
862
863 a[0][2]._get(a)
864
865
867 """Handles 'yield put' (simple and compound hold / waitevent)
868 """
869 if type(a[0][0]) == tuple:
870
871
872 b = a[0][0]
873
874
875
876 b[2]._put(arg = (b, a[1]))
877
878
879 class _Holder(Process):
880 """Provides timeout process"""
881 def __init__(self,**par):
882 Process.__init__(self,**par)
883 def trigger(self, delay):
884 yield hold, self, delay
885
886 if proc in b[2].putQ:
887 a[1].sim.reactivate(proc)
888
889 class _EventWait(Process):
890 """Provides event waiting process"""
891 def __init__(self,**par):
892 Process.__init__(self,**par)
893 def trigger(self, event):
894 yield waitevent, self, event
895 if proc in b[2].putQ:
896 a[1].eventsFired = self.eventsFired
897 a[1].sim.reactivate(proc)
898
899
900 proc = a[0][0][1]
901 actCode = a[0][1][0]
902 if actCode == hold:
903 proc._holder = _Holder(name='RENEGE - hold for %s'%proc.name,
904 sim=proc.sim)
905
906 a[1].sim.activate(proc._holder, proc._holder.trigger(a[0][1][2]))
907 elif actCode == waituntil:
908 raise FatalSimerror('Illegal code for reneging: waituntil')
909 elif actCode == waitevent:
910 proc._holder = _EventWait(name='RENEGE - waitevent for %s'\
911 %proc.name,sim=proc.sim)
912
913 a[1].sim.activate(proc._holder, proc._holder.trigger(a[0][1][2]))
914 elif actCode == queueevent:
915 raise FatalSimerror('Illegal code for reneging: queueevent')
916 else:
917 raise FatalSimerror('Illegal code for reneging %s'%actCode)
918 else:
919
920 a[0][2]._put(a)
921
922
923 Globals.sim = Simulation()
924
925
926 if __name__ == '__main__':
927 print 'SimPy.Simulation %s' %__version__
928
930 class Aa(Process):
931 sequIn = []
932 sequOut = []
933 def __init__(self, holdtime, name,sim=None):
934 Process.__init__(self, name,sim=sim)
935 self.holdtime = holdtime
936
937 def life(self, priority):
938 for i in range(1):
939 Aa.sequIn.append(self.name)
940 print self.sim.now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
941 len(rrr.activeQ)
942 print 'waitQ: ',[(k.name, k._priority[rrr]) for k in rrr.waitQ]
943 print 'activeQ: ',[(k.name, k._priority[rrr]) \
944 for k in rrr.activeQ]
945 assert rrr.n + len(rrr.activeQ) == rrr.capacity, \
946 'Inconsistent resource unit numbers'
947 print self.sim.now(),self.name, 'requests 1 ', rrr.unitName
948 yield request, self, rrr, priority
949 print self.sim.now(),self.name, 'has 1 ', rrr.unitName
950 print self.sim.now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
951 len(rrr.activeQ)
952 print self.sim.now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
953 len(rrr.activeQ)
954 assert rrr.n + len(rrr.activeQ) == rrr.capacity, \
955 'Inconsistent resource unit numbers'
956 yield hold, self, self.holdtime
957 print self.sim.now(),self.name, 'gives up 1', rrr.unitName
958 yield release, self, rrr
959 Aa.sequOut.append(self.name)
960 print self.sim.now(),self.name, 'has released 1 ', rrr.unitName
961 print 'waitQ: ',[(k.name, k._priority[rrr]) for k in rrr.waitQ]
962 print self.sim.now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
963 len(rrr.activeQ)
964 assert rrr.n + len(rrr.activeQ) == rrr.capacity, \
965 'Inconsistent resource unit numbers'
966
967 class Observer(Process):
968 def __init__(self,**vars):
969 Process.__init__(self,**vars)
970
971 def observe(self, step, processes, res):
972 while self.sim.now() < 11:
973 for i in processes:
974 print '++ %s process: %s: active:%s, passive:%s, terminated: %s, interrupted:%s, queuing:%s'\
975 %(self.sim.now(),i.name, i.active(),i.passive(),\
976 i.terminated(),i.interrupted(),i.queuing(res))
977 print
978 yield hold, self, step
979
980 print'\n+++test_demo output'
981 print '****First case == priority queue, resource service not preemptable'
982 s=Simulation()
983 s.initialize()
984 rrr = Resource(5, name = 'Parking', unitName = 'space(s)', qType = PriorityQ,
985 preemptable = 0,sim=s)
986 procs = []
987 for i in range(10):
988 z = Aa(holdtime = i, name = 'Car ' + str(i),sim=s)
989 procs.append(z)
990 s.activate(z, z.life(priority = i))
991 o = Observer(sim=s)
992 s.activate(o, o.observe(1, procs, rrr))
993 a = s.simulate(until = 10000)
994 print a
995 print 'Input sequence: ', Aa.sequIn
996 print 'Output sequence: ', Aa.sequOut
997
998 print '\n****Second case == priority queue, resource service preemptable'
999 s=Simulation()
1000 s.initialize()
1001 rrr = Resource(5, name = 'Parking', unitName = 'space(s)', qType = PriorityQ,
1002 preemptable = 1,sim=s)
1003 procs = []
1004 for i in range(10):
1005 z = Aa(holdtime = i, name = 'Car ' + str(i),sim=s)
1006 procs.append(z)
1007 s.activate(z, z.life(priority = i))
1008 o = Observer(sim=s)
1009 s.activate(o, o.observe(1, procs, rrr))
1010 Aa.sequIn = []
1011 Aa.sequOut = []
1012 a = s.simulate(until = 10000)
1013 print a
1014 print 'Input sequence: ', Aa.sequIn
1015 print 'Output sequence: ', Aa.sequOut
1016
1018 class Bus(Process):
1019 def __init__(self, **vars):
1020 Process.__init__(self, **vars)
1021
1022 def operate(self, repairduration = 0):
1023 print self.sim.now(),'>> %s starts' % (self.name)
1024 tripleft = 1000
1025 while tripleft > 0:
1026 yield hold, self, tripleft
1027 if self.interrupted():
1028 print 'interrupted by %s' %self.interruptCause.name
1029 print '%s: %s breaks down ' %(now(),self.name)
1030 tripleft = self.interruptLeft
1031 self.interruptReset()
1032 print 'tripleft ', tripleft
1033 s.reactivate(br, delay = repairduration)
1034 yield hold, self, repairduration
1035 print self.sim.now(),' repaired'
1036 else:
1037 break
1038 print self.sim.now(),'<< %s done' % (self.name)
1039
1040 class Breakdown(Process):
1041 def __init__(self, myBus,sim=None):
1042 Process.__init__(self, name = 'Breakdown ' + myBus.name,sim=sim)
1043 self.bus = myBus
1044
1045 def breakBus(self, interval):
1046
1047 while True:
1048 yield hold, self, interval
1049 if self.bus.terminated(): break
1050 self.interrupt(self.bus)
1051
1052 print'\n\n+++test_interrupt'
1053 s=Simulation()
1054 s.initialize()
1055 b = Bus(name='Bus 1',sim=s)
1056 s.activate(b, b.operate(repairduration = 20))
1057 br = Breakdown(b,sim=s)
1058 s.activate(br, br.breakBus(200))
1059 print s.simulate(until = 4000)
1060
1062 class Waiter(Process):
1063 def __init__(self,**vars):
1064 Process.__init__(self,**vars)
1065 def waiting(self, theSignal):
1066 while True:
1067 yield waitevent, self, theSignal
1068 print '%s: process \'%s\' continued after waiting for %s' %\
1069 (self.sim.now(),self.name, theSignal.name)
1070 yield queueevent, self, theSignal
1071 print '%s: process \'%s\' continued after queueing for %s' % (now(),self.name, theSignal.name)
1072
1073 class ORWaiter(Process):
1074 def __init__(self,**vars):
1075 Process.__init__(self,**vars)
1076 def waiting(self, signals):
1077 while True:
1078 yield waitevent, self, signals
1079 print self.sim.now(),'one of %s signals occurred' %\
1080 [x.name for x in signals]
1081 print '\t%s (fired / param)'%\
1082 [(x.name, x.signalparam) for x in self.eventsFired]
1083 yield hold, self, 1
1084
1085 class Caller(Process):
1086 def __init__(self,**vars):
1087 Process.__init__(self,**vars)
1088 def calling(self):
1089 while True:
1090 signal1.signal('wake up!')
1091 print '%s: signal 1 has occurred'%now()
1092 yield hold, self, 10
1093 signal2.signal('and again')
1094 signal2.signal('sig 2 again')
1095 print '%s: signal1, signal2 have occurred'%now()
1096 yield hold, self, 10
1097 print'\n+++testSimEvents output'
1098 s=Simulation()
1099 s.initialize()
1100 signal1 = SimEvent('signal 1',sim=s)
1101 signal2 = SimEvent('signal 2',sim=s)
1102 signal1.signal('startup1')
1103 signal2.signal('startup2')
1104 w1 = Waiter(name='waiting for signal 1',sim=s)
1105 s.activate(w1, w1.waiting(signal1))
1106 w2 = Waiter(name='waiting for signal 2',sim=s)
1107 s.activate(w2, w2.waiting(signal2))
1108 w3 = Waiter(name='also waiting for signal 2',sim=s)
1109 s.activate(w3, w3.waiting(signal2))
1110 w4 = ORWaiter(name='waiting for either signal 1 or signal 2',sim=s)
1111 s.activate(w4, w4.waiting([signal1, signal2]),prior = True)
1112 c = Caller(name='Caller',sim=s)
1113 s.activate(c, c.calling())
1114 print s.simulate(until = 100)
1115
1117 """
1118 Demo of waitUntil capability.
1119
1120 Scenario:
1121 Three workers require sets of tools to do their jobs. Tools are shared,
1122 scarce resources for which they compete.
1123 """
1124 class Worker(Process):
1125 def __init__(self, name, heNeeds = [],sim=None):
1126 Process.__init__(self, name,sim=sim)
1127 self.heNeeds = heNeeds
1128 def work(self):
1129 def workerNeeds():
1130 for item in self.heNeeds:
1131 if item.n == 0:
1132 return False
1133 return True
1134
1135 while self.sim.now() < 8 * 60:
1136 yield waituntil, self, workerNeeds
1137 for item in self.heNeeds:
1138 yield request, self, item
1139 print '%s %s has %s and starts job' % (self.sim.now(),self.name,
1140 [x.name for x in self.heNeeds])
1141 yield hold, self, random.uniform(10, 30)
1142 for item in self.heNeeds:
1143 yield release, self, item
1144 yield hold, self, 2
1145
1146 print '\n+++ nwaituntil demo output'
1147 random.seed(12345)
1148 s=Simulation()
1149 s.initialize()
1150 brush = Resource(capacity = 1, name = 'brush',sim=s)
1151 ladder = Resource(capacity = 2, name = 'ladder',sim=s)
1152 hammer = Resource(capacity = 1, name = 'hammer',sim=s)
1153 saw = Resource(capacity = 1, name = 'saw',sim=s)
1154 painter = Worker('painter',[brush, ladder],sim=s)
1155 s.activate(painter, painter.work())
1156 roofer = Worker('roofer',[hammer, ladder, ladder],sim=s)
1157 s.activate(roofer, roofer.work())
1158 treeguy = Worker('treeguy',[saw, ladder],sim=s)
1159 s.activate(treeguy, treeguy.work())
1160 for who in (painter, roofer, treeguy):
1161 print '%s needs %s for his job' %\
1162 (who.name,[x.name for x in who.heNeeds])
1163 print
1164 print s.simulate(until = 9 * 60)
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1176 """ Job class for testing timeout reneging
1177 """
1178 - def __init__(self, server = None, name = '',sim=None):
1182
1183 - def execute(self, timeout, usetime):
1184 yield (request, self, self.res),(hold, self, timeout)
1185 if self.acquired(self.res):
1186 self.gotResource = True
1187 yield hold, self, usetime
1188 yield release, self, self.res
1189 else:
1190 self.gotResource = False
1191
1192
1194 """Test that resource gets acquired without timeout
1195 """
1196 s=Simulation()
1197 s.initialize()
1198 res = Resource(name = 'Server', capacity = 1,sim=s)
1199 usetime = 5
1200 timeout = 1000000
1201 j1 = JobTO(server = res, name = 'Job_1',sim=s)
1202 s.activate(j1, j1.execute(timeout = timeout, usetime = usetime))
1203 j2 = JobTO(server = res, name = 'Job_2',sim=s)
1204 s.activate(j2, j2.execute(timeout = timeout, usetime = usetime))
1205 s.simulate(until = 2 * usetime)
1206 assert s.now() == 2 * usetime, 'time not == 2 * usetime'
1207 assert j1.gotResource and j2.gotResource,\
1208 'at least one job failed to get resource'
1209 assert not (res.waitQ or res.activeQ),\
1210 'job waiting or using resource'
1211
1213 """Test that timeout occurs when resource busy
1214 """
1215 s=Simulation()
1216 s.initialize()
1217 res = Resource(name = 'Server', capacity = 1, monitored = True,sim=s)
1218 usetime = 5
1219 timeout = 3
1220 j1 = JobTO(server = res, name = 'Job_1',sim=s)
1221 s.activate(j1, j1.execute(timeout = timeout, usetime = usetime))
1222 j2 = JobTO(server = res, name = 'Job_2',sim=s)
1223 s.activate(j2, j2.execute(timeout = timeout, usetime = usetime))
1224 s.simulate(until = 2 * usetime)
1225 assert(s.now() == usetime),'time not == usetime'
1226 assert(j1.gotResource),'Job_1 did not get resource'
1227 assert(not j2.gotResource),'Job_2 did not renege'
1228 assert not (res.waitQ or res.activeQ),\
1229 'job waiting or using resource'
1230
1232 """Test that timeout occurs when resource has no capacity free
1233 """
1234 s=Simulation()
1235 s.initialize()
1236 res = Resource(name = 'Server', capacity = 0,sim=s)
1237 usetime = 5
1238 timeout = 3
1239 j1 = JobTO(server = res, name = 'Job_1',sim=s)
1240 s.activate(j1, j1.execute(timeout = timeout, usetime = usetime))
1241 j2 = JobTO(server = res, name = 'Job_2',sim=s)
1242 s.activate(j2, j2.execute(timeout = timeout, usetime = usetime))
1243 s.simulate(until = 2 * usetime)
1244 assert s.now() == timeout, 'time %s not == timeout'%now()
1245 assert not j1.gotResource, 'Job_1 got resource'
1246 assert not j2.gotResource, 'Job_2 got resource'
1247 assert not (res.waitQ or res.activeQ),\
1248 'job waiting or using resource'
1249
1250
1251
1252
1253
1255 """ Job class for testing event reneging
1256 """
1257 - def __init__(self, server = None, name = '',sim=None):
1261
1262 - def execute(self, event, usetime):
1263 yield (request, self, self.res),(waitevent, self, event)
1264 if self.acquired(self.res):
1265 self.gotResource = True
1266 yield hold, self, usetime
1267 yield release, self, self.res
1268 else:
1269 self.gotResource = False
1270
1272 """ Job class for testing event reneging with multi - event lists
1273 """
1274 - def __init__(self, server = None, name = '',sim=None):
1278
1279 - def execute(self, eventlist, usetime):
1280 yield (request, self, self.res),(waitevent, self, eventlist)
1281 if self.acquired(self.res):
1282 self.gotResource = True
1283 yield hold, self, usetime
1284 yield release, self, self.res
1285 else:
1286 self.gotResource = False
1287
1289 """Fires reneging event
1290 """
1293 - def fire(self, fireDelay, event):
1294 yield hold, self, fireDelay
1295 event.signal()
1296
1298 """Test that processes acquire resource normally if no event fires
1299 """
1300 s=Simulation()
1301 s.initialize()
1302 res = Resource(name = 'Server', capacity = 1,sim=s)
1303 event = SimEvent(name='Renege_trigger',sim=s)
1304 usetime = 5
1305 j1 = JobEvt(server = res, name = 'Job_1',sim=s)
1306 s.activate(j1, j1.execute(event = event, usetime = usetime))
1307 j2 = JobEvt(server = res, name = 'Job_2',sim=s)
1308 s.activate(j2, j2.execute(event = event, usetime = usetime))
1309 s.simulate(until = 2 * usetime)
1310
1311 assert s.now() == 2 * usetime, 'time not == 2 * usetime'
1312 assert j1.gotResource and j2.gotResource,\
1313 'at least one job failed to get resource'
1314 assert not (res.waitQ or res.activeQ),\
1315 'job waiting or using resource'
1316
1318 """Test that signalled event leads to renege when resource busy
1319 """
1320 s=Simulation()
1321 s.initialize()
1322 res = Resource(name = 'Server', capacity = 1,sim=s)
1323 event = SimEvent('Renege_trigger',sim=s)
1324 usetime = 5
1325 eventtime = 1
1326 j1 = JobEvt(server = res, name = 'Job_1',sim=s)
1327 s.activate(j1, j1.execute(event = event, usetime = usetime))
1328 j2 = JobEvt(server = res, name = 'Job_2',sim=s)
1329 s.activate(j2, j2.execute(event = event, usetime = usetime))
1330 f = FireEvent(name = 'FireEvent',sim=s)
1331 s.activate(f, f.fire(fireDelay = eventtime, event = event))
1332 s.simulate(until = 2 * usetime)
1333
1334 assert(s.now() == usetime),'time not == usetime'
1335 assert(j1.gotResource),'Job_1 did not get resource'
1336 assert(not j2.gotResource),'Job_2 did not renege'
1337 assert not (res.waitQ or res.activeQ),\
1338 'job waiting or using resource'
1339
1341 """Test that renege - triggering event can be one of an event list
1342 """
1343 s=Simulation()
1344 s.initialize()
1345 res = Resource(name = 'Server', capacity = 1,sim=s)
1346 event1 = SimEvent('Renege_trigger_1',sim=s)
1347 event2 = SimEvent('Renege_trigger_2',sim=s)
1348 usetime = 5
1349 eventtime = 1
1350 j1 = JobEvtMulti(server = res, name = 'Job_1',sim=s)
1351 s.activate(j1, j1.execute(eventlist = [event1, event2],usetime = usetime))
1352 j2 = JobEvtMulti(server = res, name = 'Job_2',sim=s)
1353 s.activate(j2, j2.execute(eventlist = [event1, event2],usetime = usetime))
1354 f1 = FireEvent(name = 'FireEvent_1',sim=s)
1355 s.activate(f1, f1.fire(fireDelay = eventtime, event = event1))
1356 f2 = FireEvent(name = 'FireEvent_2',sim=s)
1357 s.activate(f2, f2.fire(fireDelay = eventtime, event = event2))
1358 s.simulate(until = 2 * usetime)
1359
1360 assert(s.now() == usetime),'time not == usetime'
1361 assert(j1.gotResource),'Job_1 did not get resource'
1362 assert(not j2.gotResource),'Job_2 did not renege'
1363 assert not (res.waitQ or res.activeQ),\
1364 'job waiting or using resource'
1365
1366 testNoTimeout()
1367 testTimeout1()
1368 testTimeout2()
1369 testNoEvent()
1370 testWaitEvent1()
1371 testWaitEvent2()
1372 test_demo()
1373 test_interrupt()
1374 testSimEvents()
1375 testwaituntil()
1376