1
2 from SimPy.Lister import *
3 import heapq as hq
4 import types
5 import time
6 import sys
7 import new
8 import random
9 import inspect
10
11
12
13 """SimulationRT 1.9.1 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
121 (like printHistogram).
122
123 18 Dec 2006: - Added trace printing of Buffers' "unitName" for yield get and put.
124
125 09 Jun 2007: - Cleaned out all uses of "object" to prevent name clash.
126
127 18 Nov 2007: - Start of 1.9 development
128 - Added 'start' method (alternative to activate) to Process
129
130 22 Nov 2007: - Major change to event list handling to speed up larger models:
131 * Drop dictionary
132 * Replace bisect by heapq
133 * Mark cancelled event notices in unpost and skip them in
134 nextev (great idea of Tony Vignaux))
135
136 4 Dec 2007: - Added twVariance calculation for both Monitor and Tally (gav)
137
138 5 Dec 2007: - Changed name back to timeVariance (gav)
139
140 1 Mar 2008: - Start of 1.9.1 bugfix release
141 - Delete circular reference in Process instances when event
142 notice has been processed (caused much circular garbage)
143
144 """
145 __TESTING=False
146 version=__version__="1.9.1 $Revision: 1.1.1.28 $ $Date: 2008/03/03 13:53:42 $"
147 if __TESTING:
148 print "SimPy.SimulationRT %s" %__version__,
149 if __debug__:
150 print "__debug__ on"
151 else:
152 print
153
154
155 hold=1
156 passivate=2
157 request=3
158 release=4
159 waitevent=5
160 queueevent=6
161 waituntil=7
162 get=8
163 put=9
164
165 _endtime=0
166 _t=0
167 _e=None
168 _stop=True
169 _wustep=False
170 try:
171 True, False
172 except NameError:
173 True, False = (1 == 1), (0 == 1)
174 condQ=[]
175 allMonitors=[]
176 allTallies=[]
177
178 if sys.platform=="win32":
179 wallclock=time.clock
180 else:
181 wallclock=time.time
182 rtstart=wallclock()
183
186
188 """resets the the ratio simulation time over clock time(seconds).
189 """
190 if _e is None:
191 raise FatalSimerror("Fatal SimPy error: Simulation not initialized")
192 _e.rel_speed=rel_speed
193
202
205
207 """Application function to stop simulation run"""
208 global _stop
209 _stop=True
210
212 """Application function to start stepping through simulation for waituntil construct."""
213 global _wustep
214 _wustep=True
215
217 """Application function to stop stepping through simulation."""
218 global _wustep
219 _wustep=False
220
223 self.value=value
224
226 return `self.value`
227
232
234 """Superclass of classes which may use generator functions"""
236
237 self._nextpoint=None
238 self.name=name
239 self._nextTime=None
240 self._remainService=0
241 self._preempted=0
242 self._priority={}
243 self._getpriority={}
244 self._putpriority={}
245 self._terminated= False
246 self._inInterrupt= False
247 self.eventsFired=[]
248
250 return self._nextTime <> None and not self._inInterrupt
251
253 return self._nextTime is None and not self._terminated
254
256 return self._terminated
257
259 return self._inInterrupt and not self._terminated
260
262 return self in resource.waitQ
263
265 """Application function to cancel all event notices for this Process
266 instance;(should be all event notices for the _generator_)."""
267 _e._unpost(whom=victim)
268
269 - def start(self,pem=None,at="undefined",delay="undefined",prior=False):
270 """Activates PEM of this Process.
271 p.start(p.pemname([args])[,{at= t |delay=period}][,prior=False]) or
272 p.start([p.ACTIONS()][,{at= t |delay=period}][,prior=False]) (ACTIONS
273 parameter optional)
274 """
275 if pem is None:
276 try:
277 pem=self.ACTIONS()
278 except AttributeError:
279 raise FatalSimerror\
280 ("Fatal SimPy error: no generator function to activate")
281 else:
282 pass
283 if _e is None:
284 raise FatalSimerror\
285 ("Fatal SimPy error: simulation is not initialized"\
286 "(call initialize() first)")
287 if not (type(pem) == types.GeneratorType):
288 raise FatalSimerror("Fatal SimPy error: activating function which"+
289 " is not a generator (contains no 'yield')")
290 if not self._terminated and not self._nextTime:
291
292 self._nextpoint=pem
293 if at=="undefined":
294 at=_t
295 if delay=="undefined":
296 zeit=max(_t,at)
297 else:
298 zeit=max(_t,_t+delay)
299 _e._post(what=self,at=zeit,prior=prior)
300
302 if len(a[0]) == 3:
303 delay=abs(a[0][2])
304 else:
305 delay=0
306 who=a[1]
307 self.interruptLeft=delay
308 self._inInterrupt=False
309 self.interruptCause=None
310 _e._post(what=who,at=_t+delay)
311
313 a[0][1]._nextTime=None
314
316 """Application function to interrupt active processes"""
317
318 if victim.active():
319 victim.interruptCause=self
320 left=victim._nextTime-_t
321 victim.interruptLeft=left
322 victim._inInterrupt=True
323 reactivate(victim)
324 return left
325 else:
326 return None
327
329 """
330 Application function for an interrupt victim to get out of
331 'interrupted' state.
332 """
333 self._inInterrupt= False
334
336 """Multi-functional test for reneging for 'request' and 'get':
337 (1)If res of type Resource:
338 Tests whether resource res was acquired when proces reactivated.
339 If yes, the parallel wakeup process is killed.
340 If not, process is removed from res.waitQ (reneging).
341 (2)If res of type Store:
342 Tests whether item(s) gotten from Store res.
343 If yes, the parallel wakeup process is killed.
344 If no, process is removed from res.getQ
345 (3)If res of type Level:
346 Tests whether units gotten from Level res.
347 If yes, the parallel wakeup process is killed.
348 If no, process is removed from res.getQ.
349 """
350 if isinstance(res,Resource):
351 test=self in res.activeQ
352 if test:
353 self.cancel(self._holder)
354 else:
355 res.waitQ.remove(self)
356 if res.monitored:
357 res.waitMon.observe(len(res.waitQ),t=now())
358 return test
359 elif isinstance(res,Store):
360 test=len(self.got)
361 if test:
362 self.cancel(self._holder)
363 else:
364 res.getQ.remove(self)
365 if res.monitored:
366 res.getQMon.observe(len(res.getQ),t=now())
367 return test
368 elif isinstance(res,Level):
369 test=not (self.got is None)
370 if test:
371 self.cancel(self._holder)
372 else:
373 res.getQ.remove(self)
374 if res.monitored:
375 res.getQMon.observe(len(res.getQ),t=now())
376 return test
377
379 """Test for reneging for 'yield put . . .' compound statement (Level and
380 Store. Returns True if not reneged.
381 If self not in buffer.putQ, kill wakeup process, else take self out of
382 buffer.putQ (reneged)"""
383 test=self in buffer.putQ
384 if test:
385 buffer.putQ.remove(self)
386 if buffer.monitored:
387 buffer.putQMon.observe(len(buffer.putQ),t=now())
388 else:
389 self.cancel(self._holder)
390 return not test
391
393 """Returns string with eventlist as;
394 t1: processname,processname2
395 t2: processname4,processname5, . . .
396 . . . .
397 """
398 ret=""
399 tempList=[]
400 tempList[:]=_e.timestamps
401 tempList.sort()
402
403 tempList=[[x[0],x[2].name] for x in tempList if not x[3]]
404 tprev=-1
405 for t in tempList:
406
407 if t[0]==tprev:
408
409 ret+=",%s"%t[1]
410 else:
411
412 if tprev==-1:
413 ret="%s: %s"%(t[0],t[1])
414 else:
415 ret+="\n%s: %s"%(t[0],t[1])
416 tprev=t[0]
417 return ret+"\n"
418
420 """Returns list of all times for which events are scheduled.
421 """
422 r=[]
423 r[:]=_e.timestamps
424 r.sort()
425
426 r1=[x[0] for x in r if not r[3]]
427 tprev=-1
428 ret=[]
429 for t in r1:
430 if t==tprev:
431
432 pass
433 else:
434 ret.append(t)
435 tprev=t
436 return ret
437
439 """Defines event list and operations on it"""
441
442
443 self.timestamps = []
444 self.sortpr=0
445 self.real_time=False
446 self.rel_speed=1
447 self.rtlast = wallclock()
448 self.stlast = 0
449
450 - def _post(self, what, at, prior=False):
451 """Post an event notice for process what for time at"""
452
453 if at < _t:
454 raise Simerror("Attempt to schedule event in the past")
455 what._nextTime = at
456 self.sortpr-=1
457 if prior:
458
459
460
461
462 what._rec=[at,self.sortpr,what,False]
463
464 hq.heappush(self.timestamps,what._rec)
465 else:
466
467
468 what._rec=[at,-self.sortpr,what,False]
469
470 hq.heappush(self.timestamps,what._rec)
471
472 - def _unpost(self, whom):
473 """
474 Mark event notice for whom as cancelled if whom is a suspended process
475 """
476 if whom._nextTime is not None:
477 whom._rec[3]=True
478 whom._nextTime=None
479
481 """Retrieve next event from event list"""
482 global _t, _stop
483 noActiveNotice=True
484
485 while noActiveNotice:
486 if self.timestamps:
487
488 (_tnotice, p,nextEvent,cancelled) = hq.heappop(self.timestamps)
489 noActiveNotice=cancelled
490 else:
491 raise Simerror("No more events at time %s" % _t)
492 nextEvent._rec=None
493 _t=_tnotice
494
495
496 if self.real_time:
497 next_time = 1.0*(_t - self.stlast)/self.rel_speed
498 next_time += self.rtlast
499 delay=next_time - wallclock()
500 if delay > 0:
501 time.sleep(delay)
502 self.rtlast = wallclock()
503 self.stlast = _t
504 if _t > _endtime:
505 _t = _endtime
506 _stop = True
507 return (None,)
508 try:
509 resultTuple = nextEvent._nextpoint.next()
510 except StopIteration:
511 nextEvent._nextpoint = None
512 nextEvent._terminated = True
513 nextEvent._nextTime = None
514 resultTuple = None
515 return (resultTuple, nextEvent)
516
518 return not self.timestamps
519
521 """Returns string with eventlist as
522 t1: [procname,procname2]
523 t2: [procname4,procname5, . . . ]
524 . . . .
525 """
526 ret=""
527 for t in self.timestamps:
528 ret+="%s:%s\n"%(t[1]._nextTime, t[1].name)
529 return ret[:-1]
530
532 """Returns list of all times for which events are scheduled.
533 """
534 return self.timestamps
535
536 -def activate(obj,process,at="undefined",delay="undefined",prior=False):
537 """Application function to activate passive process."""
538 if _e is None:
539 raise FatalSimerror\
540 ("Fatal error: simulation is not initialized (call initialize() first)")
541 if not (type(process) == types.GeneratorType):
542 raise FatalSimerror("Activating function which"+
543 " is not a generator (contains no 'yield')")
544 if not obj._terminated and not obj._nextTime:
545
546 obj._nextpoint=process
547 if at=="undefined":
548 at=_t
549 if delay=="undefined":
550 zeit=max(_t,at)
551 else:
552 zeit=max(_t,_t+delay)
553 _e._post(obj,at=zeit,prior=prior)
554
555 -def reactivate(obj,at="undefined",delay="undefined",prior=False):
556 """Application function to reactivate a process which is active,
557 suspended or passive."""
558
559 if not obj._terminated:
560 a=Process("SimPysystem")
561 a.cancel(obj)
562
563 if at=="undefined":
564 at=_t
565 if delay=="undefined":
566 zeit=max(_t,at)
567 else:
568 zeit=max(_t,_t+delay)
569 _e._post(obj,at=zeit,prior=prior)
570
572 """ A histogram gathering and sampling class"""
573
574 - def __init__(self,name = '',low=0.0,high=100.0,nbins=10):
575 list.__init__(self)
576 self.name = name
577 self.low = float(low)
578 self.high = float(high)
579 self.nbins = nbins
580 self.binsize=(self.high-self.low)/nbins
581 self._nrObs=0
582 self._sum=0
583 self[:] =[[low+(i-1)*self.binsize,0] for i in range(self.nbins+2)]
584
586 """ add a value into the correct bin"""
587 self._nrObs+=1
588 self._sum+=y
589 b = int((y-self.low+self.binsize)/self.binsize)
590 if b < 0: b = 0
591 if b > self.nbins+1: b = self.nbins+1
592 assert 0 <= b <=self.nbins+1,'Histogram.addIn: b out of range: %s'%b
593 self[b][1]+=1
594
596 histo=self
597 ylab="value"
598 nrObs=self._nrObs
599 width=len(str(nrObs))
600 res=[]
601 res.append("<Histogram %s:"%self.name)
602 res.append("\nNumber of observations: %s"%nrObs)
603 if nrObs:
604 su=self._sum
605 cum=histo[0][1]
606 fmt="%s"
607 line="\n%s <= %s < %s: %s (cum: %s/%s%s)"\
608 %(fmt,"%s",fmt,"%s","%s","%5.1f","%s")
609 line1="\n%s%s < %s: %s (cum: %s/%s%s)"\
610 %("%s","%s",fmt,"%s","%s","%5.1f","%s")
611 l1width=len(("%s <= "%fmt)%histo[1][0])
612 res.append(line1\
613 %(" "*l1width,ylab,histo[1][0],str(histo[0][1]).rjust(width),\
614 str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
615 )
616 for i in range(1,len(histo)-1):
617 cum+=histo[i][1]
618 res.append(line\
619 %(histo[i][0],ylab,histo[i+1][0],str(histo[i][1]).rjust(width),\
620 str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
621 )
622 cum+=histo[-1][1]
623 linen="\n%s <= %s %s : %s (cum: %s/%s%s)"\
624 %(fmt,"%s","%s","%s","%s","%5.1f","%s")
625 lnwidth=len(("<%s"%fmt)%histo[1][0])
626 res.append(linen\
627 %(histo[-1][0],ylab," "*lnwidth,str(histo[-1][1]).rjust(width),\
628 str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
629 )
630 res.append("\n>")
631 return " ".join(res)
632
634 """Starts data collection of all designated Monitor and Tally objects
635 (default=all) at time 'when'.
636 """
637 class Starter(Process):
638 def collect(self,monitors,tallies):
639 for m in monitors:
640 print m.name
641 m.reset()
642 for t in tallies:
643 t.reset()
644 yield hold,self
645 if monitors is None:
646 monitors=allMonitors
647 if tallies is None:
648 tallies=allTallies
649 s=Starter()
650 activate(s,s.collect(monitors=monitors,tallies=tallies),at=when)
651
653 """ Monitored variables
654
655 A Class for monitored variables, that is, variables that allow one
656 to gather simple statistics. A Monitor is a subclass of list and
657 list operations can be performed on it. An object is established
658 using m= Monitor(name = '..'). It can be given a
659 unique name for use in debugging and in tracing and ylab and tlab
660 strings for labelling graphs.
661 """
662 - def __init__(self,name='a_Monitor',ylab='y',tlab='t'):
663 list.__init__(self)
664 self.startTime = 0.0
665 self.name = name
666 self.ylab = ylab
667 self.tlab = tlab
668 allMonitors.append(self)
669
670 - def setHistogram(self,name = '',low=0.0,high=100.0,nbins=10):
671 """Sets histogram parameters.
672 Must be called before call to getHistogram"""
673 if name=='':
674 histname=self.name
675 else:
676 histname=name
677 self.histo=Histogram(name=histname,low=low,high=high,nbins=nbins)
678
680 """record y and t"""
681 if t is None: t = now()
682 self.append([t,y])
683
685 """ deprecated: tally for backward compatibility"""
686 self.observe(y,0)
687
688 - def accum(self,y,t=None):
689 """ deprecated: accum for backward compatibility"""
690 self.observe(y,t)
691
693 """reset the sums and counts for the monitored variable """
694 self[:]=[]
695 if t is None: t = now()
696 self.startTime = t
697
699 """ the series of measured times"""
700 return list(zip(*self)[0])
701
703 """ the series of measured values"""
704 return list(zip(*self)[1])
705
707 """ deprecated: the number of observations made """
708 return self.__len__()
709
711 """ the sum of the y"""
712 if self.__len__()==0: return 0
713 else:
714 sum = 0.0
715 for i in range(self.__len__()):
716 sum += self[i][1]
717 return sum
718
720 """ the simple average of the monitored variable"""
721 try: return 1.0*self.total()/self.__len__()
722 except: print 'SimPy: No observations for mean'
723
725 """ the sample variance of the monitored variable """
726 n = len(self)
727 tot = self.total()
728 ssq=0.0
729
730 for i in range(self.__len__()):
731 ssq += self[i][1]**2
732 try: return (ssq - float(tot*tot)/n)/n
733 except: print 'SimPy: No observations for sample variance'
734
736 """ the time-weighted average of the monitored variable.
737
738 If t is used it is assumed to be the current time,
739 otherwise t = now()
740 """
741 N = self.__len__()
742 if N == 0:
743 print 'SimPy: No observations for timeAverage'
744 return None
745
746 if t is None: t = now()
747 sum = 0.0
748 tlast = self.startTime
749
750 ylast = 0.0
751 for i in range(N):
752 ti,yi = self[i]
753 sum += ylast*(ti-tlast)
754 tlast = ti
755 ylast = yi
756 sum += ylast*(t-tlast)
757 T = t - self.startTime
758 if T == 0:
759 print 'SimPy: No elapsed time for timeAverage'
760 return None
761
762 return sum/float(T)
763
765 """ the time-weighted Variance of the monitored variable.
766
767 If t is used it is assumed to be the current time,
768 otherwise t = now()
769 """
770 N = self.__len__()
771 if N == 0:
772 print 'SimPy: No observations for timeVariance'
773 return None
774 if t is None: t = now()
775 sm = 0.0
776 ssq = 0.0
777 tlast = self.startTime
778
779 ylast = 0.0
780 for i in range(N):
781 ti,yi = self[i]
782 sm += ylast*(ti-tlast)
783 ssq += ylast*ylast*(ti-tlast)
784 tlast = ti
785 ylast = yi
786 sm += ylast*(t-tlast)
787 ssq += ylast*ylast*(t-tlast)
788 T = t - self.startTime
789 if T == 0:
790 print 'SimPy: No elapsed time for timeVariance'
791 return None
792 mn = sm/float(T)
793
794 return ssq/float(T) - mn*mn
795
796
797 - def histogram(self,low=0.0,high=100.0,nbins=10):
798 """ A histogram of the monitored y data values.
799 """
800 h = Histogram(name=self.name,low=low,high=high,nbins=nbins)
801 ys = self.yseries()
802 for y in ys: h.addIn(y)
803 return h
804
806 """Returns a histogram based on the parameters provided in
807 preceding call to setHistogram.
808 """
809 ys = self.yseries()
810 h=self.histo
811 for y in ys: h.addIn(y)
812 return h
813
815 """Returns formatted frequency distribution table string from Monitor.
816 Precondition: setHistogram must have been called.
817 fmt==format of bin range values
818 """
819 try:
820 histo=self.getHistogram()
821 except:
822 raise FatalSimerror("histogramTable: call setHistogram first"\
823 " for Monitor %s"%self.name)
824 ylab=self.ylab
825 nrObs=self.count()
826 width=len(str(nrObs))
827 res=[]
828 res.append("\nHistogram for %s:"%histo.name)
829 res.append("\nNumber of observations: %s"%nrObs)
830 su=sum(self.yseries())
831 cum=histo[0][1]
832 line="\n%s <= %s < %s: %s (cum: %s/%s%s)"\
833 %(fmt,"%s",fmt,"%s","%s","%5.1f","%s")
834 line1="\n%s%s < %s: %s (cum: %s/%s%s)"\
835 %("%s","%s",fmt,"%s","%s","%5.1f","%s")
836 l1width=len(("%s <= "%fmt)%histo[1][0])
837 res.append(line1\
838 %(" "*l1width,ylab,histo[1][0],str(histo[0][1]).rjust(width),\
839 str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
840 )
841 for i in range(1,len(histo)-1):
842 cum+=histo[i][1]
843 res.append(line\
844 %(histo[i][0],ylab,histo[i+1][0],str(histo[i][1]).rjust(width),\
845 str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
846 )
847 cum+=histo[-1][1]
848 linen="\n%s <= %s %s : %s (cum: %s/%s%s)"\
849 %(fmt,"%s","%s","%s","%s","%5.1f","%s")
850 lnwidth=len(("<%s"%fmt)%histo[1][0])
851 res.append(linen\
852 %(histo[-1][0],ylab," "*lnwidth,str(histo[-1][1]).rjust(width),\
853 str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
854 )
855 return " ".join(res)
856
858 - def __init__(self, name="a_Tally", ylab="y",tlab="t"):
859 self.name = name
860 self.ylab = ylab
861 self.tlab = tlab
862 self.reset()
863 self.startTime = 0.0
864 self.histo = None
865 self.sum = 0.0
866 self._sum_of_squares = 0
867 self._integral = 0.0
868 self._integral2 = 0.0
869 allTallies.append(self)
870
871 - def setHistogram(self,name = '',low=0.0,high=100.0,nbins=10):
872 """Sets histogram parameters.
873 Must be called to prior to observations initiate data collection
874 for histogram.
875 """
876 if name=='':
877 hname=self.name
878 else:
879 hname=name
880 self.histo=Histogram(name=hname,low=low,high=high,nbins=nbins)
881
883 if t is None:
884 t = now()
885 self._integral += (t - self._last_timestamp) * self._last_observation
886 yy = self._last_observation* self._last_observation
887 self._integral2 += (t - self._last_timestamp) * yy
888 self._last_timestamp = t
889 self._last_observation = y
890 self._total += y
891 self._count += 1
892 self._sum += y
893 self._sum_of_squares += y * y
894 if self.histo:
895 self.histo.addIn(y)
896
897 - def reset(self, t=None):
898 if t is None:
899 t = now()
900 self.startTime = t
901 self._last_timestamp = t
902 self._last_observation = 0.0
903 self._count = 0
904 self._total = 0.0
905 self._integral = 0.0
906 self._integral2 = 0.0
907 self._sum = 0.0
908 self._sum_of_squares = 0.0
909
911 return self._count
912
914 return self._total
915
917 return 1.0 * self._total / self._count
918
920 if t is None:
921 t=now()
922 integ=self._integral+(t - self._last_timestamp) * self._last_observation
923 if (t > self.startTime):
924 return 1.0 * integ/(t - self.startTime)
925 else:
926 print 'SimPy: No elapsed time for timeAverage'
927 return None
928
930 return 1.0 * (self._sum_of_squares - (1.0 * (self._sum * self._sum)\
931 / self._count)) / (self._count)
932
934 """ the time-weighted Variance of the Tallied variable.
935
936 If t is used it is assumed to be the current time,
937 otherwise t = now()
938 """
939 if t is None:
940 t=now()
941 twAve = self.timeAverage(t)
942
943 last = self._last_observation
944 twinteg2=self._integral2+(t - self._last_timestamp) * last * last
945
946 if (t > self.startTime):
947 return 1.0 * twinteg2/(t - self.startTime) - twAve*twAve
948 else:
949 print 'SimPy: No elapsed time for timeVariance'
950 return None
951
952
953
955 return self._count
956
958 return len(l) == self._count
959
961 return self.histo
962
964 """Returns formatted frequency distribution table string from Tally.
965 Precondition: setHistogram must have been called.
966 fmt==format of bin range values
967 """
968 try:
969 histo=self.getHistogram()
970 except:
971 raise FatalSimerror("histogramTable: call setHistogram first"\
972 " for Tally %s"%self.name)
973 ylab=self.ylab
974 nrObs=self.count()
975 width=len(str(nrObs))
976 res=[]
977 res.append("\nHistogram for %s:"%histo.name)
978 res.append("\nNumber of observations: %s"%nrObs)
979 su=self.total()
980 cum=histo[0][1]
981 line="\n%s <= %s < %s: %s (cum: %s/%s%s)"\
982 %(fmt,"%s",fmt,"%s","%s","%5.1f","%s")
983 line1="\n%s%s < %s: %s (cum: %s/%s%s)"\
984 %("%s","%s",fmt,"%s","%s","%5.1f","%s")
985 l1width=len(("%s <= "%fmt)%histo[1][0])
986 res.append(line1\
987 %(" "*l1width,ylab,histo[1][0],str(histo[0][1]).rjust(width),\
988 str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
989 )
990 for i in range(1,len(histo)-1):
991 cum+=histo[i][1]
992 res.append(line\
993 %(histo[i][0],ylab,histo[i+1][0],str(histo[i][1]).rjust(width),\
994 str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
995 )
996 cum+=histo[-1][1]
997 linen="\n%s <= %s %s : %s (cum: %s/%s%s)"\
998 %(fmt,"%s","%s","%s","%s","%5.1f","%s")
999 lnwidth=len(("<%s"%fmt)%histo[1][0])
1000 res.append(linen\
1001 %(histo[-1][0],ylab," "*lnwidth,str(histo[-1][1]).rjust(width),\
1002 str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
1003 )
1004 return " ".join(res)
1005
1008 if not moni is None:
1009 self.monit=True
1010 else:
1011 self.monit=False
1012 self.moni=moni
1013 self.resource=res
1014
1016 pass
1017
1019 pass
1020
1022 self.remove(obj)
1023 if self.monit:
1024 self.moni.observe(len(self),t=now())
1025
1029
1031 self.append(obj)
1032 if self.monit:
1033 self.moni.observe(len(self),t=now())
1034
1037
1040
1042 a= self.pop(0)
1043 if self.monit:
1044 self.moni.observe(len(self),t=now())
1045 return a
1046
1048 """Queue is always ordered according to priority.
1049 Higher value of priority attribute == higher priority.
1050 """
1053
1055 """Handles request queue for Resource"""
1056 if len(self):
1057 ix=self.resource
1058 if self[-1]._priority[ix] >= obj._priority[ix]:
1059 self.append(obj)
1060 else:
1061 z=0
1062 while self[z]._priority[ix] >= obj._priority[ix]:
1063 z += 1
1064 self.insert(z,obj)
1065 else:
1066 self.append(obj)
1067 if self.monit:
1068 self.moni.observe(len(self),t=now())
1069
1071 """Handles getQ in Buffer"""
1072 if len(self):
1073 ix=self.resource
1074
1075 if self[-1]._getpriority[ix] >= obj._getpriority[ix]:
1076 self.append(obj)
1077 else:
1078 z=0
1079 while self[z]._getpriority[ix] >= obj._getpriority[ix]:
1080 z += 1
1081 self.insert(z,obj)
1082 else:
1083 self.append(obj)
1084 if self.monit:
1085 self.moni.observe(len(self),t=now())
1086
1088 """Handles putQ in Buffer"""
1089 if len(self):
1090 ix=self.resource
1091
1092 if self[-1]._putpriority[ix] >= obj._putpriority[ix]:
1093 self.append(obj)
1094 else:
1095 z=0
1096 while self[z]._putpriority[ix] >= obj._putpriority[ix]:
1097 z += 1
1098 self.insert(z,obj)
1099 else:
1100 self.append(obj)
1101 if self.monit:
1102 self.moni.observe(len(self),t=now())
1103
1105 """Models shared, limited capacity resources with queuing;
1106 FIFO is default queuing discipline.
1107 """
1108
1109 - def __init__(self,capacity=1,name="a_resource",unitName="units",
1110 qType=FIFO,preemptable=0,monitored=False,monitorType=Monitor):
1111 """
1112 monitorType={Monitor(default)|Tally}
1113 """
1114 self.name=name
1115 self.capacity=capacity
1116 self.unitName=unitName
1117 self.n=capacity
1118 self.monitored=monitored
1119
1120 if self.monitored:
1121 self.actMon=monitorType(name="Active Queue Monitor %s"%self.name,
1122 ylab="nr in queue",tlab="time")
1123 monact=self.actMon
1124 self.waitMon=monitorType(name="Wait Queue Monitor %s"%self.name,
1125 ylab="nr in queue",tlab="time")
1126 monwait=self.waitMon
1127 else:
1128 monwait=None
1129 monact=None
1130 self.waitQ=qType(self,monwait)
1131 self.preemptable=preemptable
1132 self.activeQ=qType(self,monact)
1133 self.priority_default=0
1134
1136 """Process request event for this resource"""
1137 obj=arg[1]
1138 if len(arg[0]) == 4:
1139 obj._priority[self]=arg[0][3]
1140 else:
1141 obj._priority[self]=self.priority_default
1142 if self.preemptable and self.n == 0:
1143
1144 preempt=obj._priority[self] > self.activeQ[-1]._priority[self]
1145
1146 if preempt:
1147 z=self.activeQ[-1]
1148
1149 z._preempted+=1
1150
1151
1152 if z._preempted==1:
1153 z._remainService = z._nextTime - _t
1154
1155 Process().cancel(z)
1156
1157 self.activeQ.remove(z)
1158
1159 self.waitQ.insert(0,z)
1160
1161 if self.monitored:
1162 self.waitMon.observe(len(self.waitQ),now())
1163
1164 z._nextTime=None
1165
1166 self.activeQ.enter(obj)
1167
1168 _e._post(obj,at=_t,prior=1)
1169 else:
1170 self.waitQ.enter(obj)
1171
1172 obj._nextTime=None
1173 else:
1174 if self.n == 0:
1175 self.waitQ.enter(obj)
1176
1177 obj._nextTime=None
1178 else:
1179 self.n -= 1
1180 self.activeQ.enter(obj)
1181 _e._post(obj,at=_t,prior=1)
1182
1184 """Process release request for this resource"""
1185 self.n += 1
1186 self.activeQ.remove(arg[1])
1187 if self.monitored:
1188 self.actMon.observe(len(self.activeQ),t=now())
1189
1190 if self.waitQ:
1191 obj=self.waitQ.leave()
1192 self.n -= 1
1193 self.activeQ.enter(obj)
1194
1195 if self.preemptable:
1196
1197 if obj._preempted:
1198
1199 obj._preempted-=1
1200
1201
1202 if obj._preempted==0:
1203 reactivate(obj,delay=obj._remainService,prior=1)
1204
1205 else:
1206 reactivate(obj,delay=0,prior=1)
1207
1208 else:
1209 reactivate(obj,delay=0,prior=1)
1210 _e._post(arg[1],at=_t,prior=1)
1211
1213 """Abstract class for buffers
1214 Blocks a process when a put would cause buffer overflow or a get would cause
1215 buffer underflow.
1216 Default queuing discipline for blocked processes is FIFO."""
1217
1218 priorityDefault=0
1219 - def __init__(self,name=None,capacity="unbounded",unitName="units",
1220 putQType=FIFO,getQType=FIFO,
1221 monitored=False,monitorType=Monitor,initialBuffered=None):
1222 if capacity=="unbounded": capacity=sys.maxint
1223 self.capacity=capacity
1224 self.name=name
1225 self.putQType=putQType
1226 self.getQType=getQType
1227 self.monitored=monitored
1228 self.initialBuffered=initialBuffered
1229 self.unitName=unitName
1230 if self.monitored:
1231
1232 self.putQMon=monitorType(name="Producer Queue Monitor %s"%self.name,
1233 ylab="nr in queue",tlab="time")
1234
1235 self.getQMon=monitorType(name="Consumer Queue Monitor %s"%self.name,
1236 ylab="nr in queue",tlab="time")
1237
1238 self.bufferMon=monitorType(name="Buffer Monitor %s"%self.name,
1239 ylab="nr in buffer",tlab="time")
1240 else:
1241 self.putQMon=None
1242 self.getQMon=None
1243 self.bufferMon=None
1244 self.putQ=self.putQType(res=self,moni=self.putQMon)
1245 self.getQ=self.getQType(res=self,moni=self.getQMon)
1246 if self.monitored:
1247 self.putQMon.observe(y=len(self.putQ),t=now())
1248 self.getQMon.observe(y=len(self.getQ),t=now())
1249 self._putpriority={}
1250 self._getpriority={}
1251
1252 def _put(self):
1253 pass
1254 def _get(self):
1255 pass
1256
1258 """Models buffers for processes putting/getting un-distinguishable items.
1259 """
1262
1265
1266 theBuffer=property(gettheBuffer)
1267
1269 Buffer.__init__(self,**pars)
1270 if self.name is None:
1271 self.name="a_level"
1272
1273 if (type(self.capacity)!=type(1.0) and\
1274 type(self.capacity)!=type(1)) or\
1275 self.capacity<0:
1276 raise FatalSimerror\
1277 ("Level: capacity parameter not a positive number: %s"\
1278 %self.initialBuffered)
1279
1280 if type(self.initialBuffered)==type(1.0) or\
1281 type(self.initialBuffered)==type(1):
1282 if self.initialBuffered>self.capacity:
1283 raise FatalSimerror("initialBuffered exceeds capacity")
1284 if self.initialBuffered>=0:
1285 self.nrBuffered=self.initialBuffered
1286
1287 else:
1288 raise FatalSimerror\
1289 ("initialBuffered param of Level negative: %s"\
1290 %self.initialBuffered)
1291 elif self.initialBuffered is None:
1292 self.initialBuffered=0
1293 self.nrBuffered=0
1294 else:
1295 raise FatalSimerror\
1296 ("Level: wrong type of initialBuffered (parameter=%s)"\
1297 %self.initialBuffered)
1298 if self.monitored:
1299 self.bufferMon.observe(y=self.amount,t=now())
1300 amount=property(getamount)
1301
1302 - def _put(self,arg):
1303 """Handles put requests for Level instances"""
1304 obj=arg[1]
1305 if len(arg[0]) == 5:
1306 obj._putpriority[self]=arg[0][4]
1307 whatToPut=arg[0][3]
1308 elif len(arg[0]) == 4:
1309 obj._putpriority[self]=Buffer.priorityDefault
1310 whatToPut=arg[0][3]
1311 else:
1312 obj._putpriority[self]=Buffer.priorityDefault
1313 whatToPut=1
1314 if type(whatToPut)!=type(1) and type(whatToPut)!=type(1.0):
1315 raise FatalSimerror("Level: put parameter not a number")
1316 if not whatToPut>=0.0:
1317 raise FatalSimerror("Level: put parameter not positive number")
1318 whatToPutNr=whatToPut
1319 if whatToPutNr+self.amount>self.capacity:
1320 obj._nextTime=None
1321 obj._whatToPut=whatToPutNr
1322 self.putQ.enterPut(obj)
1323 else:
1324 self.nrBuffered+=whatToPutNr
1325 if self.monitored:
1326 self.bufferMon.observe(y=self.amount,t=now())
1327
1328
1329
1330 while len(self.getQ) and self.amount>0:
1331 proc=self.getQ[0]
1332 if proc._nrToGet<=self.amount:
1333 proc.got=proc._nrToGet
1334 self.nrBuffered-=proc.got
1335 if self.monitored:
1336 self.bufferMon.observe(y=self.amount,t=now())
1337 self.getQ.takeout(proc)
1338 _e._post(proc,at=_t)
1339 else:
1340 break
1341 _e._post(obj,at=_t,prior=1)
1342
1343 - def _get(self,arg):
1344 """Handles get requests for Level instances"""
1345 obj=arg[1]
1346 obj.got=None
1347 if len(arg[0]) == 5:
1348 obj._getpriority[self]=arg[0][4]
1349 nrToGet=arg[0][3]
1350 elif len(arg[0]) == 4:
1351 obj._getpriority[self]=Buffer.priorityDefault
1352 nrToGet=arg[0][3]
1353 else:
1354 obj._getpriority[self]=Buffer.priorityDefault
1355 nrToGet=1
1356 if type(nrToGet)!=type(1.0) and type(nrToGet)!=type(1):
1357 raise FatalSimerror\
1358 ("Level: get parameter not a number: %s"%nrToGet)
1359 if nrToGet<0:
1360 raise FatalSimerror\
1361 ("Level: get parameter not positive number: %s"%nrToGet)
1362 if self.amount < nrToGet:
1363 obj._nrToGet=nrToGet
1364 self.getQ.enterGet(obj)
1365
1366 obj._nextTime=None
1367 else:
1368 obj.got=nrToGet
1369 self.nrBuffered-=nrToGet
1370 if self.monitored:
1371 self.bufferMon.observe(y=self.amount,t=now())
1372 _e._post(obj,at=_t,prior=1)
1373
1374
1375
1376 while len(self.putQ):
1377 proc=self.putQ[0]
1378 if proc._whatToPut+self.amount<=self.capacity:
1379 self.nrBuffered+=proc._whatToPut
1380 if self.monitored:
1381 self.bufferMon.observe(y=self.amount,t=now())
1382 self.putQ.takeout(proc)
1383 _e._post(proc,at=_t)
1384 else:
1385 break
1386
1388 """Models buffers for processes coupled by putting/getting distinguishable
1389 items.
1390 Blocks a process when a put would cause buffer overflow or a get would cause
1391 buffer underflow.
1392 Default queuing discipline for blocked processes is priority FIFO.
1393 """
1396 nrBuffered=property(getnrBuffered)
1397
1400 buffered=property(getbuffered)
1401
1403 Buffer.__init__(self,**pars)
1404 self.theBuffer=[]
1405 if self.name is None:
1406 self.name="a_store"
1407 if type(self.capacity)!=type(1) or self.capacity<=0:
1408 raise FatalSimerror\
1409 ("Store: capacity parameter not a positive integer > 0: %s"\
1410 %self.initialBuffered)
1411 if type(self.initialBuffered)==type([]):
1412 if len(self.initialBuffered)>self.capacity:
1413 raise FatalSimerror("initialBuffered exceeds capacity")
1414 else:
1415 self.theBuffer[:]=self.initialBuffered
1416 elif self.initialBuffered is None:
1417 self.theBuffer=[]
1418 else:
1419 raise FatalSimerror\
1420 ("Store: initialBuffered not a list")
1421 if self.monitored:
1422 self.bufferMon.observe(y=self.nrBuffered,t=now())
1423 self._sort=None
1424
1425
1426
1428 """Adds buffer sorting to this instance of Store. It maintains
1429 theBuffer sorted by the sortAttr attribute of the objects in the
1430 buffer.
1431 The user-provided 'sortFunc' must look like this:
1432
1433 def mySort(self,par):
1434 tmplist=[(x.sortAttr,x) for x in par]
1435 tmplist.sort()
1436 return [x for (key,x) in tmplist]
1437
1438 """
1439
1440 self._sort=new.instancemethod(sortFunc,self,self.__class__)
1441 self.theBuffer=self._sort(self.theBuffer)
1442
1443 - def _put(self,arg):
1444 """Handles put requests for Store instances"""
1445 obj=arg[1]
1446 if len(arg[0]) == 5:
1447 obj._putpriority[self]=arg[0][4]
1448 whatToPut=arg[0][3]
1449 elif len(arg[0]) == 4:
1450 obj._putpriority[self]=Buffer.priorityDefault
1451 whatToPut=arg[0][3]
1452 else:
1453 raise FatalSimerror("Item to put missing in yield put stmt")
1454 if type(whatToPut)!=type([]):
1455 raise FatalSimerror("put parameter is not a list")
1456 whatToPutNr=len(whatToPut)
1457 if whatToPutNr+self.nrBuffered>self.capacity:
1458 obj._nextTime=None
1459 obj._whatToPut=whatToPut
1460 self.putQ.enterPut(obj)
1461 else:
1462 self.theBuffer.extend(whatToPut)
1463 if not(self._sort is None):
1464 self.theBuffer=self._sort(self.theBuffer)
1465 if self.monitored:
1466 self.bufferMon.observe(y=self.nrBuffered,t=now())
1467
1468
1469
1470
1471 while self.nrBuffered>0 and len(self.getQ):
1472 proc=self.getQ[0]
1473 if inspect.isfunction(proc._nrToGet):
1474 movCand=proc._nrToGet(self.theBuffer)
1475 if movCand:
1476 proc.got=movCand[:]
1477 for i in movCand:
1478 self.theBuffer.remove(i)
1479 self.getQ.takeout(proc)
1480 if self.monitored:
1481 self.bufferMon.observe(y=self.nrBuffered,t=now())
1482 _e._post(what=proc,at=_t)
1483 else:
1484 break
1485 else:
1486 if proc._nrToGet<=self.nrBuffered:
1487 nrToGet=proc._nrToGet
1488 proc.got=[]
1489 proc.got[:]=self.theBuffer[0:nrToGet]
1490 self.theBuffer[:]=self.theBuffer[nrToGet:]
1491 if self.monitored:
1492 self.bufferMon.observe(y=self.nrBuffered,t=now())
1493
1494 self.getQ.takeout(proc)
1495 _e._post(what=proc,at=_t)
1496 else:
1497 break
1498
1499 _e._post(what=obj,at=_t,prior=1)
1500
1501 - def _get(self,arg):
1502 """Handles get requests"""
1503 filtfunc=None
1504 obj=arg[1]
1505 obj.got=[]
1506 if len(arg[0]) == 5:
1507 obj._getpriority[self]=arg[0][4]
1508 if inspect.isfunction(arg[0][3]):
1509 filtfunc=arg[0][3]
1510 else:
1511 nrToGet=arg[0][3]
1512 elif len(arg[0]) == 4:
1513 obj._getpriority[self]=Buffer.priorityDefault
1514 if inspect.isfunction(arg[0][3]):
1515 filtfunc=arg[0][3]
1516 else:
1517 nrToGet=arg[0][3]
1518 else:
1519 obj._getpriority[self]=Buffer.priorityDefault
1520 nrToGet=1
1521 if not filtfunc:
1522 if nrToGet<0:
1523 raise FatalSimerror\
1524 ("Store: get parameter not positive number: %s"%nrToGet)
1525 if self.nrBuffered < nrToGet:
1526 obj._nrToGet=nrToGet
1527 self.getQ.enterGet(obj)
1528
1529 obj._nextTime=None
1530 else:
1531 for i in range(nrToGet):
1532 obj.got.append(self.theBuffer.pop(0))
1533
1534 if self.monitored:
1535 self.bufferMon.observe(y=self.nrBuffered,t=now())
1536 _e._post(obj,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(proc,at=_t)
1551 else:
1552 break
1553 else:
1554 movCand=filtfunc(self.theBuffer)
1555 if movCand:
1556 _e._post(obj,at=_t,prior=1)
1557 obj.got=movCand[:]
1558 for item in movCand:
1559 self.theBuffer.remove(item)
1560 if self.monitored:
1561 self.bufferMon.observe(y=self.nrBuffered,t=now())
1562
1563
1564
1565 while len(self.putQ):
1566 proc=self.putQ[0]
1567 if len(proc._whatToPut)+self.nrBuffered<=self.capacity:
1568 for i in proc._whatToPut:
1569 self.theBuffer.append(i)
1570 if not(self._sort is None):
1571 self.theBuffer=self._sort(self.theBuffer)
1572 if self.monitored:
1573 self.bufferMon.observe(y=self.nrBuffered,t=now())
1574 self.putQ.takeout(proc)
1575 _e._post(proc,at=_t)
1576 else:
1577 break
1578 else:
1579 obj._nrToGet=filtfunc
1580 self.getQ.enterGet(obj)
1581
1582 obj._nextTime=None
1583
1585 """Supports one-shot signalling between processes. All processes waiting for an event to occur
1586 get activated when its occurrence is signalled. From the processes queuing for an event, only
1587 the first gets activated.
1588 """
1590 self.name=name
1591 self.waits=[]
1592 self.queues=[]
1593 self.occurred=False
1594 self.signalparam=None
1595
1596 - def signal(self,param=None):
1597 """Produces a signal to self;
1598 Fires this event (makes it occur).
1599 Reactivates ALL processes waiting for this event. (Cleanup waits lists
1600 of other events if wait was for an event-group (OR).)
1601 Reactivates the first process for which event(s) it is queuing for
1602 have fired. (Cleanup queues of other events if wait was for an event-group (OR).)
1603 """
1604 self.signalparam=param
1605 if not self.waits and not self.queues:
1606 self.occurred=True
1607 else:
1608
1609 for p in self.waits:
1610 p[0].eventsFired.append(self)
1611 reactivate(p[0],prior=True)
1612
1613 for ev in p[1]:
1614 if ev!=self:
1615 if ev.occurred:
1616 p[0].eventsFired.append(ev)
1617 for iev in ev.waits:
1618 if iev[0]==p[0]:
1619 ev.waits.remove(iev)
1620 break
1621 self.waits=[]
1622 if self.queues:
1623 proc=self.queues.pop(0)[0]
1624 proc.eventsFired.append(self)
1625 reactivate(proc)
1626
1628 """Consumes a signal if it has occurred, otherwise process 'proc'
1629 waits for this event.
1630 """
1631 proc=par[0][1]
1632 proc.eventsFired=[]
1633 if not self.occurred:
1634 self.waits.append([proc,[self]])
1635 proc._nextTime=None
1636 else:
1637 proc.eventsFired.append(self)
1638 self.occurred=False
1639 _e._post(proc,at=_t,prior=1)
1640
1642 """Handles waiting for an OR of events in a tuple/list.
1643 """
1644 proc=par[0][1]
1645 evlist=par[0][2]
1646 proc.eventsFired=[]
1647 anyoccur=False
1648 for ev in evlist:
1649 if ev.occurred:
1650 anyoccur=True
1651 proc.eventsFired.append(ev)
1652 ev.occurred=False
1653 if anyoccur:
1654 _e._post(proc,at=_t,prior=1)
1655
1656 else:
1657 proc.eventsFired=[]
1658 proc._nextTime=None
1659 for ev in evlist:
1660 ev.waits.append([proc,evlist])
1661
1663 """Consumes a signal if it has occurred, otherwise process 'proc'
1664 queues for this event.
1665 """
1666 proc=par[0][1]
1667 proc.eventsFired=[]
1668 if not self.occurred:
1669 self.queues.append([proc,[self]])
1670 proc._nextTime=None
1671 else:
1672 proc.eventsFired.append(self)
1673 self.occurred=False
1674 _e._post(proc,at=_t,prior=1)
1675
1677 """Handles queueing for an OR of events in a tuple/list.
1678 """
1679 proc=par[0][1]
1680 evlist=par[0][2]
1681 proc.eventsFired=[]
1682 anyoccur=False
1683 for ev in evlist:
1684 if ev.occurred:
1685 anyoccur=True
1686 proc.eventsFired.append(ev)
1687 ev.occurred=False
1688 if anyoccur:
1689 _e._post(proc,at=_t,prior=1)
1690
1691 else:
1692 proc.eventsFired=[]
1693 proc._nextTime=None
1694 for ev in evlist:
1695 ev.queues.append([proc,evlist])
1696
1697
1699 """
1700 Gets called by simulate after every event, as long as there are processes
1701 waiting in condQ for a condition to be satisfied.
1702 Tests the conditions for all waiting processes. Where condition satisfied,
1703 reactivates that process immediately and removes it from queue.
1704 """
1705 global condQ
1706 rList=[]
1707 for el in condQ:
1708 if el.cond():
1709 rList.append(el)
1710 reactivate(el)
1711 for i in rList:
1712 condQ.remove(i)
1713
1714 if not condQ:
1715 _stopWUStepping()
1716
1718 global condQ
1719 """
1720 Puts a process 'proc' waiting for a condition into a waiting queue.
1721 'cond' is a predicate function which returns True if the condition is
1722 satisfied.
1723 """
1724 if not cond():
1725 condQ.append(proc)
1726 proc.cond=cond
1727 _startWUStepping()
1728
1729 proc._nextTime=None
1730 else:
1731
1732 _e._post(proc,at=_t,prior=1)
1733
1734
1735
1736
1738 """Schedules Processes/semi-coroutines until time 'till'.
1739 Deprecated since version 0.5.
1740 """
1741 simulate(until=till)
1742
1745
1747 """Handles 'yield request,self,res' and 'yield (request,self,res),(<code>,self,par)'.
1748 <code> can be 'hold' or 'waitevent'.
1749 """
1750 if type(a[0][0])==tuple:
1751
1752
1753 b=a[0][0]
1754
1755
1756
1757 b[2]._request(arg=(b,a[1]))
1758
1759
1760 class _Holder(Process):
1761 """Provides timeout process"""
1762 def trigger(self,delay):
1763 yield hold,self,delay
1764 if not proc in b[2].activeQ:
1765 reactivate(proc)
1766
1767 class _EventWait(Process):
1768 """Provides event waiting process"""
1769 def trigger(self,event):
1770 yield waitevent,self,event
1771 if not proc in b[2].activeQ:
1772 a[1].eventsFired=self.eventsFired
1773 reactivate(proc)
1774
1775
1776 proc=a[0][0][1]
1777 actCode=a[0][1][0]
1778 if actCode==hold:
1779 proc._holder=_Holder(name="RENEGE-hold for %s"%proc.name)
1780
1781 activate(proc._holder,proc._holder.trigger(a[0][1][2]))
1782 elif actCode==waituntil:
1783 raise FatalSimerror("Illegal code for reneging: waituntil")
1784 elif actCode==waitevent:
1785 proc._holder=_EventWait(name="RENEGE-waitevent for %s"%proc.name)
1786
1787 activate(proc._holder,proc._holder.trigger(a[0][1][2]))
1788 elif actCode==queueevent:
1789 raise FatalSimerror("Illegal code for reneging: queueevent")
1790 else:
1791 raise FatalSimerror("Illegal code for reneging %s"%actCode)
1792 else:
1793
1794 a[0][2]._request(a)
1795
1798
1801
1803
1804 evtpar=a[0][2]
1805 if isinstance(evtpar,SimEvent):
1806 a[0][2]._wait(a)
1807
1808 else:
1809
1810 evtpar[0]._waitOR(a)
1811
1813
1814 evtpar=a[0][2]
1815 if isinstance(evtpar,SimEvent):
1816 a[0][2]._queue(a)
1817
1818 else:
1819
1820 evtpar[0]._queueOR(a)
1821
1824
1826 """Handles 'yield get,self,buffer,what,priority' and
1827 'yield (get,self,buffer,what,priority),(<code>,self,par)'.
1828 <code> can be 'hold' or 'waitevent'.
1829 """
1830 if type(a[0][0])==tuple:
1831
1832
1833 b=a[0][0]
1834
1835
1836
1837 b[2]._get(arg=(b,a[1]))
1838
1839
1840 class _Holder(Process):
1841 """Provides timeout process"""
1842 def trigger(self,delay):
1843 yield hold,self,delay
1844
1845 if proc in b[2].getQ:
1846 reactivate(proc)
1847
1848 class _EventWait(Process):
1849 """Provides event waiting process"""
1850 def trigger(self,event):
1851 yield waitevent,self,event
1852 if proc in b[2].getQ:
1853 a[1].eventsFired=self.eventsFired
1854 reactivate(proc)
1855
1856
1857 proc=a[0][0][1]
1858 actCode=a[0][1][0]
1859 if actCode==hold:
1860 proc._holder=_Holder("RENEGE-hold for %s"%proc.name)
1861
1862 activate(proc._holder,proc._holder.trigger(a[0][1][2]))
1863 elif actCode==waituntil:
1864 raise FatalSimerror("Illegal code for reneging: waituntil")
1865 elif actCode==waitevent:
1866 proc._holder=_EventWait(proc.name)
1867
1868 activate(proc._holder,proc._holder.trigger(a[0][1][2]))
1869 elif actCode==queueevent:
1870 raise FatalSimerror("Illegal code for reneging: queueevent")
1871 else:
1872 raise FatalSimerror("Illegal code for reneging %s"%actCode)
1873 else:
1874
1875 a[0][2]._get(a)
1876
1877
1879 """Handles 'yield put' (simple and compound hold/waitevent)
1880 """
1881 if type(a[0][0])==tuple:
1882
1883
1884 b=a[0][0]
1885
1886
1887
1888 b[2]._put(arg=(b,a[1]))
1889
1890
1891 class _Holder(Process):
1892 """Provides timeout process"""
1893 def trigger(self,delay):
1894 yield hold,self,delay
1895
1896 if proc in b[2].putQ:
1897 reactivate(proc)
1898
1899 class _EventWait(Process):
1900 """Provides event waiting process"""
1901 def trigger(self,event):
1902 yield waitevent,self,event
1903 if proc in b[2].putQ:
1904 a[1].eventsFired=self.eventsFired
1905 reactivate(proc)
1906
1907
1908 proc=a[0][0][1]
1909 actCode=a[0][1][0]
1910 if actCode==hold:
1911 proc._holder=_Holder("RENEGE-hold for %s"%proc.name)
1912
1913 activate(proc._holder,proc._holder.trigger(a[0][1][2]))
1914 elif actCode==waituntil:
1915 raise FatalSimerror("Illegal code for reneging: waituntil")
1916 elif actCode==waitevent:
1917 proc._holder=_EventWait("RENEGE-waitevent for %s"%proc.name)
1918
1919 activate(proc._holder,proc._holder.trigger(a[0][1][2]))
1920 elif actCode==queueevent:
1921 raise FatalSimerror("Illegal code for reneging: queueevent")
1922 else:
1923 raise FatalSimerror("Illegal code for reneging %s"%actCode)
1924 else:
1925
1926 a[0][2]._put(a)
1927
1928 -def simulate(until=0,real_time=False,rel_speed=1):
1929 """Schedules Processes/semi-coroutines until time 'until'"""
1930
1931 """Gets called once. Afterwards, co-routines (generators) return by
1932 'yield' with a cargo:
1933 yield hold, self, <delay>: schedules the "self" process for activation
1934 after <delay> time units.If <,delay> missing,
1935 same as "yield hold,self,0"
1936
1937 yield passivate,self : makes the "self" process wait to be re-activated
1938
1939 yield request,self,<Resource>[,<priority>]: request 1 unit from <Resource>
1940 with <priority> pos integer (default=0)
1941
1942 yield release,self,<Resource> : release 1 unit to <Resource>
1943
1944 yield waitevent,self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]:
1945 wait for one or more of several events
1946
1947
1948 yield queueevent,self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]:
1949 queue for one or more of several events
1950
1951 yield waituntil,self,cond : wait for arbitrary condition
1952
1953 yield get,self,<buffer>[,<WhatToGet>[,<priority>]]
1954 get <WhatToGet> items from buffer (default=1);
1955 <WhatToGet> can be a pos integer or a filter function
1956 (Store only)
1957
1958 yield put,self,<buffer>[,<WhatToPut>[,priority]]
1959 put <WhatToPut> items into buffer (default=1);
1960 <WhatToPut> can be a pos integer (Level) or a list of objects
1961 (Store)
1962
1963 EXTENSIONS:
1964 Request with timeout reneging:
1965 yield (request,self,<Resource>),(hold,self,<patience>) :
1966 requests 1 unit from <Resource>. If unit not acquired in time period
1967 <patience>, self leaves waitQ (reneges).
1968
1969 Request with event-based reneging:
1970 yield (request,self,<Resource>),(waitevent,self,<eventlist>):
1971 requests 1 unit from <Resource>. If one of the events in <eventlist> occurs before unit
1972 acquired, self leaves waitQ (reneges).
1973
1974 Get with timeout reneging (for Store and Level):
1975 yield (get,self,<buffer>,nrToGet etc.),(hold,self,<patience>)
1976 requests <nrToGet> items/units from <buffer>. If not acquired <nrToGet> in time period
1977 <patience>, self leaves <buffer>.getQ (reneges).
1978
1979 Get with event-based reneging (for Store and Level):
1980 yield (get,self,<buffer>,nrToGet etc.),(waitevent,self,<eventlist>)
1981 requests <nrToGet> items/units from <buffer>. If not acquired <nrToGet> before one of
1982 the events in <eventlist> occurs, self leaves <buffer>.getQ (reneges).
1983
1984
1985
1986 Event notices get posted in event-list by scheduler after "yield" or by
1987 "activate"/"reactivate" functions.
1988
1989 if real_time==True, the simulation time and real (clock) time get
1990 synchronized as much as possible. rel_speed is the ratio simulation time
1991 over clock time(seconds). Example: rel_speed==100: 100 simulation time units take
1992 1 second clock time.
1993
1994 """
1995 global _endtime,_e,_stop,_t,_wustep
1996 _stop=False
1997
1998 if _e is None:
1999 raise FatalSimerror("Simulation not initialized")
2000 _e.real_time=real_time
2001 _e.rel_speed=rel_speed
2002 _e.rtlast = wallclock()
2003 _e.stlast = 0
2004 if _e._isEmpty():
2005 message="SimPy: No activities scheduled"
2006 return message
2007
2008 _endtime=until
2009 message="SimPy: Normal exit"
2010 dispatch={hold:holdfunc,request:requestfunc,release:releasefunc,
2011 passivate:passivatefunc,waitevent:waitevfunc,queueevent:queueevfunc,
2012 waituntil:waituntilfunc,get:getfunc,put:putfunc}
2013 commandcodes=dispatch.keys()
2014 commandwords={hold:"hold",request:"request",release:"release",passivate:"passivate",
2015 waitevent:"waitevent",queueevent:"queueevent",waituntil:"waituntil",
2016 get:"get",put:"put"}
2017 nextev=_e._nextev
2018 while not _stop and _t<=_endtime:
2019 try:
2020 a=nextev()
2021 if not a[0] is None:
2022
2023 if type(a[0][0])==tuple:
2024
2025 command=a[0][0][0]
2026 else:
2027 command = a[0][0]
2028 if __debug__:
2029 if not command in commandcodes:
2030 raise FatalSimerror("Illegal command: yield %s"%command)
2031 dispatch[command](a)
2032 except FatalSimerror,error:
2033 print "SimPy: "+error.value
2034 sys.exit(1)
2035 except Simerror,error:
2036 message="SimPy: "+error.value
2037 _stop = True
2038 if _wustep:
2039 _test()
2040 _stopWUStepping()
2041 _e=None
2042 return message
2043
2044
2045 if __name__ == "__main__":
2046 print "SimPy.SimulationRT %s" %__version__
2047
2049 class Aa(Process):
2050 sequIn=[]
2051 sequOut=[]
2052 def __init__(self,holdtime,name):
2053 Process.__init__(self,name)
2054 self.holdtime=holdtime
2055
2056 def life(self,priority):
2057 for i in range(1):
2058 Aa.sequIn.append(self.name)
2059 print now(),rrr.name,"waitQ:",len(rrr.waitQ),"activeQ:",\
2060 len(rrr.activeQ)
2061 print "waitQ: ",[(k.name,k._priority[rrr]) for k in rrr.waitQ]
2062 print "activeQ: ",[(k.name,k._priority[rrr]) \
2063 for k in rrr.activeQ]
2064 assert rrr.n+len(rrr.activeQ)==rrr.capacity, \
2065 "Inconsistent resource unit numbers"
2066 print now(),self.name,"requests 1 ", rrr.unitName
2067 yield request,self,rrr,priority
2068 print now(),self.name,"has 1 ",rrr.unitName
2069 print now(),rrr.name,"waitQ:",len(rrr.waitQ),"activeQ:",\
2070 len(rrr.activeQ)
2071 print now(),rrr.name,"waitQ:",len(rrr.waitQ),"activeQ:",\
2072 len(rrr.activeQ)
2073 assert rrr.n+len(rrr.activeQ)==rrr.capacity, \
2074 "Inconsistent resource unit numbers"
2075 yield hold,self,self.holdtime
2076 print now(),self.name,"gives up 1",rrr.unitName
2077 yield release,self,rrr
2078 Aa.sequOut.append(self.name)
2079 print now(),self.name,"has released 1 ",rrr.unitName
2080 print "waitQ: ",[(k.name,k._priority[rrr]) for k in rrr.waitQ]
2081 print now(),rrr.name,"waitQ:",len(rrr.waitQ),"activeQ:",\
2082 len(rrr.activeQ)
2083 assert rrr.n+len(rrr.activeQ)==rrr.capacity, \
2084 "Inconsistent resource unit numbers"
2085
2086 class Observer(Process):
2087 def __init__(self):
2088 Process.__init__(self)
2089
2090 def observe(self,step,processes,res):
2091 while now()<11:
2092 for i in processes:
2093 print " %s %s: act:%s, pass:%s, term: %s,interr:%s, qu:%s"\
2094 %(now(),i.name,i.active(),i.passive(),i.terminated()\
2095 ,i.interrupted(),i.queuing(res))
2096 print
2097 yield hold,self,step
2098
2099 print"\n+++test_demo output"
2100 print "****First case == priority queue, resource service not preemptable"
2101 initialize()
2102 rrr=Resource(5,name="Parking",unitName="space(s)", qType=PriorityQ,
2103 preemptable=0)
2104 procs=[]
2105 for i in range(10):
2106 z=Aa(holdtime=i,name="Car "+str(i))
2107 procs.append(z)
2108 activate(z,z.life(priority=i))
2109 o=Observer()
2110 activate(o,o.observe(1,procs,rrr))
2111 a=simulate(until=10000,real_time=True,rel_speed=1)
2112 print a
2113 print "Input sequence: ",Aa.sequIn
2114 print "Output sequence: ",Aa.sequOut
2115
2116 print "\n****Second case == priority queue, resource service preemptable"
2117 initialize()
2118 rrr=Resource(5,name="Parking",unitName="space(s)", qType=PriorityQ,
2119 preemptable=1)
2120 procs=[]
2121 for i in range(10):
2122 z=Aa(holdtime=i,name="Car "+str(i))
2123 procs.append(z)
2124 activate(z,z.life(priority=i))
2125 o=Observer()
2126 activate(o,o.observe(1,procs,rrr))
2127 Aa.sequIn=[]
2128 Aa.sequOut=[]
2129 a=simulate(until=10000)
2130 print a
2131 print "Input sequence: ",Aa.sequIn
2132 print "Output sequence: ",Aa.sequOut
2133
2135 class Bus(Process):
2136 def __init__(self,name):
2137 Process.__init__(self,name)
2138
2139 def operate(self,repairduration=0):
2140 print now(),rtnow(),">> %s starts" %(self.name)
2141 tripleft = 1000
2142 while tripleft > 0:
2143 yield hold,self,tripleft
2144 if self.interrupted():
2145 print "interrupted by %s" %self.interruptCause.name
2146 print "%s(%s): %s breaks down " %(now(),rtnow(),self.name)
2147 tripleft=self.interruptLeft
2148 self.interruptReset()
2149 print "tripleft ",tripleft
2150 reactivate(br,delay=repairduration)
2151 yield hold,self,repairduration
2152 print now(),rtnow()," repaired"
2153 else:
2154 break
2155 print now(),"<< %s done" %(self.name)
2156
2157 class Breakdown(Process):
2158 def __init__(self,myBus):
2159 Process.__init__(self,name="Breakdown "+myBus.name)
2160 self.bus=myBus
2161
2162 def breakBus(self,interval):
2163
2164 while True:
2165 yield hold,self,interval
2166 if self.bus.terminated(): break
2167 self.interrupt(self.bus)
2168
2169 print"\n\n+++test_interrupt"
2170 initialize()
2171 b=Bus("Bus 1")
2172 activate(b,b.operate(repairduration=20))
2173 br=Breakdown(b)
2174 activate(br,br.breakBus(200))
2175 print simulate(until=4000,real_time=True,rel_speed=200)
2176
2178 class Waiter(Process):
2179 def waiting(self,theSignal):
2180 while True:
2181 yield waitevent,self,theSignal
2182 print "%s: process '%s' continued after waiting for %s"%(now(),self.name,theSignal.name)
2183 yield queueevent,self,theSignal
2184 print "%s: process '%s' continued after queueing for %s"%(now(),self.name,theSignal.name)
2185
2186 class ORWaiter(Process):
2187 def waiting(self,signals):
2188 while True:
2189 yield waitevent,self,signals
2190 print now(),"one of %s signals occurred"%[x.name for x in signals]
2191 print "\t%s (fired/param)"%[(x.name,x.signalparam) for x in self.eventsFired]
2192 yield hold,self,1
2193
2194 class Caller(Process):
2195 def calling(self):
2196 while True:
2197 signal1.signal("wake up!")
2198 print "%s: signal 1 has occurred"%now()
2199 yield hold,self,10
2200 signal2.signal("and again")
2201 signal2.signal("sig 2 again")
2202 print "%s: signal1, signal2 have occurred"%now()
2203 yield hold,self,10
2204 print"\n\n+++testSimEvents output"
2205 initialize()
2206 signal1=SimEvent("signal 1")
2207 signal2=SimEvent("signal 2")
2208 signal1.signal("startup1")
2209 signal2.signal("startup2")
2210 w1=Waiter("waiting for signal 1")
2211 activate(w1,w1.waiting(signal1))
2212 w2=Waiter("waiting for signal 2")
2213 activate(w2,w2.waiting(signal2))
2214 w3=Waiter("also waiting for signal 2")
2215 activate(w3,w3.waiting(signal2))
2216 w4=ORWaiter("waiting for either signal 1 or signal 2")
2217 activate(w4,w4.waiting([signal1,signal2]),prior=True)
2218 c=Caller("Caller")
2219 activate(c,c.calling())
2220 print simulate(until=100)
2221
2223 """
2224 Demo of waitUntil capability.
2225
2226 Scenario:
2227 Three workers require sets of tools to do their jobs. Tools are shared, scarce
2228 resources for which they compete.
2229 """
2230
2231
2232 class Worker(Process):
2233 def __init__(self,name,heNeeds=[]):
2234 Process.__init__(self,name)
2235 self.heNeeds=heNeeds
2236 def work(self):
2237
2238 def workerNeeds():
2239 for item in self.heNeeds:
2240 if item.n==0:
2241 return False
2242 return True
2243
2244 while now()<8*60:
2245 yield waituntil,self,workerNeeds
2246 for item in self.heNeeds:
2247 yield request,self,item
2248 print "%s %s has %s and starts job" %(now(),self.name,
2249 [x.name for x in self.heNeeds])
2250 yield hold,self,random.uniform(10,30)
2251 for item in self.heNeeds:
2252 yield release,self,item
2253 yield hold,self,2
2254
2255 print "\n+++\nwaituntil demo output"
2256 initialize()
2257 brush=Resource(capacity=1,name="brush")
2258 ladder=Resource(capacity=2,name="ladder")
2259 hammer=Resource(capacity=1,name="hammer")
2260 saw=Resource(capacity=1,name="saw")
2261 painter=Worker("painter",[brush,ladder])
2262 activate(painter,painter.work())
2263 roofer=Worker("roofer",[hammer,ladder,ladder])
2264 activate(roofer,roofer.work())
2265 treeguy=Worker("treeguy",[saw,ladder])
2266 activate(treeguy,treeguy.work())
2267 for who in (painter,roofer,treeguy):
2268 print "%s needs %s for his job" %(who.name,[x.name for x in who.heNeeds])
2269 print
2270 print simulate(until=9*60)
2271 test_demo()
2272
2273 test_interrupt()
2274 testSimEvents()
2275 testwaituntil()
2276