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