예제 #1
0
    def register_handlers(subclass, handlers):
        """ Register a `SimulationObject`'s event handler methods.

        The simulation engine vectors execution of a simulation message to the message's registered
        event handler method. The priority of message execution in an event containing multiple messages
        is determined by the sequence of tuples in `handlers`.
        These relationships are stored in an `ApplicationSimulationObject`'s
        `metadata.event_handlers_dict`.
        Each call to `register_handlers` re-initializes all event handler methods.

        Args:
            subclass (:obj:`SimulationObject`): a subclass of `SimulationObject` that is registering
                the relationships between the simulation messages it receives and the methods that
                handle them
            handlers (:obj:`list` of (`SimulationMessage`, `function`)): a list of tuples, indicating
                which method should handle which type of `SimulationMessage` in `subclass`; ordered in
                decreasing priority for handling simulation message types

        Raises:
            :obj:`SimulatorError`: if a `SimulationMessage` appears repeatedly in `handlers`, or
                if a method in `handlers` is not callable
        """
        for message_type, handler in handlers:
            if message_type in subclass.metadata.event_handlers_dict:
                raise SimulatorError(
                    "message type '{}' appears repeatedly".format(
                        most_qual_cls_name(message_type)))
            if not callable(handler):
                raise SimulatorError(
                    "handler '{}' must be callable".format(handler))
            subclass.metadata.event_handlers_dict[message_type] = handler

        for index, (message_type, _) in enumerate(handlers):
            subclass.metadata.event_handler_priorities[message_type] = index
예제 #2
0
    def send_event(self, delay, receiving_object, message, copy=False):
        """ Send a simulation event message, specifing the event time as a delay.

        Args:
            delay (:obj:`float`): the simulation delay at which `receiving_object` should execute the event
            receiving_object (:obj:`SimulationObject`): the simulation object that will receive and
                execute the event
            message (:obj:`SimulationMessage`): the simulation message which will be carried by the event
            copy (:obj:`bool`, optional): if `True`, copy the message before adding it to the event;
                set `False` by default to optimize performance; set `True` as a safety measure to avoid
                unexpected changes to shared objects

        Raises:
            :obj:`SimulatorError`: if `delay` < 0 or `delay` is NaN, or
                if the sending object type is not registered to send messages with the type of `message`, or
                if the receiving simulation object type is not registered to receive messages with
                the type of `message`
        """
        if math.isnan(delay):
            raise SimulatorError("delay is 'NaN'")
        if delay < 0:
            raise SimulatorError("delay < 0 in send_event(): {}".format(
                str(delay)))
        self.send_event_absolute(delay + self.time,
                                 receiving_object,
                                 message,
                                 copy=copy)
예제 #3
0
    def _get_sim_config(time_max=None, sim_config=None, config_dict=None):
        """ External simulate interface

        Legal combinations of the three parameters:

        1. Just `time_max`
        2. Just `sim_config`, which will contain an entry for `time_max`
        3. Just `config_dict`, which must contain an entry for `time_max`

        Other combinations are illegal.

        Args:
            time_max (:obj:`float`, optional): the time of the end of the simulation
            sim_config (:obj:`SimulationConfig`, optional): the simulation run's configuration
            config_dict (:obj:`dict`, optional): a dictionary with keys chosen from the field names
                in :obj:`SimulationConfig`; note that `config_dict` is not a `kwargs` argument

        Returns:
            :obj:`SimulationConfig`: a validated simulation configuration

        Raises:
            :obj:`SimulatorError`: if no arguments are provided, or multiple arguments are provided,
                or `time_max` is missing from `config_dict`
        """
        num_args = 0
        if time_max is not None:
            num_args += 1
        if sim_config is not None:
            num_args += 1
        if config_dict:
            num_args += 1
        if num_args == 0:
            raise SimulatorError('time_max, sim_config, or config_dict must be provided')
        if 1 < num_args:
            raise SimulatorError('at most 1 of time_max, sim_config, or config_dict may be provided')

        # catch common error generated when sim_config= is not used by SimulationEngine.simulate(sim_config)
        if isinstance(time_max, SimulationConfig):
            raise SimulatorError(f"sim_config is not provided, sim_config= is probably needed")

        # initialize sim_config if it is not provided
        if sim_config is None:
            if time_max is not None:
                sim_config = SimulationConfig(time_max)
            else:   # config_dict must be initialized
                if 'time_max' not in config_dict:
                    raise SimulatorError('time_max must be provided in config_dict')
                sim_config = SimulationConfig(**config_dict)

        sim_config.validate()
        return sim_config
예제 #4
0
    def schedule_event(self, send_time, receive_time, sending_object,
                       receiving_object, message):
        """ Create an event and insert in this event queue, scheduled to execute at `receive_time`

        Simulation object `X` can sends an event to simulation object `Y` by invoking
            `X.send_event(receive_delay, Y, message)`.

        Args:
            send_time (:obj:`float`): the simulation time at which the event was generated (sent)
            receive_time (:obj:`float`): the simulation time at which the `receiving_object` will
                execute the event
            sending_object (:obj:`SimulationObject`): the object sending the event
            receiving_object (:obj:`SimulationObject`): the object that will receive the event; when
                the simulation is parallelized `sending_object` and `receiving_object` will need
                to be global identifiers.
            message (:obj:`SimulationMessage`): a `SimulationMessage` carried by the event; its type
                provides the simulation application's type for an `Event`; it may also carry a payload
                for the `Event` in its attributes.

        Raises:
            :obj:`SimulatorError`: if `receive_time` < `send_time`, or `receive_time` or `send_time` is NaN
        """

        if math.isnan(send_time) or math.isnan(receive_time):
            raise SimulatorError(
                "send_time ({}) and/or receive_time ({}) is NaN".format(
                    receive_time, send_time))

        # Ensure that send_time <= receive_time.
        # Events with send_time == receive_time can cause loops, but the application programmer
        # is responsible for avoiding them.
        if receive_time < send_time:
            raise SimulatorError(
                "receive_time < send_time in schedule_event(): {} < {}".format(
                    receive_time, send_time))

        if not isinstance(message, SimulationMessage):
            raise SimulatorError(
                "message should be an instance of {} but is a '{}'".format(
                    SimulationMessage.__name__,
                    type(message).__name__))

        event = Event(send_time, receive_time, sending_object,
                      receiving_object, message)
        # As per David Jefferson's thinking, the event queue is ordered by data provided by the
        # simulation application, in particular the tuple (event time, receiving object name).
        # See the comparison operators for Event. This achieves deterministic and reproducible
        # simulations.
        heapq.heappush(self.event_heap, event)
예제 #5
0
    def _SimulationEngine__handle_event_list(self, event_list):
        """ Handle a list of simulation events, which may contain multiple concurrent events

        This method's special name ensures that it cannot be overridden, and can only be called
        from `SimulationEngine`.

        Attributes:
            event_list (:obj:`list` of :obj:`Event`): the `Event` message(s) in the simulation event

        Raises:
            :obj:`SimulatorError`: if a message in `event_list` has an invalid type
        """
        self.num_events += 1

        if self.LOG_EVENTS:
            # write events to a plot log
            # plot logging is controlled by configuration files pointed to by config_constants and by env vars
            for event in event_list:
                self.fast_plot_file_logger.fast_log(str(event),
                                                    sim_time=self.time)

        # iterate through event_list, branching to handler
        for event in event_list:
            try:
                handler = self.__class__.metadata.event_handlers_dict[
                    event.message.__class__]
                handler(self, event)
            except KeyError:  # pragma: no cover
                # unreachable because of check that receiving sim
                # obj type is registered to receive the message type
                raise SimulatorError(
                    "No handler registered for Simulation message type: '{}'".
                    format(event.message.__class__.__name__))
예제 #6
0
 def __init__(self, name, period, start_time=0.):
     if period <= 0:
         raise SimulatorError(
             "period must be positive, but is {}".format(period))
     self.period = period
     self.start_time = start_time
     self.num_periods = 0
     super().__init__(name, start_time=start_time)
예제 #7
0
    def get_file_name(self, time):
        """ Get file name for checkpoint at time `time`

        Args:
            time (:obj:`float`): time

        Returns:
            :obj:`str`: file name for checkpoint at time `time`
        """
        filename_time = f'{time:.{MAX_TIME_PRECISION}f}'
        if not math.isclose(float(filename_time), time):
            raise SimulatorError(f"filename time {filename_time} is not close to time {time}")
        return os.path.join(self.dir_path, f'{filename_time}.pickle')
예제 #8
0
    def validate(self):
        """ Validate a `SimulationConfig` instance

        Validation tests that involve multiple fields must be made in this method. Call it after the
        `SimulationConfig` instance is in a consistent state.

        Returns:
            :obj:`None`: if no error is found

        Raises:
            :obj:`SimulatorError`: if `self` fails validation
        """

        self.validate_individual_fields()

        # other validation
        if self.time_max <= self.time_init:
            raise SimulatorError(f'time_max ({self.time_max}) must be greater than time_init ({self.time_init})')

        if self.profile and 0 < self.object_memory_change_interval:
            raise SimulatorError('profile and object_memory_change_interval cannot both be active, '
                                 'as the combination slows DE Sim dramatically')
예제 #9
0
    def get_object(self, simulation_object_name):
        """ Get a simulation object instance

        Args:
            simulation_object_name (:obj:`str`): get a simulation object instance that is
                part of this simulation

        Raises:
            :obj:`SimulatorError`: if the simulation object is not part of this simulation
        """
        if simulation_object_name not in self.simulation_objects:
            raise SimulatorError("cannot get simulation object '{}'".format(simulation_object_name))
        return self.simulation_objects[simulation_object_name]
예제 #10
0
    def initialize(self):
        """ Initialize a simulation

        Call `send_initial_events()` in each simulation object that has been loaded.

        Raises:
            :obj:`SimulatorError`:  if the simulation has already been initialized
        """
        if self.__initialized:
            raise SimulatorError('Simulation has already been initialized')
        for sim_obj in self.simulation_objects.values():
            sim_obj.send_initial_events()
        self.event_counts.clear()
        self.__initialized = True
예제 #11
0
    def validate_individual_fields(self):
        """ Validate constraints other than types in individual fields in a `SimulationConfig` instance

        Returns:
            :obj:`None`: if no error is found

        Raises:
            :obj:`SimulatorError`: if an attribute of `self` fails validation
        """

        # make sure stop_condition is callable
        if self.stop_condition is not None and not callable(self.stop_condition):
            raise SimulatorError(f"stop_condition ('{self.stop_condition}') must be a function")

        # validate output_dir and convert to absolute path
        if self.output_dir is not None:
            absolute_output_dir = os.path.abspath(os.path.expanduser(self.output_dir))

            if os.path.exists(absolute_output_dir):
                # raise error if absolute_output_dir exists and is not a dir
                if not os.path.isdir(absolute_output_dir):
                    raise SimulatorError(f"output_dir '{absolute_output_dir}' must be a directory")

                # raise error if absolute_output_dir is not empty
                if os.listdir(absolute_output_dir):
                    raise SimulatorError(f"output_dir '{absolute_output_dir}' is not empty")

            # if absolute_output_dir does not exist, make it
            if not os.path.exists(absolute_output_dir):
                os.makedirs(absolute_output_dir)

            self.output_dir = absolute_output_dir

        # make sure object_memory_change_interval is non-negative
        if self.object_memory_change_interval < 0:
            raise SimulatorError(f"object_memory_change_interval ('{self.object_memory_change_interval}') "
                                 "must be non-negative")
예제 #12
0
    def add_object(self, simulation_object):
        """ Add a simulation object instance to this simulation

        Args:
            simulation_object (:obj:`SimulationObject`): a simulation object instance that
                will be used by this simulation

        Raises:
            :obj:`SimulatorError`: if the simulation object's name is already in use
        """
        name = simulation_object.name
        if name in self.simulation_objects:
            raise SimulatorError("cannot add simulation object '{}', name already in use".format(name))
        simulation_object.add(self)
        self.simulation_objects[name] = simulation_object
예제 #13
0
    def assign_decreasing_priority(cls, aso_classes):
        """ Assign decreasing simultaneous execution priorities for a list of simulation object classes

        Args:
            aso_classes (:obj:`iterator` of :obj:`ApplicationSimulationObject`): an iterator over
                simulation object classes

        Raises:
            :obj:`SimulatorError`: if too many :obj:`ApplicationSimulationObject`\ s are given
        """
        if cls.LOW < len(aso_classes):
            raise SimulatorError(
                f"Too many ApplicationSimulationObjects: {len(aso_classes)}")
        for index, aso_class in enumerate(aso_classes):
            aso_class.set_class_priority(SimObjClassPriority(index + 1))
예제 #14
0
    def add(self, simulator):
        """ Add this object to a simulation.

        Args:
            simulator (:obj:`SimulationEngine`): the simulator that will use this `SimulationObject`

        Raises:
            :obj:`SimulatorError`: if this `SimulationObject` is already registered with a simulator
        """
        if self.simulator is None:
            # TODO(Arthur): reference to the simulator is problematic because it means simulator can't be GC'ed
            self.simulator = simulator
            return
        raise SimulatorError(
            "SimulationObject '{}' is already part of a simulator".format(
                self.name))
예제 #15
0
    def delete_object(self, simulation_object):
        """ Delete a simulation object instance from this simulation

        Args:
            simulation_object (:obj:`SimulationObject`): a simulation object instance that is
                part of this simulation

        Raises:
            :obj:`SimulatorError`: if the simulation object is not part of this simulation
        """
        # TODO(Arthur): is this an operation that makes sense to support? if not, remove it; if yes,
        # remove all of this object's state from simulator, and test it properly
        name = simulation_object.name
        if name not in self.simulation_objects:
            raise SimulatorError("cannot delete simulation object '{}', has not been added".format(name))
        simulation_object.delete()
        del self.simulation_objects[name]
예제 #16
0
    def list_checkpoints(self, error_if_empty=True):
        """ Get sorted list of times of saved checkpoints in checkpoint directory `self.dir_path`

        To enhance performance the list of times is cached in attribute `all_checkpoints` and
        reloaded if the directory is updated.

        Args:
            error_if_empty (:obj:`bool`, optional): if set, report an error if no checkpoints found

        Returns:
            :obj:`list`: sorted list of times of saved checkpoints

        Raises:
            :obj:`SimulatorError`: if `dirname` doesn't contain any checkpoints
        """
        # reload all_checkpoints if they have not been obtained
        # or self.dir_path has been modified since all_checkpoints was last obtained
        if self.all_checkpoints is None or self.last_dir_mod < os.stat(self.dir_path).st_mtime_ns:
            self.last_dir_mod = os.stat(self.dir_path).st_mtime_ns

            # find checkpoint times
            checkpoint_times = []
            pattern = r'^(\d+\.\d{' + f'{MAX_TIME_PRECISION},{MAX_TIME_PRECISION}' + r'})\.pickle$'
            for file_name in os.listdir(self.dir_path):
                match = re.match(pattern, file_name)
                if os.path.isfile(os.path.join(self.dir_path, file_name)) and match:
                    checkpoint_times.append(float(match.group(1)))

            # sort by time
            checkpoint_times.sort()

            self.all_checkpoints = checkpoint_times

        # error if no checkpoints found
        if error_if_empty and not self.all_checkpoints:
            raise SimulatorError("no checkpoints found in '{}'".format(self.dir_path))

        # return list of checkpoint times
        return self.all_checkpoints
예제 #17
0
 def make_error(self):
     raise SimulatorError(self.test_msg)
예제 #18
0
 def __setattr__(self, name, value):
     """ Validate an attribute when it is changed """
     try:
         super().__setattr__(name, value)
     except TypeError as e:
         raise SimulatorError(e)
예제 #19
0
    def __new__(cls, clsname, superclasses, namespace):
        """
        Args:
            cls (:obj:`class`): this class
            clsname (:obj:`str`): name of the :class:`SimulationObject` subclass being created
            superclasses (:obj:`tuple`): tuple of superclasses
            namespace (:obj:`dict`): namespace of subclass of `ApplicationSimulationObject` being created

        Returns:
            :obj:`SimulationObject`: a new instance of a subclass of `SimulationObject`

        Raises:
            :obj:`SimulatorError`: if class priority is not an `int`,
                or if the :obj:`ApplicationSimulationObject` doesn't define `messages_sent` or `event_handlers`,
                or if handlers in `event_handlers` don't refer to methods in the
                    :obj:`ApplicationSimulationObject`,
                or if `event_handlers` isn't an iterator over pairs,
                or if a message type sent isn't a subclass of SimulationMessage,
                or if `messages_sent` isn't an iterator over pairs.
        """
        # Short circuit when ApplicationSimulationObject is defined
        if clsname == 'ApplicationSimulationObject':
            return super().__new__(cls, clsname, superclasses, namespace)

        EVENT_HANDLERS = cls.EVENT_HANDLERS
        MESSAGES_SENT = cls.MESSAGES_SENT
        CLASS_PRIORITY = cls.CLASS_PRIORITY

        new_application_simulation_obj_subclass = super().__new__(
            cls, clsname, superclasses, namespace)
        new_application_simulation_obj_subclass.metadata = ApplicationSimulationObjectMetadata(
        )

        # use 'abstract' to indicate that an ApplicationSimulationObject should not be instantiated
        if 'abstract' in namespace and namespace['abstract'] is True:
            return new_application_simulation_obj_subclass

        # approach:
        #     look for EVENT_HANDLERS & MESSAGES_SENT attributes:
        #         use declaration in namespace, if found
        #         use first definition in metadata of a superclass, if found
        #         if not found, issue warning and return or raise exception
        #
        #     found:
        #         if EVENT_HANDLERS found, check types, and use register_handlers() to set
        #         if MESSAGES_SENT found, check types, and use register_sent_messages() to set

        event_handlers = None
        if EVENT_HANDLERS in namespace:
            event_handlers = namespace[EVENT_HANDLERS]

        messages_sent = None
        if MESSAGES_SENT in namespace:
            messages_sent = namespace[MESSAGES_SENT]

        class_priority = None
        if CLASS_PRIORITY in namespace:
            class_priority = namespace[CLASS_PRIORITY]
            if not isinstance(class_priority, int):
                raise SimulatorError(
                    f"ApplicationSimulationObject '{clsname}' {CLASS_PRIORITY} must be "
                    f"an int, but '{class_priority}' is a {type(class_priority).__name__}"
                )

        for superclass in superclasses:
            if event_handlers is None:
                if hasattr(superclass, 'metadata') and hasattr(
                        superclass.metadata, 'event_handlers_dict'):
                    # convert dict in superclass to list of tuple pairs
                    event_handlers = [(k, v) for k, v in getattr(
                        superclass.metadata, 'event_handlers_dict').items()]

        for superclass in superclasses:
            if messages_sent is None:
                if hasattr(superclass, 'metadata') and hasattr(
                        superclass.metadata, 'message_types_sent'):
                    messages_sent = getattr(superclass.metadata,
                                            'message_types_sent')

        for superclass in superclasses:
            if class_priority is None:
                if hasattr(superclass, 'metadata') and hasattr(
                        superclass.metadata, CLASS_PRIORITY):
                    class_priority = getattr(superclass.metadata,
                                             CLASS_PRIORITY)
        if class_priority is not None:
            setattr(new_application_simulation_obj_subclass.metadata,
                    CLASS_PRIORITY, class_priority)

        # either messages_sent or event_handlers must contain values
        if (not event_handlers and not messages_sent):
            raise SimulatorError(
                "ApplicationSimulationObject '{}' definition must inherit or provide a "
                "non-empty '{}' or '{}'.".format(clsname, EVENT_HANDLERS,
                                                 MESSAGES_SENT))
        elif not event_handlers:
            warnings.warn(
                "ApplicationSimulationObject '{}' definition does not inherit or provide a "
                "non-empty '{}'.".format(clsname, EVENT_HANDLERS))
        elif not messages_sent:
            warnings.warn(
                "ApplicationSimulationObject '{}' definition does not inherit or provide a "
                "non-empty '{}'.".format(clsname, MESSAGES_SENT))

        if event_handlers:
            try:
                resolved_handers = []
                errors = []
                for msg_type, handler in event_handlers:
                    # handler may be the string name of a method
                    if isinstance(handler, str):
                        try:
                            handler = namespace[handler]
                        except Exception:
                            errors.append(
                                "ApplicationSimulationObject '{}' definition must define "
                                "'{}'.".format(clsname, handler))
                    if not isinstance(handler, str) and not callable(handler):
                        errors.append(
                            "handler '{}' must be callable".format(handler))
                    if not issubclass(msg_type, SimulationMessage):
                        errors.append(
                            "'{}' must be a subclass of SimulationMessage".
                            format(msg_type.__name__))
                    resolved_handers.append((msg_type, handler))

                if errors:
                    raise SimulatorError("\n".join(errors))
                new_application_simulation_obj_subclass.register_handlers(
                    new_application_simulation_obj_subclass, resolved_handers)
            except (TypeError, ValueError):
                raise SimulatorError(
                    "ApplicationSimulationObject '{}': '{}' must iterate over pairs"
                    .format(clsname, EVENT_HANDLERS))

        if messages_sent:
            try:
                errors = []
                for msg_type in messages_sent:
                    if not issubclass(msg_type, SimulationMessage):
                        errors.append(
                            "'{}' in '{}' must be a subclass of SimulationMessage"
                            .format(msg_type.__name__, MESSAGES_SENT))
                if errors:
                    raise SimulatorError("\n".join(errors))
                new_application_simulation_obj_subclass.register_sent_messages(
                    new_application_simulation_obj_subclass, messages_sent)
            except (TypeError, ValueError):
                raise SimulatorError(
                    "ApplicationSimulationObject '{}': '{}' must iterate over "
                    "SimulationMessages".format(clsname, MESSAGES_SENT))

        # return the class to instantiate it
        return new_application_simulation_obj_subclass
예제 #20
0
    def _simulate(self):
        """ Run the simulation

        Returns:
            :obj:`int`: the number of times a simulation object executes `_handle_event()`. This may
                be smaller than the number of events sent, because simultaneous events at one
                simulation object are handled together.

        Raises:
            :obj:`SimulatorError`: if the simulation has not been initialized, or has no objects,
                or has no initial events, or attempts to start before the start time in `time_init`,
                or attempts to execute an event that violates non-decreasing time order
        """
        if not self.__initialized:
            raise SimulatorError("Simulation has not been initialized")

        if not len(self.get_objects()):
            raise SimulatorError("Simulation has no objects")

        if self.event_queue.empty():
            raise SimulatorError("Simulation has no initial events")

        _object_mem_tracking = False
        if 0 < self.sim_config.object_memory_change_interval:
            _object_mem_tracking = True
            # don't import tracker unless it's being used
            from pympler import tracker
            self.mem_tracker = tracker.SummaryTracker()

        # set simulation time to `time_init`
        self.time = self.sim_config.time_init

        # error if first event occurs before time_init
        next_time = self.event_queue.next_event_time()
        if next_time < self.sim_config.time_init:
            raise SimulatorError(f"Time of first event ({next_time}) is earlier than the start time "
                                 f"({self.sim_config.time_init})")

        # set up progress bar
        self.progress = SimulationProgressBar(self.sim_config.progress)

        # write header to a plot log
        # plot logging is controlled by configuration files pointed to by config_constants and by env vars
        self.fast_plotting_logger.fast_log('# {:%Y-%m-%d %H:%M:%S}'.format(datetime.now()), sim_time=0)

        self.num_events_handled = 0
        self.log_with_time(f"Simulation to {self.sim_config.time_max} starting")

        try:
            self.progress.start(self.sim_config.time_max)
            self.init_metadata_collection(self.sim_config)

            while True:

                # use the stop condition
                if self.sim_config.stop_condition is not None and self.sim_config.stop_condition(self.time):
                    self.log_with_time(self.TERMINATE_WITH_STOP_CONDITION_SATISFIED)
                    self.progress.end()
                    break

                # if tracking object use, record object and memory use changes
                if _object_mem_tracking:
                    self.track_obj_mem()

                # get the earliest next event in the simulation
                # get parameters of next event from self.event_queue
                next_time = self.event_queue.next_event_time()
                next_sim_obj = self.event_queue.next_event_obj()

                if float('inf') == next_time:
                    self.log_with_time(self.NO_EVENTS_REMAIN)
                    self.progress.end()
                    break

                if self.sim_config.time_max < next_time:
                    self.log_with_time(self.END_TIME_EXCEEDED)
                    self.progress.end()
                    break

                self.time = next_time

                # error will only be raised if an object decreases its time
                if next_time < next_sim_obj.time:
                    raise SimulatorError("Dispatching '{}', but event time ({}) "
                                         "< object time ({})".format(next_sim_obj.name, next_time, next_sim_obj.time))

                # dispatch object that's ready to execute next event
                next_sim_obj.time = next_time

                self.log_with_time(" Running '{}' at {}".format(next_sim_obj.name, next_sim_obj.time))
                next_events = self.event_queue.next_events()
                for e in next_events:
                    e_name = ' - '.join([next_sim_obj.__class__.__name__, next_sim_obj.name, e.message.__class__.__name__])
                    self.event_counts[e_name] += 1
                next_sim_obj.__handle_event_list(next_events)
                self.num_events_handled += 1
                self.progress.progress(next_time)

        except SimulatorError as e:
            raise SimulatorError('Simulation ended with error:\n' + str(e))

        self.finish_metadata_collection()
        return self.num_events_handled
예제 #21
0
    def send_event_absolute(self,
                            event_time,
                            receiving_object,
                            message,
                            copy=False):
        """ Send a simulation event message with an absolute event time.

        Args:
            event_time (:obj:`float`): the absolute simulation time at which `receiving_object` will execute the event
            receiving_object (:obj:`SimulationObject`): the simulation object that will receive and
                execute the event
            message (:obj:`SimulationMessage`): the simulation message which will be carried by the event
            copy (:obj:`bool`, optional): if `True`, copy the message before adding it to the event;
                set `False` by default to optimize performance; set `True` as a safety measure to avoid
                unexpected changes to shared objects

        Raises:
            :obj:`SimulatorError`: if `event_time` < 0, or
                if the sending object type is not registered to send messages with the type of `message`, or
                if the receiving simulation object type is not registered to receive
                messages with the type of `message`
        """
        if math.isnan(event_time):
            raise SimulatorError("event_time is 'NaN'")
        if event_time < self.time:
            raise SimulatorError(
                "event_time ({}) < current time ({}) in send_event_absolute()".
                format(round_direct(event_time, precision=3),
                       round_direct(self.time, precision=3)))

        # Do not put a class reference in a message, as the message might not be received in the
        # same address space.
        # To eliminate the risk of name collisions use the fully qualified classname.
        # TODO(Arthur): wait until after MVP
        # event_type_name = most_qual_cls_name(message)
        event_type_name = message.__class__.__name__

        # check that the sending object type is registered to send the message type
        if not isinstance(message, SimulationMessage):
            raise SimulatorError(
                "simulation messages must be instances of type 'SimulationMessage'; "
                "'{}' is not".format(event_type_name))
        if message.__class__ not in self.__class__.metadata.message_types_sent:
            raise SimulatorError(
                "'{}' simulation objects not registered to send '{}' messages".
                format(most_qual_cls_name(self), event_type_name))

        # check that the receiving simulation object type is registered to receive the message type
        receiver_priorities = receiving_object.get_receiving_priorities_dict()
        if message.__class__ not in receiver_priorities:
            raise SimulatorError(
                "'{}' simulation objects not registered to receive '{}' messages"
                .format(most_qual_cls_name(receiving_object), event_type_name))

        if copy:
            message = deepcopy(message)

        self.simulator.event_queue.schedule_event(self.time, event_time, self,
                                                  receiving_object, message)
        self.log_with_time("Send: ({}, {:6.2f}) -> ({}, {:6.2f}): {}".format(
            self.name, self.time, receiving_object.name, event_time,
            message.__class__.__name__))