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