realtime.rst 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  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. Realtime simulation
  14. ===================
  15. Realtime simulation is closely linked to normal simulation, with the exception that simulation will not progress as fast as possible. The value returned by the time advance will be interpreted in seconds and the simulator will actually wait (not busy loop) until the requested time has passed. Several realtime backends are supported in PyPDEVS and are mentioned below.
  16. Example model
  17. -------------
  18. The example model will be something else than the *queue* from before, as this isn't really that interesting for realtime simulation. We will instead use the *trafficLight* model. It has a *trafficLight* that is either running autonomous or is in a manual mode. Normally, the traffic light will work autonomously, though it is possible to interrupt the traffic light and switch it to manual mode and back to autonomous again.
  19. This complete model is (:download:`trafficLightModel.py <trafficLightModel.py>`)::
  20. class TrafficLightMode:
  21. def __init__(self, current="red"):
  22. self.set(current)
  23. def set(self, value="red"):
  24. self.__colour=value
  25. def get(self):
  26. return self.__colour
  27. def __str__(self):
  28. return self.get()
  29. class TrafficLight(AtomicDEVS):
  30. def __init__(self, name):
  31. AtomicDEVS.__init__(self, name)
  32. self.state = TrafficLightMode("red")
  33. self.INTERRUPT = self.addInPort(name="INTERRUPT")
  34. self.OBSERVED = self.addOutPort(name="OBSERVED")
  35. def extTransition(self, inputs):
  36. input = inputs[self.INTERRUPT][0]
  37. if input == "toManual":
  38. if state == "manual":
  39. # staying in manual mode
  40. return TrafficLightMode("manual")
  41. if state in ("red", "green", "yellow"):
  42. return TrafficLightMode("manual")
  43. elif input == "toAutonomous":
  44. if state == "manual":
  45. return TrafficLightMode("red")
  46. raise DEVSException("Unkown input in TrafficLight")
  47. def intTransition(self):
  48. state = self.state.get()
  49. if state == "red":
  50. return TrafficLightMode("green")
  51. elif state == "green":
  52. return TrafficLightMode("yellow")
  53. elif state == "yellow":
  54. return TrafficLightMode("red")
  55. else:
  56. raise DEVSException("Unkown state in TrafficLight")
  57. def outputFnc(self):
  58. state = self.state.get()
  59. if state == "red":
  60. return {self.OBSERVED: ["grey"]}
  61. elif state == "green":
  62. return {self.OBSERVED: ["yellow"]}
  63. elif state == "yellow":
  64. return {self.OBSERVED: ["grey"]}
  65. else:
  66. raise DEVSException("Unknown state in TrafficLight")
  67. def timeAdvance(self):
  68. if state == "red":
  69. return 60
  70. elif state == "green":
  71. return 50
  72. elif state == "yellow":
  73. return 10
  74. elif state == "manual":
  75. return INFINITY
  76. else:
  77. raise DEVSException("Unknown state in TrafficLight")
  78. With our model being set up, we could run it as-fast-as-possible by starting it like::
  79. model = TrafficLight("trafficLight")
  80. sim = Simulator(model)
  81. sim.simulate()
  82. To make it run in real time, we only need to do some minor changes. First, we need to define on which port we want to put some external input. We can choose a way to address this port, but lets assume that we choose the same name as the name of the port. This gives us::
  83. refs = {"INTERRUPT": model.INTERRUPT}
  84. Now we only need to pass this mapping to the simulator, together with the choice for realtime simulation. This is done as follows::
  85. refs = {"INTERRUPT": model.INTERRUPT}
  86. sim.setRealTime(True)
  87. sim.setRealTimePorts(refs)
  88. That is all extra configuration that is required for real time simulation.
  89. As soon as the *simulate()* method is called, the simulation will be started as usual, though now several additional options are enabled. Specifically, the user can now input external data on the declared ports. This input should be of the form *portname data*.
  90. In our example, the model will respond to both *toManual* and *toAutonomous* and we chose *INTERRUPT* as portname in our mapping. So our model will react on the input *INTERRUPT toManual*. This input can then be given through the invocation of the *realtime_interrupt(string)* call as follows::
  91. sim.realtime_interrupt("INTERRUPT toManual")
  92. Malformed input will cause an exception and simulation will be halted.
  93. .. note:: All input that is injected will be passed to the model as a *string*. If the model is thus supposed to process integers, a string to integer processing step should happen in the model itself.
  94. Input files
  95. -----------
  96. PyPDEVS also supports the use of input files together with input provided at run time. The input file will be parsed at startup and should be of the form *time port value*, with time being the simulation time at which this input should be injected. Again, this input will always be interpreted as a string. If a syntax error is detected while reading through this file, the error will immediately be shown.
  97. .. note:: The file input closely resembles the usual prompt, though it is not possible to define a termination at a certain time by simply stating the time at the end. For this, you should use the termination time as provided by the standard interface.
  98. An example input file for our example could be::
  99. 10 INTERRUPT toManual
  100. 20 INTERRUPT toAutonomous
  101. 30 INTERRUPT toManual
  102. Backends
  103. --------
  104. Several backends are provided for the realtime simulation, each serving a different purpose. The default backend is the best for most people who just want to simulate in realtime. Other options are for when PyPDEVS is coupled to TkInter, or used in the context of a game loop system.
  105. The following backends are currently supported:
  106. * Python threads: the default, provides simple threading and doesn't require any other programs. Activated with *setRealTimePlatformThreads()*.
  107. * TkInter: uses Tk for all of its waiting and delays (using the Tk event list). Activated with *setRealTimePlatformTk()*.
  108. * Game loop: requires an external program to call the simulator after a certain delay. Activated with *setRealTimePlatformGameLoop()*.
  109. For each of these backends, an example is given on how to use and invoke it, using the traffic light model presented above.
  110. Python Threads
  111. ^^^^^^^^^^^^^^
  112. This is the simplest platform to use, and is used by default. After the invocation of *sim.simulate()*, simulation will happen in the background of the currently running application. The call to *sim.simulate()* will return immediately. Afterwards, users can do some other operations. Most interestingly, users can provide input to the running simulation by invoking the *realtime_interrupt(string)* method.
  113. Simulation runs as a daemon thread, so exiting the main thread will automatically terminate the simulation.
  114. .. warning:: Python threads can sometimes have a rather low granularity in CPython 2. So while we are simulating in soft realtime anyway, it is important to note that delays could potentially become significant.
  115. An example is given below (:download:`experiment_threads.py <experiment_threads.py>`)::
  116. from pypdevs.simulator import Simulator
  117. from trafficLightModel import *
  118. model = TrafficLight(name="trafficLight")
  119. refs = {"INTERRUPT": model.INTERRUPT}
  120. sim = Simulator(model)
  121. sim.setRealTime(True)
  122. sim.setRealTimeInputFile(None)
  123. sim.setRealTimePorts(refs)
  124. sim.setVerbose(None)
  125. sim.setRealTimePlatformThreads()
  126. sim.simulate()
  127. while 1:
  128. sim.realtime_interrupt(raw_input())
  129. In this example, users are presented with a prompt where they can inject events in the simulation, for example by typing *INTERRUPT toManual* during simulation. Sending an empty input (*i.e.*, malformed), simulation will also terminate.
  130. TkInter
  131. ^^^^^^^
  132. The TkInter event loop can be considered the most difficult one to master, as you will also need to interface with TkInter.
  133. Luckily, PythonPDEVS hides most of this complexity for you. You will, however, still need to define your GUI application and start PythonPDEVS. Upon configuration of PythonPDEVS, a reference to the root window needs to be passed to PythonPDEVS, such that it knows to which GUI to couple.
  134. Upon termination of the GUI, PythonPDEVS will automatically terminate simulation as well.
  135. The following example will create a simple TkInter GUI of a traffic light, visualizing the current state of the traffic light, and providing two buttons to send specific events. Despite the addition of TkInter code, the PythonPDEVS interface is still very similar.
  136. The experiment file is as follows (:download:`experiment_tk.py <experiment_tk.py>`)::
  137. from pypdevs.simulator import Simulator
  138. from Tkinter import *
  139. from trafficLightModel import *
  140. isBlinking = None
  141. model = TrafficLight(name="trafficLight")
  142. refs = {"INTERRUPT": model.INTERRUPT}
  143. root = Tk()
  144. sim = Simulator(model)
  145. sim.setRealTime(True)
  146. sim.setRealTimeInputFile(None)
  147. sim.setRealTimePorts(refs)
  148. sim.setVerbose(None)
  149. sim.setRealTimePlatformTk(root)
  150. def toManual():
  151. global isBlinking
  152. isBlinking = False
  153. sim.realtime_interrupt("INTERRUPT toManual")
  154. def toAutonomous():
  155. global isBlinking
  156. isBlinking = None
  157. sim.realtime_interrupt("INTERRUPT toAutonomous")
  158. size = 50
  159. xbase = 10
  160. ybase = 10
  161. frame = Frame(root)
  162. canvas = Canvas(frame)
  163. canvas.create_oval(xbase, ybase, xbase+size, ybase+size, fill="black", tags="red_light")
  164. canvas.create_oval(xbase, ybase+size, xbase+size, ybase+2*size, fill="black", tags="yellow_light")
  165. canvas.create_oval(xbase, ybase+2*size, xbase+size, ybase+3*size, fill="black", tags="green_light")
  166. canvas.pack()
  167. frame.pack()
  168. def updateLights():
  169. state = model.state.get()
  170. if state == "red":
  171. canvas.itemconfig("red_light", fill="red")
  172. canvas.itemconfig("yellow_light", fill="black")
  173. canvas.itemconfig("green_light", fill="black")
  174. elif state == "yellow":
  175. canvas.itemconfig("red_light", fill="black")
  176. canvas.itemconfig("yellow_light", fill="yellow")
  177. canvas.itemconfig("green_light", fill="black")
  178. elif state == "green":
  179. canvas.itemconfig("red_light", fill="black")
  180. canvas.itemconfig("yellow_light", fill="black")
  181. canvas.itemconfig("green_light", fill="green")
  182. elif state == "manual":
  183. canvas.itemconfig("red_light", fill="black")
  184. global isBlinking
  185. if isBlinking:
  186. canvas.itemconfig("yellow_light", fill="yellow")
  187. isBlinking = False
  188. else:
  189. canvas.itemconfig("yellow_light", fill="black")
  190. isBlinking = True
  191. canvas.itemconfig("green_light", fill="black")
  192. root.after(500, updateLights)
  193. b = Button(root, text="toManual", command=toManual)
  194. b.pack()
  195. c = Button(root, text="toAutonomous", command=toAutonomous)
  196. c.pack()
  197. root.after(100, updateLights)
  198. sim.simulate()
  199. root.mainloop()
  200. Game Loop
  201. ^^^^^^^^^
  202. This mechanism will not block the main thread and if the main thread stops, so will the simulation. The caller can, after the invocation of *simulate()*, give control to PythonPDEVS to process all outstanding events. This methods is called *realtime_loop_call()*. A simple game loop would thus look like::
  203. sim = Simulator(Model())
  204. sim.simulate()
  205. sim.setRealTimePlatformGameLoop()
  206. while (True):
  207. # Do rendering and such
  208. ...
  209. # Advance the state of the DEVS model, processing all input events
  210. sim.realtime_loop_call()
  211. The game loop mechanism is thus closely linked to the invoker. The calls to the *realtime_loop_call()* function and the initializer are the only concept of time that this mechanism uses. Newer versions of PythonPDEVS will automatically detect the number of Frames per Second (FPS), so there is no longer any need to do this manually.
  212. An example is presented below (:download:`experiment_loop.py <experiment_loop.py>`)::
  213. from pypdevs.simulator import Simulator
  214. from trafficLightModel import *
  215. model = TrafficLight(name="trafficLight")
  216. refs = {"INTERRUPT": model.INTERRUPT}
  217. sim = Simulator(model)
  218. sim.setRealTime(True)
  219. sim.setRealTimeInputFile(None)
  220. sim.setRealTimePorts(refs)
  221. sim.setVerbose(None)
  222. sim.setRealTimePlatformGameLoop()
  223. sim.simulate()
  224. import time
  225. while 1:
  226. before = time.time()
  227. sim.realtime_loop_call()
  228. time.sleep(0.1 - (before - time.time()))
  229. print("Current state: " + str(model.state.get()))
  230. It is important to remark here, that the time management (*i.e.*, invoking sleep and computing how long to sleep), is the responsibility of the invoking code, instead of PythonPDEVS. PythonPDEVS will simply poll for the current wall clock time when it is invoked, and progress simulated time up to that point in time (depending on the scale factor).