examples_ds.rst 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  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 for Dynamic Structure DEVS
  14. ===================================
  15. We used the same approach to Dynamic Structure as adevs, meaning that a *modelTransition* method will be called at every step in simulated time on every model that transitioned.
  16. A small *trafficLight* model is included in the *examples* folder of the PyPDEVS distribution. In this section, we introduce the use of the new constructs.
  17. Dynamic structure is possible for both Classic and Parallel DEVS, but only for local simulation.
  18. Starting point
  19. --------------
  20. We will start from a very simple Coupled DEVS model, which contains 2 Atomic DEVS models.
  21. The first model sends messages on its output port, which are then routed to the second model.
  22. Note that, for compactness, we include the experiment part in the same file as the model.
  23. This can be expressed as follows (:download:`base_dsdevs.py <base_dsdevs.py>`)::
  24. from pypdevs.DEVS import AtomicDEVS, CoupledDEVS
  25. from pypdevs.simulator import Simulator
  26. class Root(CoupledDEVS):
  27. def __init__(self):
  28. CoupledDEVS.__init__(self, "Root")
  29. self.models = []
  30. # First model
  31. self.models.append(self.addSubModel(Generator()))
  32. # Second model
  33. self.models.append(self.addSubModel(Consumer(0)))
  34. # And connect them
  35. self.connectPorts(self.models[0].outport, self.models[1].inport)
  36. class Generator(AtomicDEVS):
  37. def __init__(self):
  38. AtomicDEVS.__init__(self, "Generator")
  39. # Keep a counter of how many events were sent
  40. self.outport = self.addOutPort("outport")
  41. self.state = 0
  42. def intTransition(self):
  43. # Increment counter
  44. return self.state + 1
  45. def outputFnc(self):
  46. # Send the amount of messages sent on the output port
  47. return {self.outport: [self.state]}
  48. def timeAdvance(self):
  49. # Fixed 1.0
  50. return 1.0
  51. class Consumer(AtomicDEVS):
  52. def __init__(self, count):
  53. AtomicDEVS.__init__(self, "Consumer_%i" % count)
  54. self.inport = self.addInPort("inport")
  55. def extTransition(self, inputs):
  56. for inp in inputs[self.inport]:
  57. print("Got input %i on model %s" % (inp, self.name))
  58. sim = Simulator(Root())
  59. sim.setTerminationTime(5)
  60. sim.simulate()
  61. All undefined functions will just use the default implementation.
  62. As such, this model will simply print messages::
  63. Got input 0 on model Consumer_0
  64. Got input 1 on model Consumer_0
  65. Got input 2 on model Consumer_0
  66. Got input 3 on model Consumer_0
  67. Got input 4 on model Consumer_0
  68. We will now extend this model to include dynamic structure.
  69. Dynamic Structure
  70. -----------------
  71. To allow for dynamic structure, we need to augment both our experiment and our model.
  72. Experiment
  73. ^^^^^^^^^^
  74. First, the dynamic structure configuration parameter should be enabled in the experiment.
  75. For performance reasons, this feature is disabled by default. It can be enabled as follows::
  76. sim = Simulator(Root())
  77. sim.setTerminationTime(5)
  78. sim.setDSDEVS(True)
  79. sim.simulate()
  80. Model
  81. ^^^^^
  82. With dynamic structure enabled, models can define a new method: *modelTransition(state)*.
  83. This method is called on all Atomic DEVS models that executed a transition in that time step.
  84. This method is invoked on the model, and can thus access the state of the model.
  85. For now, ignore the *state* parameter.
  86. In this method, all modifications on the model are allowed, as it was during model construction.
  87. That is, it is possible to invoke the following methods::
  88. setInPort(portname)
  89. setOutPort(portname)
  90. addSubModel(model)
  91. connectPorts(output, input)
  92. But apart from these known methods, it is also possible to delete existing constructs, with the operations::
  93. removePort(port)
  94. removeSubModel(model)
  95. disconnectPorts(output, input)
  96. On Atomic DEVS models, only the port operations are available. On Coupled DEVS models, all of these are available.
  97. Removing a port or submodel will automatically disconnect all its connections.
  98. The method will also return a boolean, indicating whether or not to propagate the structural changes on to the parent model.
  99. If it is *True*, the method is invoked on the parent as well. Note that the root model should not return *True*.
  100. Propagation is necessary, as models are only allowed to change the structure of their subtree.
  101. .. NOTE::
  102. In the latest implementation, modifying the structure outside of your own subtree has no negative consequences. However, it should be seen as a best practice to only modify yourself.
  103. For example, to create a second receiver as soon as the generator has output 3 messages, you can modify the following methods (:download:`simple_dsdevs.py <simple_dsdevs.py>`)::
  104. class Generator(AtomicDEVS):
  105. ...
  106. def modelTransition(self, state):
  107. # Notify parent of structural change if state equals 3
  108. return self.state == 3
  109. class Root(CoupledDEVS):
  110. ...
  111. def modelTransition(self, state):
  112. # We are notified, so are required to add a new model and link it
  113. self.models.append(self.addSubModel(Consumer(1)))
  114. self.connectPorts(self.models[0].outport, self.models[-1].inport)
  115. ## Optionally, we could also remove the Consumer(0) instance as follows:
  116. # self.removeSubModel(self.models[1])
  117. # Always returns False, as this is top-level
  118. return False
  119. This would give the following output (or similar, due to concurrency)::
  120. Got input 0 on model Consumer_0
  121. Got input 1 on model Consumer_0
  122. Got input 2 on model Consumer_0
  123. Got input 3 on model Consumer_0
  124. Got input 3 on model Consumer_1
  125. Got input 4 on model Consumer_0
  126. Got input 4 on model Consumer_1
  127. .. NOTE::
  128. As structural changes are not a common operation, their performance is not optimized extensively. To make matters worse, many structural optimizations done by PythonPDEVS will automatically be redone after each structural change.
  129. Passing state
  130. ^^^^^^^^^^^^^
  131. Finally, we come to the *state* parameter of the modelTransition call.
  132. In some cases, it will be necessary to pass arguments to the parent, to notify it of how the structure should change.
  133. This is useful if the child knows information that is vital to the change.
  134. Since Coupled DEVS models cannot hold state, and should not directly access the state of their children, we can use the *state* parameter for this.
  135. The *state* parameter is simply a dictionary object, which is passed between all the different *modelTransition* calls.
  136. Simply put, it is an object shared by all calls.
  137. For example, if we would want the structural change from before to create a new consumer every time, with an ID provided by the Generator, this can be done as follows (:download:`state_dsdevs.py <state_dsdevs.py>`)::
  138. class Generator(AtomicDEVS):
  139. ...
  140. def modelTransition(self, state):
  141. # We pass on the ID that we would like to create, which is equal to our counter
  142. state["ID"] = self.state
  143. # Always create a new element
  144. return True
  145. class Root(CoupledDEVS):
  146. ...
  147. def modelTransition(self, state):
  148. # We are notified, so are required to add a new model and link it
  149. # We can use the ID provided by the model below us
  150. self.models.append(self.addSubModel(Consumer(state["ID"])))
  151. self.connectPorts(self.models[0].outport, self.models[-1].inport)
  152. # Always returns False, as this is top-level
  153. return False
  154. This would then create the output (or similar, due to concurrency)::
  155. Got input 0 on model Consumer_0
  156. Got input 1 on model Consumer_0
  157. Got input 1 on model Consumer_1
  158. Got input 2 on model Consumer_0
  159. Got input 2 on model Consumer_1
  160. Got input 2 on model Consumer_2
  161. Got input 3 on model Consumer_0
  162. Got input 3 on model Consumer_1
  163. Got input 3 on model Consumer_2
  164. Got input 3 on model Consumer_3
  165. More complex example
  166. --------------------
  167. In the PyPDEVS distribution, a more complex example is provided.
  168. That example provides a model of two traffic lights, with a policeman who periodically changes the traffic light he is interrupting.