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