util.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. # Copyright 2014 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. Common utility functions used in PyPDEVS
  17. """
  18. import pypdevs.middleware as middleware
  19. from pypdevs.MPIRedirect import MPIRedirect
  20. from collections import defaultdict
  21. EPSILON = 1E-6
  22. try:
  23. import cPickle as pickle
  24. except ImportError:
  25. import pickle
  26. def broadcastModel(data, proxies, allow_reinit, scheduler_locations):
  27. """
  28. Broadcast the model to simulate to the provided proxies
  29. :param data: data to be broadcasted to everywhere
  30. :param proxies: iterable containing all proxies
  31. :param allowReinit: should reinitialisation be allowed
  32. """
  33. if (len(proxies) == 1) and not allow_reinit:
  34. # Shortcut for local simulation with the garantee that no reinits will happen
  35. proxies[0].sendModel(data, scheduler_locations[0])
  36. return
  37. # Otherwise, we always have to pickle
  38. pickled_data = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
  39. if len(proxies) == 1:
  40. proxies[0].saveAndProcessModel(pickled_data, scheduler_locations[0])
  41. else:
  42. for i, proxy in enumerate(proxies[1:]):
  43. # Prepare by setting up the broadcast receiving
  44. proxy.prepare(scheduler_locations[i+1])
  45. # Pickle the data ourselves, to avoid an MPI error when this goes wrong (as we can likely back-up from this error)
  46. # Broadcast the model to everywhere
  47. middleware.COMM_WORLD.bcast(pickled_data, root=0)
  48. # Immediately wait for a barrier, this will be OK as soon as all models have initted their model
  49. # Still send to ourselves, as we don't receive it from the broadcast
  50. # Local calls, so no real overhead
  51. proxies[0].sendModel(data, scheduler_locations[0])
  52. proxies[0].setPickledData(pickled_data)
  53. middleware.COMM_WORLD.barrier()
  54. def broadcastCancel():
  55. """
  56. Cancel the broadcast receiving in a nice way, to prevent MPI errors
  57. """
  58. middleware.COMM_WORLD.bcast(None, root=0)
  59. def toStr(inp):
  60. """
  61. Return a string representation of the input, enclosed with ' characters
  62. :param inp: the input value
  63. :returns: string -- input value, enclosed by ' characters
  64. """
  65. return "'%s'" % inp
  66. def addDict(destination, source):
  67. """
  68. Adds 2 dicts together in the first dictionary
  69. :param destination: the destination dictionary to merge the source into
  70. :param source: the dictionary to merge in
  71. .. note:: the *destination* parameter will be modified and no return value is provided. The *source* parameter is not modified.
  72. """
  73. for i in source:
  74. destination[i] = destination.get(i, 0) + source[i]
  75. def allZeroDict(source):
  76. """
  77. Checks whether or not a dictionary contains only 0 items
  78. :param source: a dictionary to test
  79. :returns: bool -- whether or not all entries in the dictionary are equal to zero
  80. """
  81. for i in source.values():
  82. if i != 0:
  83. return False
  84. return True
  85. def runTraceAtController(server, uid, model, args):
  86. """
  87. Run a trace command on our version that is running at the constroller
  88. :param server: the server to ask the proxy from
  89. :param uid: the UID of the tracer (identical throughout the simulation)
  90. :param model: the model that transitions
  91. :param args: the arguments for the trace function
  92. """
  93. to_run = easyCommand("self.tracers.getByID(%i).trace" % uid,
  94. args).replace("\n", "\\n")
  95. if server.getName() == 0:
  96. server.getProxy(0).delayedAction(model.time_last, model.model_id, to_run)
  97. else:
  98. server.queueMessage(model.time_last, model.model_id, to_run)
  99. def easyCommand(function, args):
  100. """
  101. Easy wrapper to create a string representation of function calls
  102. :param function: the function should be called
  103. :param args: list of all the arguments for the function
  104. :returns: str -- string representation to be passed to *exec*
  105. """
  106. text = str(function) + "("
  107. for i in range(len(args)):
  108. if i != 0:
  109. text += ", "
  110. if isinstance(args[i], str):
  111. args[i] = '"%s"' % args[i][1:-1].replace('"', "\\\"").replace("'", "\\'")
  112. text += str(args[i])
  113. text += ")"
  114. return text
  115. class DEVSException(Exception):
  116. """
  117. DEVS specific exceptions
  118. """
  119. def __init__(self, message="not specified in source"):
  120. """
  121. Constructor
  122. :param message: error message to print
  123. """
  124. Exception.__init__(self, message)
  125. def __str__(self):
  126. """
  127. String representation of the exception
  128. """
  129. if hasattr(self, "message"):
  130. return "DEVS Exception: " + str(self.message)
  131. return "DEVS Exception: " + str(self.args[0])
  132. class QuickStopException(Exception):
  133. """
  134. An exception specifically to stop the simulation and perform a relocation ASAP
  135. """
  136. def __init__(self):
  137. Exception.__init__(self, "(none)")
  138. def __str__(self):
  139. """
  140. Should be unused
  141. """
  142. return "Quick Stop Exception"
  143. def saveLocations(filename, model_locations, model_ids):
  144. """
  145. Save an allocation specified by the parameter.
  146. :param filename: filename to save the allocation to
  147. :param modellocations: allocation to save to file
  148. :param model_ids: all model_ids to model mappings
  149. """
  150. # Save the locations
  151. f = open(filename, 'w')
  152. for model_id in model_locations:
  153. # Format:
  154. # model_id location fullname
  155. f.write("%s %s %s\n" % (model_id,
  156. model_locations[model_id],
  157. model_ids[model_id].getModelFullName()))
  158. f.close()
  159. def constructGraph(models):
  160. """
  161. Construct a graph from the model, containing the weight (= number of messages) on a connection
  162. between two components.
  163. :param models: the root model to use for graph construction
  164. :returns: dict -- all from-to edges with their number of events
  165. """
  166. edges = defaultdict(lambda: defaultdict(int))
  167. for model in models.component_set:
  168. for outport in model.OPorts:
  169. for inport in outport.outline:
  170. edges[outport.host_DEVS][inport.host_DEVS] += outport.msg_count
  171. return edges