197 lines
6.7 KiB
Python
197 lines
6.7 KiB
Python
# Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
|
|
# McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""
|
|
Common utility functions used in PyPDEVS
|
|
"""
|
|
import pypdevs.middleware as middleware
|
|
from pypdevs.MPIRedirect import MPIRedirect
|
|
from collections import defaultdict
|
|
|
|
EPSILON = 1E-6
|
|
|
|
try:
|
|
import cPickle as pickle
|
|
except ImportError:
|
|
import pickle
|
|
|
|
def broadcastModel(data, proxies, allow_reinit, scheduler_locations):
|
|
"""
|
|
Broadcast the model to simulate to the provided proxies
|
|
|
|
:param data: data to be broadcasted to everywhere
|
|
:param proxies: iterable containing all proxies
|
|
:param allowReinit: should reinitialisation be allowed
|
|
"""
|
|
if (len(proxies) == 1) and not allow_reinit:
|
|
# Shortcut for local simulation with the garantee that no reinits will happen
|
|
proxies[0].sendModel(data, scheduler_locations[0])
|
|
return
|
|
# Otherwise, we always have to pickle
|
|
pickled_data = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
|
|
if len(proxies) == 1:
|
|
proxies[0].saveAndProcessModel(pickled_data, scheduler_locations[0])
|
|
else:
|
|
for i, proxy in enumerate(proxies[1:]):
|
|
# Prepare by setting up the broadcast receiving
|
|
proxy.prepare(scheduler_locations[i+1])
|
|
# Pickle the data ourselves, to avoid an MPI error when this goes wrong (as we can likely back-up from this error)
|
|
# Broadcast the model to everywhere
|
|
middleware.COMM_WORLD.bcast(pickled_data, root=0)
|
|
# Immediately wait for a barrier, this will be OK as soon as all models have initted their model
|
|
# Still send to ourselves, as we don't receive it from the broadcast
|
|
# Local calls, so no real overhead
|
|
proxies[0].sendModel(data, scheduler_locations[0])
|
|
proxies[0].setPickledData(pickled_data)
|
|
middleware.COMM_WORLD.barrier()
|
|
|
|
def broadcastCancel():
|
|
"""
|
|
Cancel the broadcast receiving in a nice way, to prevent MPI errors
|
|
"""
|
|
middleware.COMM_WORLD.bcast(None, root=0)
|
|
|
|
def toStr(inp):
|
|
"""
|
|
Return a string representation of the input, enclosed with ' characters
|
|
|
|
:param inp: the input value
|
|
:returns: string -- input value, enclosed by ' characters
|
|
"""
|
|
return "'%s'" % inp
|
|
|
|
def addDict(destination, source):
|
|
"""
|
|
Adds 2 dicts together in the first dictionary
|
|
|
|
:param destination: the destination dictionary to merge the source into
|
|
:param source: the dictionary to merge in
|
|
|
|
.. note:: the *destination* parameter will be modified and no return value is provided. The *source* parameter is not modified.
|
|
"""
|
|
for i in source:
|
|
destination[i] = destination.get(i, 0) + source[i]
|
|
|
|
def allZeroDict(source):
|
|
"""
|
|
Checks whether or not a dictionary contains only 0 items
|
|
|
|
:param source: a dictionary to test
|
|
:returns: bool -- whether or not all entries in the dictionary are equal to zero
|
|
"""
|
|
for i in source.values():
|
|
if i != 0:
|
|
return False
|
|
return True
|
|
|
|
def runTraceAtController(server, uid, model, args):
|
|
"""
|
|
Run a trace command on our version that is running at the constroller
|
|
|
|
:param server: the server to ask the proxy from
|
|
:param uid: the UID of the tracer (identical throughout the simulation)
|
|
:param model: the model that transitions
|
|
:param args: the arguments for the trace function
|
|
"""
|
|
to_run = easyCommand("self.tracers.getByID(%i).trace" % uid,
|
|
args).replace("\n", "\\n")
|
|
if server.getName() == 0:
|
|
server.getProxy(0).delayedAction(model.time_last, model.model_id, to_run)
|
|
else:
|
|
server.queueMessage(model.time_last, model.model_id, to_run)
|
|
|
|
def easyCommand(function, args):
|
|
"""
|
|
Easy wrapper to create a string representation of function calls
|
|
|
|
:param function: the function should be called
|
|
:param args: list of all the arguments for the function
|
|
:returns: str -- string representation to be passed to *exec*
|
|
"""
|
|
text = str(function) + "("
|
|
for i in range(len(args)):
|
|
if i != 0:
|
|
text += ", "
|
|
if isinstance(args[i], str):
|
|
args[i] = '"%s"' % args[i][1:-1].replace('"', "\\\"").replace("'", "\\'")
|
|
text += str(args[i])
|
|
text += ")"
|
|
return text
|
|
|
|
class DEVSException(Exception):
|
|
"""
|
|
DEVS specific exceptions
|
|
"""
|
|
def __init__(self, message="not specified in source"):
|
|
"""
|
|
Constructor
|
|
|
|
:param message: error message to print
|
|
"""
|
|
Exception.__init__(self, message)
|
|
|
|
def __str__(self):
|
|
"""
|
|
String representation of the exception
|
|
"""
|
|
if hasattr(self, "message"):
|
|
return "DEVS Exception: " + str(self.message)
|
|
return "DEVS Exception: " + str(self.args[0])
|
|
|
|
class QuickStopException(Exception):
|
|
"""
|
|
An exception specifically to stop the simulation and perform a relocation ASAP
|
|
"""
|
|
def __init__(self):
|
|
Exception.__init__(self, "(none)")
|
|
|
|
def __str__(self):
|
|
"""
|
|
Should be unused
|
|
"""
|
|
return "Quick Stop Exception"
|
|
|
|
def saveLocations(filename, model_locations, model_ids):
|
|
"""
|
|
Save an allocation specified by the parameter.
|
|
|
|
:param filename: filename to save the allocation to
|
|
:param modellocations: allocation to save to file
|
|
:param model_ids: all model_ids to model mappings
|
|
"""
|
|
# Save the locations
|
|
f = open(filename, 'w')
|
|
for model_id in model_locations:
|
|
# Format:
|
|
# model_id location fullname
|
|
f.write("%s %s %s\n" % (model_id,
|
|
model_locations[model_id],
|
|
model_ids[model_id].getModelFullName()))
|
|
f.close()
|
|
|
|
def constructGraph(models):
|
|
"""
|
|
Construct a graph from the model, containing the weight (= number of messages) on a connection
|
|
between two components.
|
|
|
|
:param models: the root model to use for graph construction
|
|
:returns: dict -- all from-to edges with their number of events
|
|
"""
|
|
edges = defaultdict(lambda: defaultdict(int))
|
|
for model in models.component_set:
|
|
for outport in model.OPorts:
|
|
for inport in outport.outline:
|
|
edges[outport.host_DEVS][inport.host_DEVS] += outport.msg_count
|
|
return edges
|