minimal.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. # Copyright 2015 Modelling, Simulation and Design Lab (MSDL) at
  2. # McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
  3. #
  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. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """
  16. The minimal PythonPDEVS simulation kernel. It only supports simple Parallel DEVS simulation, without any fancy configuration options.
  17. While it behaves exactly the same as the normal simulation kernel with default options, it is a lot faster due to skipping all features.
  18. """
  19. from collections import defaultdict
  20. # Uncomment this part to make a completely stand-alone simulation kernel
  21. class BaseDEVS(object):
  22. def __init__(self, name):
  23. self.name = name
  24. self.IPorts = []
  25. self.OPorts = []
  26. self.ports = []
  27. self.parent = None
  28. self.time_last = (0.0, 0)
  29. self.time_next = (0.0, 1)
  30. self.my_input = {}
  31. def addPort(self, name, is_input):
  32. name = name if name is not None else "port%s" % len(self.ports)
  33. port = Port(is_input=is_input, name=name)
  34. if is_input:
  35. self.IPorts.append(port)
  36. else:
  37. self.OPorts.append(port)
  38. port.port_id = len(self.ports)
  39. self.ports.append(port)
  40. port.host_DEVS = self
  41. return port
  42. def addInPort(self, name=None):
  43. return self.addPort(name, True)
  44. def addOutPort(self, name=None):
  45. return self.addPort(name, False)
  46. def getModelName(self):
  47. return self.name
  48. def getModelFullName(self):
  49. return self.full_name
  50. class AtomicDEVS(BaseDEVS):
  51. ID = 0
  52. def __init__(self, name):
  53. BaseDEVS.__init__(self, name)
  54. self.elapsed = 0.0
  55. self.state = None
  56. self.model_id = AtomicDEVS.ID
  57. AtomicDEVS.ID += 1
  58. def extTransition(self, inputs):
  59. return self.state
  60. def intTransition(self):
  61. return self.state
  62. def confTransition(self, inputs):
  63. self.state = self.intTransition()
  64. return self.extTransition(inputs)
  65. def timeAdvance(self):
  66. return float('inf')
  67. def outputFnc(self):
  68. return {}
  69. class CoupledDEVS(BaseDEVS):
  70. def __init__(self, name):
  71. BaseDEVS.__init__(self, name)
  72. self.component_set = []
  73. def addSubModel(self, model):
  74. model.parent = self
  75. self.component_set.append(model)
  76. return model
  77. def connectPorts(self, p1, p2):
  78. p1.outline.append(p2)
  79. p2.inline.append(p1)
  80. class RootDEVS(object):
  81. def __init__(self, components):
  82. from schedulers.schedulerHS import SchedulerHS as Scheduler
  83. self.component_set = components
  84. self.time_next = float('inf')
  85. self.scheduler = Scheduler(self.component_set, 1e-6, len(self.component_set))
  86. class Port(object):
  87. def __init__(self, is_input, name=None):
  88. self.inline = []
  89. self.outline = []
  90. self.host_DEVS = None
  91. self.name = name
  92. def getPortname(self):
  93. return self.name
  94. def directConnect(component_set):
  95. """
  96. Perform a trimmed down version of the direct connection algorithm.
  97. It does not support transfer functions, but all the rest is the same.
  98. :param component_set: the iterable to direct connect
  99. :returns: the direct connected component_set
  100. """
  101. new_list = []
  102. for i in component_set:
  103. if isinstance(i, CoupledDEVS):
  104. component_set.extend(i.component_set)
  105. else:
  106. # Found an atomic model
  107. new_list.append(i)
  108. component_set = new_list
  109. # All and only all atomic models are now direct children of this model
  110. for i in component_set:
  111. # Remap the output ports
  112. for outport in i.OPorts:
  113. # The new contents of the line
  114. outport.routing_outline = set()
  115. worklist = list(outport.outline)
  116. for outline in worklist:
  117. # If it is a coupled model, we must expand this model
  118. if isinstance(outline.host_DEVS, CoupledDEVS):
  119. worklist.extend(outline.outline)
  120. else:
  121. outport.routing_outline.add(outline)
  122. outport.routing_outline = list(outport.routing_outline)
  123. return component_set
  124. class Simulator(object):
  125. """
  126. Minimal simulation kernel, offering only setTerminationTime and simulate.
  127. Use this Simulator instead of the normal one to use the minimal kernel.
  128. While it has a lot less features, its performance is much higher.
  129. The polymorphic scheduler is also used by default.
  130. """
  131. def __init__(self, model):
  132. """
  133. Constructor
  134. :param model: the model to simulate
  135. """
  136. self.original_model = model
  137. if isinstance(model, CoupledDEVS):
  138. component_set = directConnect(model.component_set)
  139. ids = 0
  140. for m in component_set:
  141. m.time_last = (-m.elapsed, 0)
  142. m.time_next = (-m.elapsed + m.timeAdvance(), 1)
  143. m.model_id = ids
  144. ids += 1
  145. self.model = RootDEVS(component_set)
  146. elif isinstance(model, AtomicDEVS):
  147. for p in model.OPorts:
  148. p.routing_outline = []
  149. model.time_last = (-model.elapsed, 0)
  150. model.time_next = (model.time_last[0] + model.timeAdvance(), 1)
  151. model.model_id = 0
  152. self.model = RootDEVS([model])
  153. self.setTerminationTime(float('inf'))
  154. def setTerminationTime(self, time):
  155. """
  156. Set the termination time of the simulation.
  157. :param time: simulation time at which simulation should terminate
  158. """
  159. self.setTerminationCondition(lambda t, m: time <= t[0])
  160. def setTerminationCondition(self, function):
  161. """
  162. Set the termination condition of the simulation.
  163. :param function: termination condition to execute, taking the current simulated time and the model, returning a boolean (True to terminate)
  164. """
  165. self.termination_function = function
  166. def simulate(self):
  167. """
  168. Perform the simulation
  169. """
  170. scheduler = self.model.scheduler
  171. tn = scheduler.readFirst()
  172. while not self.termination_function(tn, self.original_model):
  173. # Generate outputs
  174. transitioning = defaultdict(int)
  175. for c in scheduler.getImminent(tn):
  176. transitioning[c] |= 1
  177. outbag = c.outputFnc()
  178. for outport in outbag:
  179. p = outbag[outport]
  180. for inport in outport.routing_outline:
  181. inport.host_DEVS.my_input.setdefault(inport, []).extend(p)
  182. transitioning[inport.host_DEVS] |= 2
  183. # Perform transitions
  184. for aDEVS, ttype in transitioning.iteritems():
  185. if ttype == 1:
  186. aDEVS.state = aDEVS.intTransition()
  187. elif ttype == 2:
  188. aDEVS.elapsed = tn[0] - aDEVS.time_last[0]
  189. aDEVS.state = aDEVS.extTransition(aDEVS.my_input)
  190. elif ttype == 3:
  191. aDEVS.elapsed = 0.
  192. aDEVS.state = aDEVS.confTransition(aDEVS.my_input)
  193. aDEVS.time_next = (tn[0] + aDEVS.timeAdvance(), 1 if tn[0] > aDEVS.time_last[0] else tn[1] + 1)
  194. aDEVS.time_last = tn
  195. aDEVS.my_input = {}
  196. # Do reschedules
  197. scheduler.massReschedule(transitioning)
  198. tn = scheduler.readFirst()
  199. return tn[0]
  200. def __getattr__(self, attr):
  201. """
  202. Wrapper to inform users that they are using the minimal kernel if they zant to do some unsupported configuration option.
  203. """
  204. if attr.startswith("set"):
  205. raise Exception("You are using the minimal simulation kernel, which does not support any configuration except for the termination time. Please switch to the normal simulation kernel to use this option.")
  206. else:
  207. raise AttributeError()