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