examples_classic.rst 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. ..
  2. Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
  3. McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. Examples
  14. ========
  15. A small *trafficModel* and corresponding *trafficExperiment* file is included in the *examples* folder of the PyPDEVS distribution. This (completely working) example is slightly too big to use as a first introduction to PyPDEVS and therefore this page will start with a very simple example.
  16. For this, we will first introduce a simplified queue model, which will be used as the basis of all our examples. The complete model can be downloaded: :download:`queue_example_classic.py <queue_example_classic.py>`.
  17. This section should provide you with all necessary information to get you started with creating your very own PyPDEVS simulation. More advanced features are presented in the next section.
  18. Generator
  19. ---------
  20. Somewhat simpler than a queue even, is a generator. It will simply create a message to send after a certain delay and then it will stop doing anything.
  21. Informally, this would result in a DEVS specification as:
  22. * Time advance function returns the waiting time to generate the message, infinity after the message was created
  23. * Output function outputs the generated message
  24. * Internal transition function marks the generator as done
  25. * External transition function will never happen (as there are no inputs)
  26. In PythonPDEVS, this simply becomes the class::
  27. class Generator(AtomicDEVS):
  28. def __init__(self):
  29. AtomicDEVS.__init__(self, "Generator")
  30. self.state = True
  31. self.outport = self.addOutPort("outport")
  32. def timeAdvance(self):
  33. if self.state:
  34. return 1.0
  35. else:
  36. return INFINITY
  37. def outputFnc(self):
  38. # Our message is simply the integer 5, though this could be anything
  39. return {self.outport: 5}
  40. def intTransition(self):
  41. self.state = False
  42. return self.state
  43. Note that all functions have a *default* behaviour, which is sufficient if the function will never be called.
  44. It is possible to simulate this model, though nothing spectacular will happen. For this reason, we will postpone actual simulation examples.
  45. Simple queue
  46. ------------
  47. To couple the *Generator* model up to something useful, we will now create a simple queue model. It doesn't do any real computation and just forwards the message after a certain (fixed) time delay. For simplicity, we allow the queue to *drop* the previous message if a message was already being processed.
  48. Informally, this would result in a DEVS specification as:
  49. * Time advance function returns the processing time if a message is being processed, or INFINITY otherwise
  50. * Output function outputs the message
  51. * Internal transition function removes the message from the queue
  52. * External transition function adds the message to the queue
  53. To implement this in PythonPDEVS, one simply has to write::
  54. class Queue(AtomicDEVS):
  55. def __init__(self):
  56. AtomicDEVS.__init__(self, "Queue")
  57. self.state = None
  58. self.processing_time = 1.0
  59. self.inport = self.addInPort("input")
  60. self.outport = self.addOutPort("output")
  61. def timeAdvance(self):
  62. if self.state is None:
  63. return INFINITY
  64. else:
  65. return self.processing_time
  66. def outputFnc(self):
  67. return {self.outport: self.state}
  68. def extTransition(self, inputs):
  69. self.state = inputs[self.inport]
  70. return self.state
  71. def intTransition(self):
  72. self.state = None
  73. return self.state
  74. And we are done with our basic queue model.
  75. However, there is currently no means of testing it, as simply simulating this model will have no effect, due to no messages arriving. We will thus have to couple it with the *Generator* we previously made.
  76. Coupling
  77. --------
  78. To couple up the *Generator* to the *Queue*, all we have to do is create a *CoupledDEVS* class and simulate this class::
  79. class CQueue(CoupledDEVS):
  80. def __init__(self):
  81. CoupledDEVS.__init__(self, "CQueue")
  82. self.generator = self.addSubModel(Generator())
  83. self.queue = self.addSubModel(Queue())
  84. self.connectPorts(self.generator.outport, self.queue.inport)
  85. That is all for the coupled model. Note that it is not required for every port of a model to be connected to another port. For example the *outport* of the *Queue* is not connected. Any output that is put on this port is thus discarded.
  86. It is perfectly allowed to do model construction and connection in e.g. a loop or conditionally, as long as the required functions are called.
  87. .. note:: The DEVS formalism allows for an input-to-output translation function, but this is not implemented in PythonPDEVS.
  88. Simulation
  89. ----------
  90. Now that we have an actual coupled model that does something remotely useful, it is time to simulate it. Simulation is as simple as constructing a *Simulator* object with the model and calling *simulate()* on the simulator::
  91. model = CQueue()
  92. sim = Simulator(model)
  93. # Required to set Classic DEVS, as we simulate in Parallel DEVS otherwise
  94. sim.setClassicDEVS()
  95. sim.simulate()
  96. Be sure not to forget to call the *setClassicDEVS()* method, as otherwise your model will be simulated using Parallel DEVS (likely resulting into errors).
  97. Sadly, nothing seems to happen because no tracers are enabled. Note that it is possible to access the attributes of the model and see that they are actually changed as directed by the simulation::
  98. model = CQueue()
  99. print(model.generator.state)
  100. sim = Simulator(model)
  101. # Required to set Classic DEVS, as we simulate in Parallel DEVS otherwise
  102. sim.setClassicDEVS()
  103. sim.simulate()
  104. print(model.generator.state)
  105. This code will simply print *True* in the beginning and *False* at the end, since the model is updated in-place in this situation. The model will **not** be simulated in-place if reinitialisation is enabled.
  106. Tracing
  107. -------
  108. To actually see some results from the simulation, it is advised to enable certain tracers. The simplest tracer is the *verbose* tracer, which will output some details in a human-readable format. Enabling the verbose tracer is as simple as setting the *setVerbose()* configuration to a destination file. For the verbose tracer, it is also possible to trace to stdout by using the *None* argument::
  109. model = CQueue()
  110. sim = Simulator(model)
  111. sim.setVerbose(None)
  112. # Required to set Classic DEVS, as we simulate in Parallel DEVS otherwise
  113. sim.setClassicDEVS()
  114. sim.simulate()
  115. Saving the output to a file can de done by passing the file name as a string. Note that a file handle does **not** work::
  116. model = CQueue()
  117. sim = Simulator(model)
  118. sim.setVerbose("myOutputFile")
  119. # Required to set Classic DEVS, as we simulate in Parallel DEVS otherwise
  120. sim.setClassicDEVS()
  121. sim.simulate()
  122. Multiple tracers can be defined simultaneously, all of which will be used. So to trace to the files *myOutputFile* and *myOutputFile* and simultaneously output to stdout, you could use::
  123. model = CQueue()
  124. sim = Simulator(model)
  125. sim.setVerbose("myOutputFile")
  126. sim.setVerbose(None)
  127. sim.setVerbose("myOutputFile2")
  128. # Required to set Classic DEVS, as we simulate in Parallel DEVS otherwise
  129. sim.setClassicDEVS()
  130. sim.simulate()
  131. .. note:: There is no way to unset a single tracer. There is however a way to remove all currently registered tracers: *setRemoveTracers()*, though it is generally only useful in reinitialized simulations.
  132. An example output of the *verbose* tracer is::
  133. __ Current Time: 0.00 __________________________________________
  134. INITIAL CONDITIONS in model <CQueue.Generator>
  135. Initial State: True
  136. Next scheduled internal transition at time 1.00
  137. INITIAL CONDITIONS in model <CQueue.Queue>
  138. Initial State: None
  139. Next scheduled internal transition at time inf
  140. __ Current Time: 1.00 __________________________________________
  141. EXTERNAL TRANSITION in model <CQueue.Queue>
  142. Input Port Configuration:
  143. port <input>:
  144. 5
  145. New State: 5
  146. Next scheduled internal transition at time 2.00
  147. INTERNAL TRANSITION in model <CQueue.Generator>
  148. New State: False
  149. Output Port Configuration:
  150. port <outport>:
  151. 5
  152. Next scheduled internal transition at time inf
  153. __ Current Time: 2.00 __________________________________________
  154. INTERNAL TRANSITION in model <CQueue.Queue>
  155. New State: None
  156. Output Port Configuration:
  157. port <output>:
  158. 5
  159. Next scheduled internal transition at time inf
  160. .. note:: Several other tracers are available, such as *VCD*, *XML* and *Cell*. Their usage is very similar and is only useful in several situations. Only the *Cell* tracer requires further explanation and is mentioned in the *Advanced examples* section.
  161. Termination
  162. -----------
  163. Our previous example stopped simulation automatically, since both models returned a time advance equal to infinity.
  164. In several cases, it is desired to stop simulation after a certain period. The simplest example of this is when the *Generator* would keep generating messages after a certain delay. Without a termination condition, the simulation will keep going forever.
  165. Adding a termination time is as simple as setting one additional configuration option::
  166. sim.setTerminationTime(5.0)
  167. This will make the simulation stop as soon as simulation time 5.0 is reached.
  168. A termination time is sufficient in most situations, though it is possible to use a more advanced approach: using a termination function. Using the option::
  169. sim.setTerminationCondition(termFunc)
  170. With this additional option, the function *termFunc* will be evaluated at every timestep. If the function returns *True*, simulation will stop. The function will receive 2 parameters from the simulator: the model being simulated and the current simulation time.
  171. Should our generator save the number of messages it has generated, an example of such a termination function could be::
  172. def termFunc(clock, model):
  173. if model.generator.generated > 5:
  174. # The generator has generated more than 5 events
  175. # So stop
  176. return True
  177. elif clock[0] > 10:
  178. # Or if the clock has progressed past simulation time 10
  179. return True
  180. else:
  181. # Otherwise, we simply continue
  182. return False
  183. The *clock* parameter in the termination condition will be a **tuple** instead of a simple floating point number. The first field of the tuple is the current simulation time (and can be used as such). The second field is a so-called *age* field, containing the number of times the same simulation time has occured. This is passed on in the termination condition as it is required in some cases for distributed simulation.
  184. .. note:: Using a termination function is a lot slower than simply using a termination time. This option should therefore be avoided if at all possible.
  185. .. warning:: It is **only** allowed to read from the model in the termination function. Performing write actions to the model has unpredictable consequences!
  186. .. warning:: Running a termination function in a distributed simulation is slightly different, so please refer to the advanced section for this!
  187. Simulation time
  188. ---------------
  189. Accessing the global simulation time is a frequent operation, though it is not supported by DEVS out-of-the-box. Of course, the simulator internally keeps such a clock, though this is not meant to be accessed by the user directly as it is an implementation detail of PyPDEVS (and it might even change between releases!).
  190. If you require access to the simulation time, e.g. to put a timestamp on a message, this can be done by writing some additional code in the model that requires this time as follows::
  191. class MyModelState():
  192. def __init__(self):
  193. self.actual_state = ...
  194. self.current_time = 0.0
  195. class MyModel(AtomicDEVS):
  196. def __init__(self, ...):
  197. AtomicDEVS.__init__(self, "ExampleModel")
  198. self.state = MyModelState()
  199. ...
  200. def extTransition(self, inputs):
  201. self.state.current_time += self.elapsed
  202. ...
  203. return self.state
  204. def intTransition(self):
  205. self.state.current_time += self.timeAdvance()
  206. ...
  207. return self.state
  208. In the *extTransition* method, we use the *elapsed* attribute to determine the time between the last transition and the current transition. However, in the *intTransition* we are **not** allowed to access it.
  209. A more detailed explanation can be found at :ref:`elapsed_time`.
  210. You are allowed to call the *timeAdvance* method again, as this is the time that was waited before calling the internal transition function (as defined in the DEVS formalism).
  211. This requires, however, that your timeAdvance is deterministic (as it should be).
  212. Deterministic timeAdvance functions are not trivial if you use random numbers, for which you should read up on :ref:`random_numbers` in PythonPDEVS.