`
`Exhibit ZTE-CC-SIMULATION
`
`Matloff, Norm,
`Introduction to Discrete-Event Simulation and
`the SimPy Language
`
`
`
`
`
`
`
`
`
`
`
`
`
`
`
`
`
`Case 2:15-cv-00225-JRG-RSP Document 60-5 Filed 09/28/15 Page 2 of 34 PageID #: 1047
`
`Introduction to Discrete-Event Simulation and the SimPy Language
`
`Norm Matloff
`
`February 13, 2008
`c 2006-2008, N.S. Matloff
`
`Contents
`
`1 What Is Discrete-Event Simulation (DES)?
`
`2 World Views in DES Programming
`2.1 The Activity-Oriented Paradigm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
`
`2.2 The Event-Oriented Paradigm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
`
`2.3 The Process-Oriented Paradigm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
`
`3
`
`Introduction to the SimPy Simulation Language
`3.1 SimPy Overview .
`. .
`. .
`.
`. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
`
`3.2
`
`Introduction to SimPy Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
`
`3
`
`3
`3
`
`4
`
`6
`
`7
`8
`
`9
`
`3.2.1 MachRep1.py: Our First SimPy Program . . . . . . . . . . . . . . . . . . . . . . . 10
`
`3.2.2 MachRep2.py: Introducing the Resource Class . . . . . . . . . . . . . . . . . . . . 14
`
`3.2.3 MachRep3.py: Introducing Passivate/Reactivate Operations
`
`. . . . . . . . . . . . . 16
`
`3.2.4 MMk.py: “Do It Yourself” Queue Management . . . . . . . . . . . . . . . . . . . . 18
`
`3.2.5
`
`SMP.py: Simultaneous Possession of Resources . . . . . . . . . . . . . . . . . . . . 20
`
`3.2.6 Cell.py: Dynamic Creation of Threads . . . . . . . . . . . . . . . . . . . . . . . . . 22
`
`3.3 Note These Restrictions on PEMs
`
`. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
`
`3.4 SimPy Data Collection and Display . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
`
`3.4.1
`
`Introduction to Monitors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
`
`3.4.2 Time Averages .
`
`.
`
`.
`
`. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
`
`3.4.3 The Function Monitor.timeAverage()
`
`. . . . . . . . . . . . . . . . . . . . . . . . . 27
`
`3.4.4 But I Recommend That You Not Use This Function . . . . . . . . . . . . . . . . . . 27
`
`1
`
`
`
`Case 2:15-cv-00225-JRG-RSP Document 60-5 Filed 09/28/15 Page 3 of 34 PageID #: 1048
`
`3.4.5 Little’s Rule .
`
`.
`
`3.5 Other SimPy Features .
`
`.
`
`.
`
`.
`
`.
`
`.
`
`.
`
`. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
`
`. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
`
`A How to Obtain and Install SimPy
`
`29
`
`30
`B Debugging and Verifying SimPy Programs
`B.1 Debugging Tools
`.
`.
`.
`.
`.
`.
`. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
`
`B.2 Know How Control Transfers in SimPy Programs . . . . . . . . . . . . . . . . . . . . . . . 30
`
`B.3 Always Know What (Simulated) Time It Is
`
`. . . . . . . . . . . . . . . . . . . . . . . . . . 31
`
`B.4 Starting Over
`
`.
`
`B.5 Repeatability .
`
`.
`
`.
`
`.
`
`.
`
`. .
`
`. .
`
`. .
`
`. .
`
`.
`
`.
`
`. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
`
`. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
`
`B.6 Peeking at the SimPy’s Internal Event List . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
`
`B.7 SimPy’s Invaluable Tracing Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
`
`C Online Documentation for SimPy
`
`33
`
`2
`
`
`
`Case 2:15-cv-00225-JRG-RSP Document 60-5 Filed 09/28/15 Page 4 of 34 PageID #: 1049
`
`1 What Is Discrete-Event Simulation (DES)?
`
`Consider simulation of some system which evolves through time. There is a huge variety of such applica-
`tions. One can simulate a weather system, for instance. A key point, though, is that in that setting, the events
`being simulated would be continuous, meaning for example that if we were to graph temperature against
`time, the curve would be continuous, no breaks.
`
`By contrast, suppose we simulate the operation of a warehouse. Purchase orders come in and are filled,
`reducing inventory, but inventory is replenished from time to time. Here a typical variable would be the
`inventory itself, i.e. the number of items currently in stock for a given product. If we were to graph that
`number against time, we would get what mathematicians call a step function, i.e. a set of flat line seg-
`ments with breaks between them. The events here—decreases and increases in the inventory—are discrete
`variables, not continuous ones.
`
`DES involves simulating such systems.
`
`2 World Views in DES Programming
`
`Simulation programming can often be difficult—difficult to write the code, and difficult to debug. The
`reason for this is that it really is a form of parallel programming, with many different activities in progress
`simultaneously, and parallel programming can be challenging.
`For this reason, many people have tried to develop separate simulation languages, or at least simulation
`paradigms (i.e. programming styles) which enable to programmer to achieve clarity in simulation code.
`Special simulation languages have been invented in the past, notably SIMULA, which was invented in the
`1960s and has significance today in that it was the language which invented the concept of object-oriented
`programmg that is so popular today. However, the trend today is to simply develop simulation libraries
`which can be called from ordinary languages such as C++, instead of inventing entire new languages.1 So,
`the central focus today is on the programming paradigms, not on language. In this section we will present
`an overview of the three major discrete-event simulation paradigms.
`Several world views have been developed for DES programming, as seen in the next few sections.
`
`2.1 The Activity-Oriented Paradigm
`
`Let us think of simulating a queuing system. Jobs arrive at random times, and the job server takes a ran-
`dom time for each service. The time between arrivals of jobs, and the time needed to serve a job, will be
`continuous random variables, possibly having exponential or other continuous distributions.
`
`For concreteness, think of an example in which the server is an ATM cash machine and the jobs are cus-
`tomers waiting in line.
`Under the activity-oriented paradigm, we would break time into tiny increments. If for instance the mean
`interarrival time were, say 20 seconds, we might break time into increments of size 0.001. At each time
`point, our code would look around at all the activities, e.g. currently-active job servicing, and check for the
`possible occurrence of events, e.g. completion of service. Our goal is to find the long-run average job wait
`
`1These libraries are often called “languages” anyway, and I will do so too.
`
`3
`
`
`
`Case 2:15-cv-00225-JRG-RSP Document 60-5 Filed 09/28/15 Page 5 of 34 PageID #: 1050
`
`time.
`
`Let SimTime represent current simulated time. Our simulation code in the queue example above would look
`something like this:
`
`1
`
`2
`
`3
`
`4
`
`5
`
`6
`
`7
`
`8
`
`9
`
`10
`
`11
`
`12
`
`13
`
`14
`
`15
`
`16
`
`17
`
`18
`
`19
`
`20
`
`21
`
`22
`
`23
`
`24
`
`25
`
`26
`
`27
`
`28
`
`29
`
`QueueLength = 0
`NJobsServed = 0
`SumResidenceTimes = 0
`ServerBusy = false
`generate NextArrivalTime // random # generation
`NIncrements = MaxSimTime / 0.001
`for SimTime = 1*0.001 to NIncrements*0.001 do
`if SimTime = NextArrivalTime then
`add new jobobject to queue
`QueueLength++
`generate NextArrivalTime // random # generation
`if not ServerBusy then
`ServerBusy = true
`jobobject.ArrivalTime = SimTime
`generate ServiceFinishedtime
`currentjob = jobobject
`delete head of queue and assign to currentjob
`QueueLength--
`
`else
`if SimTime = ServiceFinishedtime then
`NJobsServed++
`SumResidenceTimes += SimTime - currentjob.ArrivalTime
`if QueueLength > 0 then
`generate ServiceFinishedtime
`delete currentjob from queue
`QueueLength--
`else
`ServerBusy = false
`print out SumResidenceTimes / NJobsServed
`
`// random # generation
`
`2.2 The Event-Oriented Paradigm
`
`Clearly, an activity-oriented simulation program is going to be very slow to execute. Most time increments
`will produce no state change to the system at all, i.e. no new arrivals to the queue and no completions of
`service by the server. Thus the activity checks will be wasted processor time. This is a big issue, because
`in general simulation code often needs a very long time to run. (Electronic chip manufacturers use DES for
`chip simulation. A simulation can take days to run.)
`
`Inspection of the above pseudocode, though, shows a way to dramatically increase simulation speed. Instead
`of having time “creep along” so slowly, why not take a “shortcut” to the next event? What we could do is
`something like the following:
`
`Instead of having the simulated time advance via the code
`
`1
`
`for SimTime = 1*0.001 to NIncrements*0.001 do
`
`we could advance simulated time directly to the time of the next event:
`
`4
`
`
`
`Case 2:15-cv-00225-JRG-RSP Document 60-5 Filed 09/28/15 Page 6 of 34 PageID #: 1051
`
`1
`
`2
`
`3
`
`4
`
`5
`
`1
`
`2
`
`3
`
`4
`
`5
`
`6
`
`7
`
`8
`
`9
`
`10
`
`11
`
`12
`
`13
`
`14
`
`15
`
`16
`
`17
`
`18
`
`19
`
`20
`
`21
`
`22
`
`23
`
`24
`
`25
`
`26
`
`27
`
`28
`
`29
`
`30
`
`31
`
`if ServerBusy and NextArrivalTime < ServiceFinishedtime or
`not ServerBusy then
`SimTime = NextArrivalTime
`
`else
`SimTime = ServiceFinishedtime
`
`(The reason for checking ServerBusy is that ServiceFinishedtime will be undefined if ServerBusy is false.)
`The entire pseudocode would then be
`
`QueueLength = 0
`NJobsServed = 0
`SumResidenceTimes = 0
`ServerBusy = false
`generate NextArrivalTime
`SimTime = 0.0;
`while (1) do
`if ServerBusy and NextArrivalTime < ServiceFinishedtime or
`not ServerBusy then
`SimTime = NextArrivalTime
`
`else
`SimTime = ServiceFinishedtime
`if SimTime > MaxSimTime then break
`if SimTime = NextArrivalTime then
`QueueLength++
`generate NextArrivalTime
`if not ServerBusy then
`ServerBusy = true
`jobobject.ArrivalTime = SimTime
`currentjob = jobobject
`generate ServiceFinishedtime
`QueueLength--
`else // the case SimTime = ServiceFinishedtime
`NJobsServed++
`SumResidenceTimes += SimTime - currentjob.ArrivalTime
`if QueueLength > 0 then
`generate ServiceFinishedtime
`QueueLength--
`else
`ServerBusy = false
`print out SumResidenceTimes / NJobsServed
`
`The event-oriented paradigm formalizes this idea. We store an event set, which is the set of all pending
`events. In our queue example above, for instance, there will always be at least one event pending, namely
`the next arrival, and sometimes a second pending event, namely the completion of a service. Our code above
`simply inspects the scheduled event times of all pending events (again, there will be either one or two of
`them in our example here), and updates SimTime to the minimum among them.
`In the general case, there may be many events in the event set, but the principle is still the same—in each
`iteration of the while loop, we update SimTime to the minimum among the scheduled event times. Note
`also that in each iteration of the while loop, a new event is generated and added to the set; be sure to look at
`the pseudocode above and verify this.
`
`5
`
`
`
`Case 2:15-cv-00225-JRG-RSP Document 60-5 Filed 09/28/15 Page 7 of 34 PageID #: 1052
`
`Thus a major portion of the execution time for the program will consist of a find-minimum operation within
`the event set. Accordingly, it is desirable to choose a data structure for the set which will facilitate this
`operation, such as a heap-based priority queue. In many event-oriented packages, though, the event set is
`implemented simply as a linearly-linked list. This will be sufficiently efficient as long as there usually aren’t
`too many events in the event set; again, in the queue example above, the maximum size of the event set is 2.
`(We will return to the issue of efficient event lists in a later unit.)
`
`Again, note the contrast between this and continuous simulation models. The shortcut which is the heart
`of the event-oriented paradigm was only possible because of the discrete nature of system change. So this
`paradigm is not possible in models in which the states are continuous in nature.
`
`The event-oriented paradigm was common in the earlier years of simulation, used in packages in which code
`in a general-purpose programming language such as C called functions in a simulation library. It still has
`some popularity today. Compared to the main alternative, the process-oriented paradigm, the chief virtues
`of the event-oriented approach are:
`
`• Ease of implementation. The process-oriented approach requires something like threads, and in those
`early days there were no thread packages available. One needed to write one’s own threads mecha-
`nisms, by writing highly platform-dependent assembly-language routines for stack manipulation.
`• Execution speed. The threads machinery of process-oriented simulation really slows down execution
`speed (even if user-level threads are used).
`• Flexibility. If for example one event will trigger two others, it is easy to write this into the application
`code.
`
`2.3 The Process-Oriented Paradigm
`
`Here each simulation activity is modeled by a process. The idea of a process is similar to the notion by
`the same name in Unix, and indeed one could write process-oriented simulations using Unix processes.
`However, these would be inconvenient to write, difficult to debug, and above all they would be slow.
`
`As noted earlier, the old process-oriented software such as SIMULA and later CSIM were highly platform-
`dependent, due to the need for stack manipulation. However, these days this problem no longer exists, due
`to the fact that modern systems include threads packages (e.g. pthreads in Unix, Java threads, Windows
`threads and so on). Threads are sometimes called “lightweight” processes.
`
`If we were to simulate a queuing system as above, but using the process-oriented paradigm, we would have
`two threads, one simulating the arrivals and the other simulating the operation of the server. Those would
`be the application-specific threads (so NumActiveAppThreads = 2 in the code below), and we would also
`have a general thread to manage the event set.
`
`Our arrivals thread would look something like
`
`1
`
`2
`
`3
`
`4
`
`5
`
`6
`
`NumActiveAppThreads++
`while SimTime < MaxSimTime do
`generate NextArrivalTime
`add an arrival event for time NextArrivalTime to the event set
`sleep until wakened by the event-set manager
`jobobject.ArrivalTime = SimTime
`
`6
`
`
`
`Case 2:15-cv-00225-JRG-RSP Document 60-5 Filed 09/28/15 Page 8 of 34 PageID #: 1053
`
`7
`
`8
`
`1
`
`2
`
`3
`
`4
`
`5
`
`6
`
`7
`
`8
`
`9
`
`10
`
`11
`
`12
`
`1
`
`2
`
`3
`
`4
`
`5
`
`6
`
`1
`
`2
`
`3
`
`4
`
`5
`
`6
`
`7
`
`add jobobject to the machine queue
`thread exit
`
`The server thread would look something like
`
`NumActiveAppThreads++
`while SimTime < MaxSimTime do
`sleep until QueueLength > 0
`while QueueLength > 0 do
`remove queue head and assign to jobobject
`QueueLength--
`generate ServiceFinishedtime
`add a service-done event for time ServiceFinishedtime to the event set
`sleep until wakened by the event-set manager
`SumResidenceTimes += SimTime - jobobject.ArrivalTime
`NJobsServed++
`thread exit
`
`The event set manager thread would look something like
`
`while SimTime < MaxSimTime do
`sleep until event set is nonempty
`delete the minimum-time event E from the event set
`update SimTime to the time scheduled for E
`wake whichever thread had added E to the event set
`thread exit
`
`The function main() would look something like this:
`
`QueueLength = 0
`NJobsServed = 0
`SumResidenceTimes = 0
`ServerBusy = false
`start the 3 threads
`sleep until all 3 threads exit
`print out SumResidenceTimes / NJobsServed
`
`Note that the event set manager would be library code, while the other modules shown above would be
`application code.
`
`Two widely used oper-source process-oriented packages are C++SIM, available at http://cxxsim.
`ncl.ac.uk and SimPy, available at http://simpy.sourceforge.net.
`
`The process-oriented paradigm produces more modular code. This is probably easier to write and easier for
`others to read. It is considered more elegant, and is the more popular of the two main world views today.
`
`3 Introduction to the SimPy Simulation Language
`
`SimPy (rhymes with “Blimpie”) is a package for process-oriented discrete-event simulation. It is written in,
`and called from, Python. I like the clean manner in which it is designed, and the use of Python generators—
`
`7
`
`
`
`Case 2:15-cv-00225-JRG-RSP Document 60-5 Filed 09/28/15 Page 9 of 34 PageID #: 1054
`
`and for that matter, Python itself—is a really strong point. If you haven’t used Python before, you can learn
`enough about it to use SimPy quite quickly; see my quick introduction to Python, at my Python tutorials
`page, http://heather.cs.ucdavis.edu/˜matloff/python.html.
`
`Instructions on how to obtain and install SimPy are given in Appendix A.
`
`Instead of using threads, as is the case for most process-oriented simulation packages, SimPy makes novel
`use of Python’s generators capability.2 Generators allow the programmer to specify that a function can be
`prematurely exited and then later re-entered at the point of last exit, enabling coroutines, meaning functions
`that alternate execution with each other. The exit/re-entry points are marked by Python’s yield keyword.
`Each new call to the function causes a resumption of execution of the function at the point immediately
`following the last yield executed in that function. As you will see below, that is exactly what we need for
`DES.
`
`For convenience, I will refer to each coroutine (or, more accurately, each instance of a coroutine), as a
`thread.3
`
`3.1 SimPy Overview
`
`Here are the major SimPy classes which we will cover in this introduction:4
`
`• Process: simulates an entity which evolves in time, e.g. one customer who needs to be served by an
`ATM machine; we will refer to it as a thread, even though it is not a formal Python thread
`• Resource: simulates something to be queued for, e.g. the machine
`
`Here are the major SimPy operations/function calls we will cover in this introduction:
`
`• activate(): used to mark a thread as runnable when it is first created
`• simulate(): starts the simulation
`• yield hold: used to indicate the passage of a certain amount of time within a thread; yield is a Python
`operator whose first operand is a function to be called, in this case a code for a function that performs
`the hold operation in the SimPy library
`• yield request: used to cause a thread to join a queue for a given resource (and start using it immedi-
`ately if no other jobs are waiting for the resource)
`• yield release: used to indicate that the thread is done using the given resource, thus enabling the next
`thread in the queue, if any, to use the resource
`• yield passivate: used to have a thread wait until “awakened” by some other thread
`2Python 2.2 or better is required. See my Python generators tutorial at the above URL if you wish to learn about generators, but
`you do not need to know about them to use SimPy.
`3This tutorial does not assume the reader has a background in threads programming. In fact, readers who do have that back-
`ground will have to unlearn some of what they did before, because our threads here will be non-preemptive, unlike the preemptive
`type one sees in most major threads packages.
`4Others will be covered in our followup tutorial at AdvancedSimpy.pdf.
`
`8
`
`
`
`Case 2:15-cv-00225-JRG-RSP Document 60-5 Filed 09/28/15 Page 10 of 34 PageID #:
` 1055
`
`• reactivate(): does the “awakening” of a previously-passivated thread
`• cancel(): cancels all the events associated with a previously-passivated thread
`Here is how the flow of control goes from one function to another:
`
`• When main() calls simulate() main() blocks. The simulation itself then begins, and main() will not
`run again until the simulation ends. (When main() resumes, typically it will print out the results of
`the simulation.)
`• Anytime a thread executes yield, that thread will pause. SimPy’s internal functions will then run, and
`will restart some thread (possibly the same thread).
`• When a thread is finally restarted, its execution will resume right after whichever yield statement was
`executed last in this thread.
`
`Note that activate(), reactivate() and cancel do NOT result in a pause to the calling function. Such a pause
`occurs only when yield is invoked. Those with extensive experience in threads programming (which, as
`mentioned, we do NOT assume here) will recognize this the non-preemptive approach to threads. In my
`opinion, this is a huge advantage, for two reasons:
`
`• Your code is not cluttered up with a lot of lock/unlock operations.
`• Execution is deterministic, which makes both writing and debugging the program much easier.
`(A disadvantage is that SimPy, in fact Python in general, cannot run in a parallel manner on multiprocessor
`machines.)
`
`3.2
`
`Introduction to SimPy Programming
`
`In
`We will demonstrate the usage of SimPy by presenting three variations on a machine-repair model.
`each case, we are modeling a system consisting of two machines which are subject to breakdown, but with
`different repair patterns:
`
`• MachRep1.py: There are two repairpersons, so that the two machines can be repaired simultaneously
`if they are both down at once.
`• MachRep2.py: Here there is only one repairperson, so if both machines are down then one machine
`must queue for the repairperson while the other machine is being repaired.
`• MachRep3.py: Here there is only one repairperson, and he/she is not summoned until both machines
`are down.
`
`In all cases, the up times and repair times are assumed to be exponentially distributed with means 1.0 and
`0.5, respectively. Now, let’s look at the three programs.5
`5You can make your own copies of these programs by downloading the raw .tex file for this tutorial, and then editing out the
`material other than the program you want.
`
`9
`
`
`
`Case 2:15-cv-00225-JRG-RSP Document 60-5 Filed 09/28/15 Page 11 of 34 PageID #:
` 1056
`
`3.2.1 MachRep1.py: Our First SimPy Program
`
`Here is the code:
`
`#!/usr/bin/env python
`
`# MachRep1.py
`
`# Introductory SimPy example: Two machines, which sometimes break down.
`# Up time is exponentially distributed with mean 1.0, and repair time is
`# exponentially distributed with mean 0.5. There are two repairpersons,
`# so the two machines can be repaired simultaneously if they are down
`# at the same time.
`
`# Output is long-run proportion of up time. Should get value of about
`# 0.66.
`
`import SimPy.Simulation # required
`import random
`
`class G: # global variables
`Rnd = random.Random(12345)
`
`class MachineClass(SimPy.Simulation.Process):
`UpRate = 1/1.0 # reciprocal of mean up time
`RepairRate = 1/0.5 # reciprocal of mean repair time
`TotalUpTime = 0.0 # total up time for all machines
`NextID = 0 # next available ID number for MachineClass objects
`def __init__(self): # required constructor
`SimPy.Simulation.Process.__init__(self) # must call parent constructor
`# instance variables
`self.StartUpTime = 0.0 # time the current up period started
`self.ID = MachineClass.NextID
`# ID for this MachineClass object
`MachineClass.NextID += 1
`def Run(self): # required constructor
`while 1:
`# record current time, now(), so can see how long machine is up
`self.StartUpTime = SimPy.Simulation.now()
`# hold for exponentially distributed up time
`UpTime = G.Rnd.expovariate(MachineClass.UpRate)
`yield SimPy.Simulation.hold,self,UpTime # simulate UpTime
`# update up time total
`MachineClass.TotalUpTime += SimPy.Simulation.now() - self.StartUpTime
`RepairTime = G.Rnd.expovariate(MachineClass.RepairRate)
`# hold for exponentially distributed repair time
`yield SimPy.Simulation.hold,self,RepairTime
`
`def main():
`SimPy.Simulation.initialize() # required
`# set up the two machine threads
`for I in range(2):
`# create a MachineClass object
`M = MachineClass()
`# register thread M, executing M’s Run() method,
`SimPy.Simulation.activate(M,M.Run()) # required
`# run until simulated time 10000
`MaxSimtime = 10000.0
`SimPy.Simulation.simulate(until=MaxSimtime) # required
`print "the percentage of up time was", \
`MachineClass.TotalUpTime/(2*MaxSimtime)
`
`if __name__ == ’__main__’: main()
`
`First, some style issues:
`
`10
`
`1
`
`2 3
`
`4 5
`
`6
`7
`8
`9
`10
`11
`12
`13
`14
`15
`16
`17
`18
`19
`20
`21
`22
`23
`24
`25
`26
`27
`28
`29
`30
`31
`32
`33
`34
`35
`36
`37
`38
`39
`40
`41
`42
`43
`44
`45
`46
`47
`48
`49
`50
`51
`52
`53
`54
`55
`56
`57
`58
`
`
`
`Case 2:15-cv-00225-JRG-RSP Document 60-5 Filed 09/28/15 Page 12 of 34 PageID #:
` 1057
`
`• My style is to put all global variables into a Python class, which I usually call G. See my Python
`introductory tutorial, cited earlier, if you wish to know my reasons.
`• In order to be able to use debugging tools, I always define a function main() which is my “main”
`program, and include the line
`
`if __name__ == ’__main__’: main()
`
`Again, see my Python introductory tutorial if you wish to know the reasons.
`• In this first SimPy example, I am using the “wordier” form of Python’s import facility:
`
`import SimPy.Simulation
`
`This leads to rather cluttered code, such as
`
`SimPy.Simulation.simulate(until=MaxSimtime)
`
`instead of
`
`simulate(until=MaxSimtime)
`
`The latter could be used had we done the import via
`
`from SimPy.Simulation import *
`
`But in this first SimPy program, I wanted to clearly distinguish SimPy’s functions from the others.
`The same holds for the functions in the Python library random. So, in this program, we use long
`names.
`
`Let’s look at main(). Since we are simulating two machines, we create two objects of our MachineClass
`class. These will be the basis for our two machine threads. Here MachineClass is a class that I wrote, as a
`subclass of SimPy’s built-in class Process.
`By calling SimPy’s activate() function on the two instances of MachineClass, we tell SimPy to create a
`thread for each of them, which will execute the Run() function for their class. This puts them on SimPy’s
`internal “ready” list of threads that are ready to run.
`The call to SimPy’s simulate() function starts the simulation. The next statement, the print, won’t execute
`for quite a while, since it won’t be reached until the call to simulate() returns, and that won’t occur until the
`end of the simulation.
`Python allows named arguments in function calls,6, and this feature is used often in the SimPy library. For
`example, SimPy’s simulate() function has many arguments, one of which is named until.7 In our call here,
`we have only specified the value of until, omitting the values of the other arguments. That tells the Python
`interpreter that we accept whatever default values the other arguments have, but we want the argument until
`to have the value 10000.0. That argument has the meaning that we will run the simulation for a simulated
`time span of duration 10000.0.
`In general, I’ll refer to the functions like MachineClass.Run() in this example) as process execution meth-
`ods (PEMs). (Functions in Python are called methods.)
`6See my Python introductory tutorial.
`7Look in the file Simulation.py of the SimPy library to see the entire code for simulate().
`
`11
`
`
`
`Case 2:15-cv-00225-JRG-RSP Document 60-5 Filed 09/28/15 Page 13 of 34 PageID #:
` 1058
`
`The object G.Rnd is an instance of the Random class in the random module of the Python library. This
`will allow us to generate random numbers, the heart of the simulation. We have arbitrarily initialized the
`seed to 12345.
`
`Since we are assuming up times and repair times are exponentially distributed, our code calls the function
`random.Random.expovariate(). Its argument is the reciprocal of the mean. Here we have taken the mean
`up time and repair times to be 1.0 and 0.5, respectively, just as an example.
`Note too that Python’s random class contains a variety of random number generators. To see what is
`available, get into interactive mode in Python and type
`
`>>> import random
`>>> dir(random)
`
`To find out what the functions do, use Python’s online help facility, e.g.
`
`>>> help(random.expovariate)
`
`The call to SimPy’s initialize() function is required for all SimPy programs.
`Now, let’s look at MachineClass. First we define two class variables,8 TotalUpTime and NextID. As
`the comment shows, TotalUpTime will be used to find the total up time for all machines, so that we can
`eventually find out what proportion of the time the machines are up. Be sure to make certain you understand
`why TotalUpTime must be a class variable rather than an instance variable.
`().9 Since our class here, MachineClass, is a subclass
`init
`Next, there is the class’ constructor function,
`of the SimPy built-in class Process, the first thing we must do is call the latter’s constructor; our program
`will not work if we forget this (it will also fail if we forget the argument self in either constructor).
`Finally, we set several of the class’ instance variables, explained in the comments. Note in particular the ID
`variable. You should always put in some kind of variable like this, not necessarily because it is used in the
`simulation code itself, but rather as a debugging aid.
`
`If you have experience with pre-emptive thread systems, note that we did NOT need to protect the line
`
`MachineClass.NextID += 1
`
`with a lock variable. This is because a SimPy thread retains control until voluntarily relinquishing it via a
`yield. Our thread here will NOT be interrupted in the midst of incrementing MachineClass.NextID.
`Now let’s look at the details of Machine.Run(), where the main action of the simulation takes place.
`The SimPy function now() yields the current simulated time. We are starting this machine in up mode, i.e.
`no failure has occurred yet. Remember, we want to record how much of the time each machine is up, so
`we need to have a variable which shows when the current up period for this machine began. With this in
`mind, we had our code self.StartUpTime = SimPy.Simulation.now() record the current time, so that later
`the code
`8If you are not familiar with the general object-oriented programming terms class variable and instance variable, see my
`Python introductory tutorial.
`9Some programmers consider this to be a bit different from a constructor function, but I’ll use that term here.
`
`12
`
`
`
`Case 2:15-cv-00225-JRG-RSP Document 60-5 Filed 09/28/15 Page 14 of 34 PageID #:
` 1059
`
`MachineClass.TotalUpTime += SimPy.Simulation.now() - self.StartUpTime
`
`will calculate the duration of this latest uptime period, and add it to our running total.
`Again, make sure you understand why StartUpTime needs to be an instance variable rather than a class
`variable.
`
`A point to always remember about simulation programming is that you must constantly go back and forth
`between two mental views of things. On the one hand, there is what I call the “virtual reality” view, where
`you are imagining what would happen in the real system you are simulating. On the other hand, there is the
`“nuts and bolts programming” view, in which you are focused on what actual program statesments do. With
`these two views in mind, let’s discuss the lines
`
`UpTime = G.Rnd.expovariate(MachineClass.UpRate)
`yield SimPy.Simulation.hold,self,UpTime
`
`First, from a “virtual reality” point of view, what the yield does is simulate the passage of time, specifically,
`UpTime amount of time, while the machine goes through an up period, at the end of which a breakdown
`occurs.
`Now here’s the “nuts and bolts programming” point of view: Python’s yield construct is a like a return,
`as it does mean an exit from the function and the passing of a return value to the caller. In this case, that
`return value is the tuple (SimPy.Simulation.hold,self,UpTime). Note by the way that the first element in
`that tuple is in SimPy cases always the name of a function in the SimPy library. The difference between
`yield and return is that the “exit” from the function is only temporary. The SimPy internals will later call
`this function again, and instead of starting at the beginning, it will “pick up where it left off.” In other words,
`the statement
`
`yield SimPy.Simulation.hold,self,UpTime
`
`will cause a temporary exit from the function but later we will come back and resume execution at the line
`
`MachineClass.TotalUpTime += SimPy.Simulation.now() - self.StartUpTime
`
`The term “yield” alludes to the fact that this thread physically relinquishes control of the Python interpreter.
`Execution of this thread will be suspended, and another thread will be run. Later, after simulated time
`has advanced to the end of the up period, control will return to this thread, resuming exactly where the
`suspension occurred.
`
`The second yield,
`
`RepairTime = G.Rnd.expovariate(MachineClass.RepairRate)
`yield SimPy.Simulation.hold,self,RepairTime
`
`works similarly, suspending execution of the thread for a simulated exponentially-distributed amount of time
`to model the repair time.
`In other words, the while loop within MachineClass.Run() simulates a repeated cycle of up time, down
`time, up time, down time, ... for this machine.
`
`13
`
`
`
`Case 2:15-cv-00225-JRG-RSP Document 60-5 Filed 09/28/15 Page 15 of 34 PageID #:
` 1060
`
`It is very important to understand how control transfers back and