class Rectangle(Area):
    """
    This class implements a rectangular simulation area of the given size in
    the two-dimensional space with the origin in (0, 0).
    """

    def __init__(self, width, height):
        """
        *Parameters*:
            - **width** (`float`): a width of the rectangular simulation area
              (along the horizontal x-axis),
            - **height** (`float`): a height of the rectangular simulation area
              (along the vertical y-axis).

        *Raises*:
            - **ValueError**: raised when a given value of either **width** or
              **height** parameter is equal to or less than 0.
        """
        super(Rectangle, self).__init__(Rectangle.__name__)
        check_argument_type(Rectangle.__name__, 'width', float, width,
                            self.logger)
        if width <= 0:
            raise ValueError('Parameter "width": the width of a simulation'
                             ' area cannot be equal to or less than 0, but'
                             ' %d given!' % float(width))
        self.__width = float(width)
        check_argument_type(Rectangle.__name__, 'height', float, height,
                            self.logger)
        if height <= 0:
            raise ValueError('Parameter "height": the height of a simulation'
                             ' area cannot be equal to or less than 0, but'
                             ' %d given!' % float(height))
        self.__height = float(height)

    @property
    def height(self):
        """
        (*Property*)  A height of the simulation area of type `float`.
        """
        return self.__height

    @property
    def width(self):
        """
        (*Property*)  A width of the simulation area of type `float`.
        """
        return self.__width

    def within(self, horizontal_coordinate, vertical_coordinate):
        """
        Tests whether the given coordinates are within the simulation area.

        *Parameters*:
            - **horizontal_coordinate** (`float`): a horizontal (x-axis)
              coordinate;
            - **vertical_coordinate** (`float`): a vertical (y-axis)
              coordinate.

        *Returns*:
            (`bool`) `True` if the given coordinates are within the rectangular
            simulation area, or `False` otherwise.
        """
        if horizontal_coordinate < 0.0 or vertical_coordinate < 0.0:
            return False
        if horizontal_coordinate <= self.__width \
                and vertical_coordinate <= self.__height:
            return True
        return False

    def get_area(self):
        """
        Creates a dictionary that stores information about the simulation area.

        *Returns*:
            A dictionary that stores information about the simulation area;
            it has the following fields:

                - 'area name': a name of the simulation area of type `str`,
                - 'width': a width of the simulation area of type `float`,
                - 'height': a height of the simulation area of type `float`.
        """
        return {'area name': self.__class__.__name__.lower(),
                'width': self.__width,
                'height': self.__height} class Square(Rectangle):
    """
    This class implements a square simulation area of the given size in the
    two-dimensional space with the origin in (0, 0).
    """

    def __init__(self, side):
        """
        *Parameters*:
            - **side** (`float`): a side length of the square simulation area.

        .. note::

            In this case, the :meth:`sim2net.area.rectangle.Rectangle` method
            is called with the **width** and **height** parameters set to the
            value of the given **side** argument.
        """
        super(Square, self).__init__(side, side)

    def get_area(self):
        """
        Creates a dictionary that stores information about the simulation area.

        *Returns*:
            A dictionary that stores information about the simulation area;
            it has the following fields:

                - 'area name': a name of the simulation area of type `str`,
                - 'side': a side length of the square simulation area of type
                  `float`.
        """
        return {'area name': self.__class__.__name__.lower(),
                'side': self.width} class Crash(Failure):
    """
    This class implements the process crash model.

    .. note::

        It is presumed that the :meth:`node_failure` method is called at each
        step of the simulation.
    """

    def __init__(self, time, nodes_number, crash_probability,
                 maximum_crash_number, total_simulation_steps,
                 transient_steps=0):
        """
        *Parameters*:
            - **time**: a simulation time object of the
              :class:`sim2net._time.Time` class;
            - **nodes_number** (`int`): the total number of nodes in the
              simulated network;
            - **crash_probability** (`float`): the probability that a single
              process will crash during the total simulation time;
            - **maximum_crash_number** (`int`): the maximum number of faulty
              processes;
            - **total_simulation_steps** (`int`): the total number of
              simulation steps;
            - **transient_steps** (`int`): a number of steps at the beginning
              of the simulation during which no crashes occur (default: `0`).

        *Raises*:
            - **ValueError**: raised when the given value of the *time* object
              is `None`; or when the given number of nodes is less than or
              equal to zero; or when the given crash probability is less than
              zero or grater than one; or when the given value of the maximum
              number of faulty processes or the given value of the total
              simulation steps is less than zero; or when the number of steps
              in the transient period is less than zero or greater than the
              given value of the total simulation steps.
        """
        super(Crash, self).__init__(Crash.__name__)
        if time is None:
            raise ValueError('Parameter "time": a time abstraction object'
                             ' expected but "None" value given!')
        check_argument_type(Crash.__name__, 'nodes_number', int, nodes_number,
                            self.logger)
        if nodes_number <= 0:
            raise ValueError('Parameter "nodes_number": the number of nodes'
                             ' cannot be less or equal to zero but %d given!'
                             % int(nodes_number))
        check_argument_type(Crash.__name__, 'crash_probability', float,
                            crash_probability, self.logger)
        if crash_probability < 0.0 or crash_probability > 1.0:
            raise ValueError('Parameter "crash_probability": a value of the'
                             ' crash probability parameter cannot be less'
                             ' than zero and greater than one but %f given!'
                             % float(crash_probability))
        check_argument_type(Crash.__name__, 'maximum_crash_number', int,
                            maximum_crash_number, self.logger)
        if maximum_crash_number < 0:
            raise ValueError('Parameter "maximum_crash_number": a value of'
                             ' the maximum number of crashes cannot be less'
                             ' than zero but %d given!'
                             % int(maximum_crash_number))
        check_argument_type(Crash.__name__, 'total_simulation_steps', int,
                            total_simulation_steps, self.logger)
        if total_simulation_steps < 0:
            raise ValueError('Parameter "total_simulation_steps": a value of'
                             ' the total number of simulation steps cannot be'
                             ' less than zero but %d given!'
                             % int(total_simulation_steps))
        check_argument_type(Crash.__name__, 'transient_steps', int,
                            transient_steps, self.logger)
        if transient_steps < 0 or transient_steps > total_simulation_steps:
            raise ValueError('Parameter "transient_steps": a number of the'
                             ' transient steps cannot be less than zero or'
                             ' greater than the total number of simulation'
                             ' steps but %d given!' % int(transient_steps))
        self.__time = time
        self.__crashed = \
            self.__crashes(int(nodes_number),
                           float(crash_probability), int(maximum_crash_number),
                           int(total_simulation_steps), int(transient_steps))

    def __crashes(self, nodes_number, crash_probability, maximum_crash_number,
                  total_simulation_steps, transient_steps):
        """
        Determines faulty processes and their times of crash with the use of
        the given *crash probability*.  There is also a possibility to setup a
        transient period (at the beginning of the simulation), during which
        process failures do not occur, and the total number of faulty processes
        can also be limited to a given value.

        *Parameters*:
            - **nodes_number** (`int`): the total number of nodes in the
              simulated network;
            - **crash_probability** (`float`): the probability that a single
              process will crash during the total simulation time;
            - **maximum_crash_number** (`int`): the maximum number of faulty
              processes;
            - **total_simulation_steps** (`int`): the total number of
              simulation steps;
            - **transient_steps** (`int`): a number of steps at the beginning
              of the simulation during which no crashes occur (default: `0`).

        *Returns*:
            A `list` of `tuples`; each tuple contains an identifier of the node
            with faulty process and its time of crash (in simulation steps).
            The list is sorted in ascending order by crash times.
        """
        if maximum_crash_number == 0:
            return []
        crashes = [-1] * nodes_number
        crashes_number = 0
        for node_id in xrange(nodes_number):
            crash = self.random_generator.uniform(0.0, 1.0)
            if crash <= crash_probability:
                crashes[node_id] = \
                    self.random_generator.uniform(0.0 + transient_steps,
                                                  total_simulation_steps)
                crashes_number = crashes_number + 1
                if crashes_number == maximum_crash_number:
                    break
        self.random_generator.random_order(crashes)
        crashed = dict()
        for node_id in xrange(nodes_number):
            if crashes[node_id] > 0:
                crashed[node_id] = int(crashes[node_id])
        assert len(crashed) <= maximum_crash_number, \
            'The number of faulty process (%d) is greater then the maximum' \
            ' value (%d)!' % (len(crashes), maximum_crash_number)
        return sorted(crashed.iteritems(), key=operator.itemgetter(1))

    def node_failure(self, failures):
        """
        Gives *in place* information about nodes which processes have failed
        according to the crash model.

        *Parameters*:
            - **failures** (`list`): a list of boolean values of the size equal
              to the total number of nodes in the simulated network; `True`
              value in position :math:`i` indicates that the process on node
              number :math:`i` has failed.

        *Returns*"
            A `list` of nodes which processes failed at the current simulation
            step.

        *Examples*:

        In order to avoid any process failures use this class with the
        *crash_probability* and/or *maximum_crash_number* parameters set to
        `0`, as in the examples below.

        .. testsetup::

            from sim2net._time import Time
            from sim2net.failure.crash import Crash

        .. doctest::

            >>> clock = Time()
            >>> clock.setup()
            >>> crash = Crash(clock, 4, 0.0, 0, 2)
            >>> failures = [False, False, False, False]
            >>> clock.tick()
            (0, 0.0)
            >>> crash.node_failure(failures)
            []
            >>> print failures
            [False, False, False, False]
            >>> clock.tick()
            (1, 1.0)
            >>> crash.node_failure(failures)
            []
            >>> print failures
            [False, False, False, False]

            >>> clock = Time()
            >>> clock.setup()
            >>> crash = Crash(clock, 4, 1.0, 0, 2)
            >>> failures = [False, False, False, False]
            >>> clock.tick()
            (0, 0.0)
            >>> crash.node_failure(failures)
            []
            >>> print failures
            [False, False, False, False]
            >>> clock.tick()
            (1, 1.0)
            >>> crash.node_failure(failures)
            []
            >>> print failures
            [False, False, False, False]

        """
        crashes = list()
        while self.__crashed:
            if self.__crashed[0][1] <= self.__time.simulation_step:
                failures[self.__crashed[0][0]] = True
                crashes.append(self.__crashed[0][0])
                self.logger.debug('In accordance with the node failure model'
                                  ' node #%d has failed'
                                  % self.__crashed[0][0])
                self.__crashed.pop(0)
            else:
                break
        return crashes class GaussMarkov(Mobility):
    """
    This class implements the Gauss-Markov mobility model, in which motion of
    each node is modelled in the form of a Gauss-Markov stochastic process.

    .. note::

        * Due to the characteristics of this model, it is expected that each
          node has assigned the normal speed distribution (see:
          :mod:`sim2net.speed.normal`) -- the speed is used as random variable
          :math:`v_x` when a new speed is calculated.

        * All direction values used in this implementation are expressed in
          radians.

        * The :meth:`get_current_position` method computes a position of a node
          at the current *simulation step* (see: :mod:`sim2net._time`), so it
          is presumed that the method is called at each step of the simulation.
    """

    #: Default value of the alpha parameter.
    __DEFAULT_ALPHA = 0.75

    #: Default value of the direction standard deviation.
    __DEFAULT_DIRECTION_DEVIATION = float(pi/2.0)

    #: Default direction margin used to change the direction mean to ensure
    #: that nodes do not remain near the border of the simulation area for a
    #: long period of time.
    __DEFAULT_DIRECTION_MARGIN = 0.15

    #: Default value of the direction mean.
    __DEFAULT_DIRECTION_MEAN = float(pi/0.6)


    def __init__(self, area, time, initial_coordinates, initial_speed,
                 **kwargs):
        """
        *Parameters*:
            - **area**: an object representing the simulation area;
            - **time**: a simulation time object of the
              :class:`sim2net._time.Time` class;
            - **initial_coordinates** (`list`): initial coordinates of all
              nodes; each element of this parameter should be a tuple of two
              coordinates: horizontal and vertical (respectively) of type
              `float`;
            - **initial_speed** (`float`): a value of the initial speed that is
              assigned to each node at the beginning of the simulation;
            - **kwargs** (`dict`): a dictionary of (optional) keyword
              parameters related to the Gauss-Markov mobility model; the
              following parameters are accepted:

              **alpha** (`float`)
                  The tuning parameter :math:`0\\leqslant\\alpha\\leqslant 1`
                  used to vary the randomness of movements (default: `0.75`).

              **direction_deviation** (`float`)
                  Constant representing the standard deviation of direction
                  random variable :math:`d_x` (it defaults to
                  :math:`\\frac{\\pi}{2}`).

              **direction_margin** (`float`)
                  Constant used to change direction mean :math:`\\overline{d}`
                  to ensure that nodes do not remain near a border of the
                  simulation area for a long period of time (it defaults to
                  `0.15`, or `15%` of the simulation area width/height, and
                  cannot be less than zero and greater than one; see:
                  :meth:`_GaussMarkov__velocity_recalculation`).

              **direction_mean** (`float`)
                  Constant representing mean value :math:`\\overline{d}` of
                  direction (it defaults to :math:`\\frac{\\pi}{6}`).  The same
                  value is used as mean of direction random variable
                  :math:`d_x`.

              **recalculation_interval** (`int`)
                  Velocity (i.e. speed and direction) recalculation time
                  interval (it defaults to the *simulation frequency*; see:
                  :mod:`sim2net._time`).  It determines how often, counting in
                  simulation steps, new values of velocity are recalculated.

        *Raises*:
            - **ValueError**: raised when the given value of the *area*,
              *time*, *initial_coordinates* or *initial_speed* parameter is
              `None`; or when the given value of the keyword parameter *alpha*
              is less than zero or greater that one; or when the given value of
              the (optional) keyword parameter *direction_margin* is less than
              zero or greater than one.

        *Example*:

        .. doctest::
            :options: +SKIP

            >>> gm = GaussMarkov(area, time, coordinates, 10.0, alpha=0.35)

        """
        if area is None:
            raise ValueError('Parameter "area": a simulation area object' \
                             ' expected but "None" value given!')
        if time is None:
            raise ValueError('Parameter "time": a time abstraction object' \
                             ' expected but "None" value given!')
        if initial_coordinates is None:
            raise ValueError('Parameter "initial_coordinates": identifiers' \
                             ' of nodes expected but "None" value given!')
        if initial_speed is None:
            raise ValueError('Parameter "initial_speed": a value of the' \
                             ' initial speed of nodes expected but "None"' \
                             ' value given!')
        Mobility.__init__(self, GaussMarkov.__name__)
        self.__area = area
        self.__time = time
        check_argument_type(GaussMarkov.__name__, 'initial_coordinates', list,
                            initial_coordinates, self.logger)
        check_argument_type(GaussMarkov.__name__, 'initial_speed', float,
                            initial_speed, self.logger)
        if 'alpha' in kwargs:
            check_argument_type(GaussMarkov.__name__, 'alpha', float,
                                kwargs['alpha'], self.logger)
            if kwargs['alpha'] < 0.0 or kwargs['alpha'] > 1.0:
                raise ValueError('Keyword parameter "alpha": a value of the' \
                                 ' "alpha" parameter cannot be less than' \
                                 ' zero and greater than one but %f given!' \
                                 % float(kwargs['alpha']))
        if 'direction_deviation' in kwargs:
            check_argument_type(GaussMarkov.__name__, 'direction_deviation',
                                float, kwargs['direction_deviation'],
                                self.logger)
        if 'direction_margin' in kwargs:
            check_argument_type(GaussMarkov.__name__, 'direction_margin',
                                float, kwargs['direction_margin'], self.logger)
            if kwargs['direction_margin'] < 0.0 \
               or kwargs['direction_margin'] > 1.0:
                raise ValueError('Keyword parameter "direction_margin": a' \
                                 ' value of the direction margin cannot be' \
                                 ' less than zero and greater than one but' \
                                 ' %f given!' \
                                 % float(kwargs['direction_margin']))
        if 'direction_mean' in kwargs:
            check_argument_type(GaussMarkov.__name__, 'direction_mean', float,
                                kwargs['direction_mean'], self.logger)
        if 'recalculation_interval' in kwargs:
            check_argument_type(GaussMarkov.__name__, 'recalculation_interval',
                                int, kwargs['recalculation_interval'],
                                self.logger)
        self.__alpha = float(kwargs.get('alpha', GaussMarkov.__DEFAULT_ALPHA))
        self.__direction_deviation = \
            float(kwargs.get('direction_deviation',
                             GaussMarkov.__DEFAULT_DIRECTION_DEVIATION))
        self.__direction_margin = \
            float(kwargs.get('direction_margin',
                             GaussMarkov.__DEFAULT_DIRECTION_MARGIN))
        self.__direction_mean = \
            float(kwargs.get('direction_mean',
                             GaussMarkov.__DEFAULT_DIRECTION_MEAN))
        self.__recalculation_interval = \
            int(kwargs.get('recalculation_interval',
                           self.__time.simulation_frequency))
        # 1.0 - alpha:
        self.__gauss_markov_factor_one = 1.0 - self.__alpha
        # sqrt((1.0 - alpha^2)):
        self.__gauss_markov_factor_two = sqrt(1.0 - (pow(self.__alpha, 2.0)))
        # area width * direction margin:
        self.__width_left = self.__area.width * self.__direction_margin
        # area width - (area width * direction margin):
        self.__width_right = self.__area.width - self.__width_left
        # area height * direction margin:
        self.__height_bottom = self.__area.height * self.__direction_margin
        # area height - (area height * direction margin):
        self.__height_top = self.__area.height - self.__height_bottom
        # { node id:
        #     { 'speed':      current speed),
        #       'direction':  current direction } }
        self.__velocities = dict()
        for node_id in range(0, len(initial_coordinates)):
            self.__velocities[node_id] = dict()
            self.__velocities[node_id]['speed'] = float(initial_speed)
            self.__velocities[node_id]['direction'] = \
                self.__get_new_direction()
        self.logger.debug('Speed and direction values has been initialized' \
                          ' for %d nodes' % len(self.__velocities))

    def __get_new_direction(self):
        """
        Randomizes a new direction with the normal (Gaussian) distribution.

        *Returns*:
            (`float`) a newly randomized direction value.
        """
        return fabs(self.random_generator.normal(self.__direction_mean,
                                                 self.__direction_deviation))

    def __velocity_recalculation(self, node_id, node_speed, node_coordinates):
        """
        Recalculates a node's velocity, i.e. its speed and direction, as a
        Gauss-Markov stochastic process.

        To ensure that a node does not remain near a border of the simulation
        area for a long period of time, the node is forced away from the border
        when it moves within certain distance of the edge.  This is done by
        modifying mean direction :math:`\\overline{d}`.  For example, when a
        node is near the right border of the simulation area, the value of
        :math:`\\overline{d}` changes to 180 degrees (:math:`\\pi`).  The
        distance that is used in this method is calculated as a product of the
        **direction margin** and area width or height.

        *Parameters*:
            - **node_id** (`int`): an identifier of the node;
            - **node_speed**: an object representing the node's speed;
            - **node_coordinates** (`list`): values of the node's horizontal
              and vertical coordinates at the previous simulation step.
        """
        # speed
        random_speed = node_speed.get_new()
        self.__velocities[node_id]['speed'] = \
            fabs((self.__alpha * self.__velocities[node_id]['speed'])
                 + (self.__gauss_markov_factor_one * node_speed.mean)
                 + (self.__gauss_markov_factor_two * random_speed))
        # direction
        if node_coordinates[1] >= self.__height_top:
            if node_coordinates[0] <= self.__width_left:
                mean = (7.0/4.0)*pi
            elif node_coordinates[0] >= self.__width_right:
                mean = (5.0/4.0)*pi
            else:
                mean = (3.0/2.0)*pi
        elif node_coordinates[1] <= self.__height_bottom:
            if node_coordinates[0] <= self.__width_left:
                mean = pi/4.0
            elif node_coordinates[0] >= self.__width_right:
                mean = (3.0/4.0)*pi
            else:
                mean = pi/2.0
        elif node_coordinates[0] <= self.__width_left:
            mean = 0.0
        elif node_coordinates[0] >= self.__width_right:
            mean = pi
        else:
            mean = self.__direction_mean
        random_direction = self.__get_new_direction()
        self.__velocities[node_id]['direction'] = \
            (self.__alpha * self.__velocities[node_id]['direction']) \
            + (self.__gauss_markov_factor_one * mean) \
            + (self.__gauss_markov_factor_two * random_direction)
        if self.logger.isEnabledFor('DEBUG'):
            msg = 'A new velocity has been selected for the node #%d: the' \
                  ' new speed is equal to %f and the new direction is equal' \
                  ' to %f'
            self.logger.debug(msg %
                              (node_id, self.__velocities[node_id]['speed'],
                               self.__velocities[node_id]['direction']))

    def __step_move(self, node_id, node_coordinates):
        """
        Computes a node's position at the current simulation step.

        *Parameters*:
            - **node_id** (`int`): an identifier of the node;
            - **node_coordinates** (`list`): values of the node's horizontal
              and vertical coordinates at the previous simulation step.

        *Returns*:
            (`tuple`) current values of the node's horizontal and vertical
            coordinates.
        """
        horizontal_coordinate = \
            node_coordinates[0] \
            + (self.__velocities[node_id]['speed'] \
               * cos(self.__velocities[node_id]['direction']))
        horizontal_coordinate = \
            node_coordinates[0] \
            + ((horizontal_coordinate - node_coordinates[0]) \
                * self.__time.simulation_period)
        if horizontal_coordinate < 0.0:
            horizontal_coordinate = 0.0
            self.__velocities[node_id]['direction'] = \
                (2.0*pi) - (2.0*self.__velocities[node_id]['direction'])
        elif horizontal_coordinate > self.__area.width:
            horizontal_coordinate = \
                self.__area.width - (horizontal_coordinate - self.__area.width)
            self.__velocities[node_id]['direction'] = \
                (2.0*pi) - (2.0*self.__velocities[node_id]['direction'])
        vertical_coordinate = \
            node_coordinates[1] \
            + (self.__velocities[node_id]['speed'] \
               * sin(self.__velocities[node_id]['direction']))
        vertical_coordinate = \
            node_coordinates[1] \
            + ((vertical_coordinate - node_coordinates[1]) \
                * self.__time.simulation_period)
        if vertical_coordinate < 0.0:
            vertical_coordinate = 0.0
            self.__velocities[node_id]['direction'] = \
                (2.0*pi) - (2.0*self.__velocities[node_id]['direction'])
        elif vertical_coordinate > self.__area.height:
            vertical_coordinate = \
                self.__area.height - (vertical_coordinate - self.__area.height)
            self.__velocities[node_id]['direction'] = \
                (2.0*pi) - (2.0*self.__velocities[node_id]['direction'])
        return (horizontal_coordinate, vertical_coordinate)

    def get_current_position(self, node_id, node_speed, node_coordinates):
        """
        Calculates and returns a node's position at the current simulation step
        in accordance with the Gauss-Markov mobility model.

        A distance of the route traveled by the node, between the current and
        previous simulation steps, is calculated as the product of the current
        node's speed and the *simulation period* (see: :mod:`sim2net._time`
        module).  Therefore, it is assumed that this method is called at every
        simulation step.

        *Parameters*:
            - **node_id** (`int`): an identifier of the node;
            - **node_speed**: an object representing the node's speed;
            - **node_coordinates** (`list`): values of the node's horizontal
              and vertical coordinates at the previous simulation step.

        *Returns*:
            A tuple containing current values of the node's horizontal and
            vertical coordinates.
        """
        if self.__time.simulation_step == 0:
            if __debug__ and self.logger.isEnabledFor('DEBUG'):
                msg = 'The current position of the node #%d is (%f, %f) with' \
                      ' the current speed equal to %f and direction equal to' \
                      ' %f'
                self.logger.debug(msg %
                    (node_id, node_coordinates[0], node_coordinates[1],
                     self.__velocities[node_id]['speed'],
                     self.__velocities[node_id]['direction']))
            return node_coordinates
        coordinates = self.__step_move(node_id, node_coordinates)
        if __debug__ and self.logger.isEnabledFor('DEBUG'):
            msg = 'The current position of the node #%d is (%f, %f) with the' \
                  ' current speed equal to %f and direction equal to %f'
            self.logger.debug(msg % (node_id, coordinates[0], coordinates[1],
                                     self.__velocities[node_id]['speed'],
                                     self.__velocities[node_id]['direction']))
        if self.__time.simulation_step % self.__recalculation_interval == 0:
            self.__velocity_recalculation(node_id, node_speed, node_coordinates)
        # print "%.30f    %.30f" % (coordinates[0], coordinates[1])
        return coordinates class NomadicCommunity(RandomWaypoint, Mobility):
    """
    This class implements the Nomadic Community mobility model, in which a
    group of nodes travels together from one location to another.

    In this implementation, coordinates of the reference point are uniformly
    selected at random within the simulation area once every :math:`x+y\\times
    pause\\_time` *simulation time* units (see: :mod:`sim2net._time` module),
    where :math:`x` is uniformly picked at random from the range :math:`[100,
    200]`, and :math:`y` from the range :math:`[1, 10]`.  Nodes roam around
    reference points in accordance with the **Random Waypoint** mobility model
    (see: :mod:`sim2net.mobility.random_waypoint` module).  The width and
    height of the (square or rectangular) free roam area around the reference
    point are computed as a product of the *area_factor* parameter and the
    width and height (respectively) of the simulation area.

    .. note::

        The :meth:`get_current_position` method computes a position of a node
        at the current *simulation step* (see: :mod:`sim2net._time`), so it is
        presumed that the method is called at each step of the simulation.
    """

    #: The minimum value of the range used to determine the reference point
    #: change time.
    __DEFAULT_MINIMUM_RELOCATION_TIME = 100.0

    #: The maximum value of the range used to determine the reference point
    #: change time.
    __DEFAULT_MAXIMUM_RELOCATION_TIME = 200.0

    #: The minimum value of the range for the pause time used to determine the
    #: reference point change time.
    __DEFAULT_MINIMUM_RELOCATION_PAUSE_TIME = 1.00

    #: The maximum value of the range for the pause time used to determine the
    #: reference point change time.
    __DEFAULT_MAXIMUM_RELOCATION_PAUSE_TIME = 10.0


    def __init__(self, area, time, initial_coordinates, pause_time=0.0,
                 area_factor=0.25):
        """
        *Parameters*:
            - **area**: an object representing the simulation area;
            - **time**: a simulation time object of the
              :class:`sim2net._time.Time` class;
            - **initial_coordinates** (`list`): initial coordinates of all
              nodes; each element of this parameter should be a tuple of two
              coordinates: horizontal and vertical (respectively) of type
              `float`;
            - **pause_time** (`float`): a maximum value of the pause time in
              the *simulation time* units (default: `0.0`, see also:
              :mod:`sim2net._time`);
            - **area_factor** (`float`): a factor used to determine the width
              and height of the free roam area around the reference point
              (default: `0.25`).

        *Raises*:
            - **ValueError**: raised when the given value of the *area*, *time*
              or *initial_coordinates* parameter is `None`; or when the given
              value of the *pause_time* parameter is less that zero; or when
              the given value of the *area_factor* parameter is less than zero
              or greater than one.

        (At the beginning, nodes' destination points are set to be equal to its
        initial coordinates passed by the *initial_coordinates* parameter.)
        """
        if area is None:
            raise ValueError('Parameter "area": a simulation area object' \
                             ' expected but "None" value given!')
        if time is None:
            raise ValueError('Parameter "time": a time abstraction object' \
                             ' expected but "None" value given!')
        if initial_coordinates is None:
            raise ValueError('Parameter "initial_coordinates": identifiers' \
                             ' of nodes expected but "None" value given!')
        Mobility.__init__(self, NomadicCommunity.__name__)
        check_argument_type(RandomWaypoint.__name__, 'initial_coordinates',
                            list, initial_coordinates, self.logger)
        check_argument_type(RandomWaypoint.__name__, 'pause_time', float,
                            pause_time, self.logger)
        if pause_time < 0.0:
            raise ValueError('Parameter "pause_time": a value of the pause' \
                             ' time cannot be less that zero but %f given!' \
                             % float(pause_time))
        self._pause_time = float(pause_time)
        check_argument_type(RandomWaypoint.__name__, 'area_factor', float,
                            area_factor, self.logger)
        if area_factor < 0.0 or area_factor > 1.0:
            raise ValueError('Parameter "area_factor": a value of the factor' \
                             ' used to compute the free roam area of nodes' \
                             ' cannot be less than zero or greater than one' \
                             ' but %f given!' % float(area_factor))
        self.__area_factor = float(area_factor)
        self._area = area
        self._time = time
        self.__reference_point = \
            (self.random_generator.uniform(self._area.ORIGIN[0],
                                           self._area.width),
             self.random_generator.uniform(self._area.ORIGIN[1],
                                           self._area.height))
        self.__relocation_time = None
        # { node id:
        #     { 'destination':  (horizontal coordinate, vertical coordinate),
        #       'pause time' :  time } }
        #       'on site'    :  True/False } }
        self._destinations = dict()
        for node_id in range(0, len(initial_coordinates)):
            self._destinations[node_id] = dict()
            self._destinations[node_id]['destination'] = \
                initial_coordinates[node_id]
            self._destinations[node_id]['pause time'] = None
            self._destinations[node_id]['on site'] = False
        self.logger.debug('Destination points has been initialized for %d' \
                          ' nodes with the initial reference point at (%f,' \
                          ' %f)' % (len(self._destinations),
                                    self.__reference_point[0],
                                    self.__reference_point[1]))

    def __get_free_roam_area_edges(self, reference_point):
        """
        Computes boundaries of a free roam area around a given reference point.

        *Parameter*:
            - **reference_point** (`tuple`) containing horizontal and vertical
              coordinates (respectively) of the reference point.

        *Returns*:
            A `tuple` containing values of the top, right, bottom and left
            boundaries (respectively) in the simulation area.
        """
        offset = 0.5 * self._area.width * self.__area_factor
        left_edge = reference_point[0] - offset
        if left_edge < 0.0:
            left_edge = 0.0
        right_edge = reference_point[0] + offset
        if right_edge > self._area.width:
            right_edge = self._area.width
        offset = 0.5 * self._area.height * self.__area_factor
        bottom_edge = reference_point[1] - offset
        if bottom_edge < 0.0:
            bottom_edge = 0.0
        top_edge = reference_point[1] + offset
        if top_edge > self._area.height:
            top_edge = self._area.height
        return (top_edge, right_edge, bottom_edge, left_edge)

    def __get_new_reference_point(self):
        """
        Uniformly randomizes new coordinates of the reference point.  The
        vertical and horizontal coordinates are returned (respectively) as a
        `tuple`.
        """
        edges = self.__get_free_roam_area_edges(self.__reference_point)
        while True:
            reference_point = \
                (self.random_generator.uniform(self._area.ORIGIN[0],
                                               self._area.width),
                 self.random_generator.uniform(self._area.ORIGIN[1],
                                               self._area.height))
            new_edges = self.__get_free_roam_area_edges(reference_point)
            if new_edges[2] > edges[0] or new_edges[3] > edges[1] \
               or new_edges[0] < edges[2] or new_edges[1] < edges[3]:
                break
        self.logger.debug('New coordinates of the reference point has been' \
                          ' selected: (%f, %f)' % (reference_point[0],
                                                   reference_point[1]))
        return reference_point

    def __get_new_relocation_time(self):
        """
        Randomizes and returns a new relocation time of type `float`, after
        which coordinates of the reference point will be changed.
        """
        relocation_time = \
            self.random_generator.uniform(
                NomadicCommunity.__DEFAULT_MINIMUM_RELOCATION_TIME,
                NomadicCommunity.__DEFAULT_MAXIMUM_RELOCATION_TIME) \
            + (self.random_generator.uniform(
                   NomadicCommunity.__DEFAULT_MINIMUM_RELOCATION_PAUSE_TIME,
                   NomadicCommunity.__DEFAULT_MAXIMUM_RELOCATION_PAUSE_TIME) \
               * self._pause_time)
        self.logger.debug('New reference point relocation time has been' \
                          ' selected: %f simulation time units'
                          % relocation_time)
        return relocation_time

    def __reference_point_relocation(self):
        """
        Relocates the reference point by picking its new coordinates.  The
        relocation takes place only if all nodes are within the current area of
        free roam and the relocation time has expired.  Otherwise, the current
        coordinates of the reference point are preserved.
        """
        if self.__relocation_time is None:
            for node_id in self._destinations:
                if not self._destinations[node_id]['on site']:
                    return
            self.__relocation_time = \
                self._time.simulation_time + self.__get_new_relocation_time()
        else:
            if self.__relocation_time <= self._time.simulation_time:
                self.__relocation_time = None
                for node_id in self._destinations:
                    self._destinations[node_id]['on site'] = False
                self.__reference_point = self.__get_new_reference_point()

    def _get_new_destination(self):
        """
        Uniformly randomizes a new waypoint within the range of free roam and
        returns its coordinates as a `tuple`.
        """
        edges = self.__get_free_roam_area_edges(self.__reference_point)
        return (self.random_generator.uniform(edges[3], edges[1]),
                self.random_generator.uniform(edges[2], edges[0]))

    def get_current_position(self, node_id, node_speed, node_coordinates):
        """
        Calculates and returns a node's position at the current simulation step
        in accordance with the Nomadic Community mobility model (and Random
        Waypoint model within the area of free roam).

        A distance of the route traveled by the node, between the current and
        previous simulation steps, is calculated as the product of the current
        node's speed and the *simulation period* (see: :mod:`sim2net._time`
        module).  Therefore, it is assumed that this method is called at every
        simulation step.

        *Parameters*:
            - **node_id** (`int`): an identifier of the node;
            - **node_speed**: an object representing the node's speed;
            - **node_coordinates** (`list`): values of the node's horizontal
              and vertical coordinates at the previous simulation step.

        *Returns*:
            A tuple containing current values of the node's horizontal and
            vertical coordinates.
        """
        # reference point relocation?
        self.__reference_point_relocation()
        # pause time?
        if self._destinations[node_id]['pause time'] is not None:
            if self._pause(node_id, node_coordinates) is None:
                self._assign_new_destination(node_id, node_speed)
            return node_coordinates
        # movement
        coordinates = self._step_move(node_id, node_speed, node_coordinates)
        assert 0 <= coordinates[0] <= self._area.width \
               and 0 <= coordinates[1] <= self._area.height, \
               'The new coordinates (%f, %f) exceed dimensions of the' \
               ' simulation area!' % coordinates
        if (coordinates[0] == self._destinations[node_id]['destination'][0]
            and
            coordinates[1] == self._destinations[node_id]['destination'][1]):
            if not self._destinations[node_id]['on site']:
                edges = self.__get_free_roam_area_edges(self.__reference_point)
                if coordinates[0] >= edges[3] \
                   and coordinates[0] <= edges[1] \
                   and coordinates[1] >= edges[2] \
                   and coordinates[1] <= edges[0]:
                    self._destinations[node_id]['on site'] = True
            if self._assign_new_pause_time(node_id) is not None:
                self._destinations[node_id]['destination'] = [None, None]
            else:
                self._assign_new_destination(node_id, node_speed)
        elif __debug__ and self.logger.isEnabledFor('DEBUG'):
            msg = 'The current position of the node #%d is (%f, %f) with the' \
                  ' current speed equal to %f'
            self.logger.debug(msg % (node_id, coordinates[0], coordinates[1],
                                     fabs(node_speed.current)))
        print "%.30f    %.30f    %.30f    %.30f" % (coordinates[0], coordinates[1], self.__reference_point[0], self.__reference_point[1])
        return coordinates class RandomDirection(RandomWaypoint, Mobility):
    """
    This class implements the Random Direction mobility model, in which each
    node moves along straight lines from one destination point, on the boundary
    of the simulation area, to another.

    The nodes may also have *pause times* when they reach their destination
    points, and their speeds are selected at random between the *minimum* and
    *maximum speed* values.  (All random picks are uniformly distributed).

    .. note::

        The :meth:`get_current_position` method computes a position of a node
        at the current *simulation step* (see: :mod:`sim2net._time`), so it is
        presumed that the method is called at each step of the simulation.

    .. seealso::  :mod:`sim2net.mobility.random_waypoint`
    """

    def __init__(self, area, time, initial_coordinates, pause_time=0.0):
        """
        *Parameters*:
            - **area**: an object representing the simulation area;
            - **time**: a simulation time object of the
              :class:`sim2net._time.Time` class;
            - **initial_coordinates** (`list`): initial coordinates of all
              nodes; each element of this parameter should be a tuple of two
              coordinates: horizontal and vertical (respectively) of type
              `float`;
            - **pause_time** (`float`): a maximum value of the pause time in
              the *simulation time* units (default: `0.0`, see also:
              :mod:`sim2net._time`).

        *Raises*:
            - **ValueError**: raised when the given value of the *area*, *time*
              or *initial_coordinates* parameter is `None` or when the given
              value of the *pause_time* parameter is less that zero.

        (At the beginning, nodes' destination points are set to be equal to its
        initial coordinates passed by the *initial_coordinates* parameter.)
        """
        if area is None:
            raise ValueError('Parameter "area": a simulation area object' \
                             ' expected but "None" value given!')
        if time is None:
            raise ValueError('Parameter "time": a time abstraction object' \
                             ' expected but "None" value given!')
        if initial_coordinates is None:
            raise ValueError('Parameter "initial_coordinates": identifiers' \
                             ' of nodes expected but "None" value given!')
        Mobility.__init__(self, RandomDirection.__name__)
        self._area = area
        self._time = time
        self._destinations = dict()
        check_argument_type(RandomDirection.__name__, 'initial_coordinates',
                            list, initial_coordinates, self.logger)
        check_argument_type(RandomDirection.__name__, 'pause_time', float,
                            pause_time, self.logger)
        if pause_time < 0.0:
            raise ValueError('Parameter "pause_time": a value of the pause' \
                             ' time cannot be less that zero but %f given!' \
                             % float(pause_time))
        self._pause_time = float(pause_time)
        # { node id:
        #     { 'destination':  (horizontal coordinate, vertical coordinate),
        #       'pause time' :  time } }
        for node_id in range(0, len(initial_coordinates)):
            self._destinations[node_id] = dict()
            self._destinations[node_id]['destination'] = \
                initial_coordinates[node_id]
            self._destinations[node_id]['pause time'] = None
        self.logger.debug('Destination points has been initialized for %d' \
                          ' nodes' % len(self._destinations))

    def _get_new_destination(self):
        """
        Randomizes a new destination point on the boundary of the simulation
        area and returns its coordinates as a `tuple`.
        """
        destinations = (self.random_generator.uniform(self._area.ORIGIN[0],
                                                      self._area.width),
                        self.random_generator.uniform(self._area.ORIGIN[1],
                                                      self._area.height))
        direction = self.random_generator.uniform(0.0, 4.0)
        if direction > 3.0:
            return (0.0, destinations[1])
        elif direction > 2.0:
            return (destinations[0], self._area.height)
        elif direction > 1.0:
            return (self._area.width, destinations[1])
        else: return (destinations[0], 0.0) class RandomWaypoint(Mobility):
    """
    This class implements the Random Waypoint mobility model, in which each
    node moves along straight lines from one waypoint to another.

    The waypoints are randomly picked within the simulation area.  The nodes
    may also have *pause times* when they reach waypoints, and their speeds are
    selected at random between the *minimum* and *maximum speed* values.  (All
    random picks are uniformly distributed).

    .. note::

        The :meth:`get_current_position` method computes a position of a node
        at the current *simulation step* (see: :mod:`sim2net._time`), so it is
        presumed that the method is called at each step of the simulation.
    """

    def __init__(self, area, time, initial_coordinates, pause_time=0.0):
        """
        *Parameters*:
            - **area**: an object representing the simulation area;
            - **time**: a simulation time object of the
              :class:`sim2net._time.Time` class;
            - **initial_coordinates** (`list`): initial coordinates of all
              nodes; each element of this parameter should be a tuple of two
              coordinates: horizontal and vertical (respectively) of type
              `float`;
            - **pause_time** (`float`): a maximum value of the pause time in
              the *simulation time* units (default: `0.0`, see also:
              :mod:`sim2net._time`).

        *Raises*:
            - **ValueError**: raised when the given value of the *area*, *time*
              or *initial_coordinates* parameter is `None` or when the given
              value of the *pause_time* parameter is less that zero.

        (At the beginning, nodes' destination points are set to be equal to its
        initial coordinates passed by the *initial_coordinates* parameter.)
        """
        if area is None:
            raise ValueError('Parameter "area": a simulation area object' \
                             ' expected but "None" value given!')
        if time is None:
            raise ValueError('Parameter "time": a time abstraction object' \
                             ' expected but "None" value given!')
        if initial_coordinates is None:
            raise ValueError('Parameter "initial_coordinates": identifiers' \
                             ' of nodes expected but "None" value given!')
        super(RandomWaypoint, self).__init__(RandomWaypoint.__name__)
        self._area = area
        self._time = time
        self._destinations = dict()
        check_argument_type(RandomWaypoint.__name__, 'initial_coordinates',
                            list, initial_coordinates, self.logger)
        check_argument_type(RandomWaypoint.__name__, 'pause_time', float,
                            pause_time, self.logger)
        if pause_time < 0.0:
            raise ValueError('Parameter "pause_time": a value of the pause' \
                             ' time cannot be less that zero but %f given!' \
                             % float(pause_time))
        self._pause_time = float(pause_time)
        # { node id:
        #     { 'destination':  (horizontal coordinate, vertical coordinate),
        #       'pause time' :  time } }
        for node_id in range(0, len(initial_coordinates)):
            self._destinations[node_id] = dict()
            self._destinations[node_id]['destination'] = \
                initial_coordinates[node_id]
            self._destinations[node_id]['pause time'] = None
        self.logger.debug('Destination points has been initialized for %d' \
                          ' nodes' % len(self._destinations))

    def _get_new_destination(self):
        """
        Randomizes a new waypoint and returns its coordinates as a `tuple`.
        """
        return (self.random_generator.uniform(self._area.ORIGIN[0],
                                              self._area.width),
                self.random_generator.uniform(self._area.ORIGIN[1],
                                              self._area.height))

    def _get_new_pause_time(self):
        """
        Randomizes a new pause time and returns its value of type `float`.
        """
        return self.random_generator.uniform(0.0, self._pause_time)

    def _assign_new_destination(self, node_id, node_speed):
        """
        Assigns a new destination point for a node of a given ID and picks its
        new speed value.  (See also: :meth:`_get_new_destination`)

        *Parameters*:
            - **node_id** (`int`): an identifier of the node;
            - **node_speed**: an object representing the node's speed.
        """
        self._destinations[node_id]['destination'] = \
            self._get_new_destination()
        node_speed.get_new()
        if self.logger.isEnabledFor('DEBUG'):
            msg = 'A new destination has been selected for the node #%d:' \
                  ' (%f, %f) with the current speed equal to %f'
            self.logger.debug(
                msg % (node_id, self._destinations[node_id]['destination'][0],
                       self._destinations[node_id]['destination'][1],
                       fabs(node_speed.current)))

    def _assign_new_pause_time(self, node_id):
        """
        Assigns a new pause time for a node of a given ID and returns the
        value.  If the maximum pause time is set to `0`, `None` value is
        assigned and returned.

        *Parameters*:
            - **node_id** (`int`): an identifier of the node.

        *Returns*:
            (`float`) a newly randomized pause time.
        """
        if self._pause_time > 0:
            pause_time = self._get_new_pause_time()
            self._destinations[node_id]['pause time'] = pause_time
        else:
            pause_time = 0.0
            self._destinations[node_id]['pause time'] = None
        if self.logger.isEnabledFor('DEBUG'):
            msg = 'The node #%d is now in its destination position (%f, %f)' \
                  ' with the pause time equal to %f'
            self.logger.debug(msg %
                (node_id, self._destinations[node_id]['destination'][0],
                self._destinations[node_id]['destination'][1], pause_time))
        return self._destinations[node_id]['pause time']

    def _parallel_trajectory(self, coordinate, destination, step_distance):
        """
        Computes the current position of a node when one of its coordinates is
        equal to the corresponding destination coordinate.  In such a case, the
        node moves on a straight line that is parallel to the horizontal or
        vertical axis of the simulation area.  (See also:
        :meth:`_diagonal_trajectory`.)

        *Parameters*:
            - **coordinate** (`float`): a value of the previous node's
              coordinate that is not equal to its corresponding destination
              coordinate;
            - **destination** (`float`): a value of the destination coordinate;
            - **step_distance** (`float`): a distance that the node has moved
              between the previous and current simulation steps.

        *Returns*:
            (`float`) a current value of the node's coordinate.
        """
        if destination > coordinate \
           and destination >= coordinate + step_distance:
            return coordinate + step_distance
        if destination < coordinate \
           and destination < coordinate - step_distance:
            return coordinate - step_distance
        return destination

    def _diagonal_trajectory(self, node_id, node_coordinates, step_distance):
        """
        Computes the current position of a node if its trajectory is not
        parallel to the horizontal or vertical axis of the simulation area.
        (See also: :meth:`_parallel_trajectory`.)

        *Parameters*:
            - **node_id** (`int`): an identifier of the node;
            - **node_coordinates** (`list`): values of the node's horizontal
              and vertical coordinates at the previous simulation step.
            - **step_distance** (`float`): a distance that the node has moved
              between the previous and current simulation step.

        *Returns*:
            (`tuple`) current values of the node's horizontal and vertical
            coordinates.
        """
        horizontal_destination = self._destinations[node_id]['destination'][0]
        vertical_destination = self._destinations[node_id]['destination'][1]
        horizontal_distance = \
            fabs(horizontal_destination - node_coordinates[0])
        vertical_distance = fabs(vertical_destination - node_coordinates[1])
        distance = \
            sqrt(pow(horizontal_distance, 2.0) + pow(vertical_distance, 2.0))
        if step_distance >= distance:
            return (horizontal_destination, vertical_destination)
        horizontal_coordinate = \
            (horizontal_distance * step_distance) / distance
        vertical_coordinate = \
            (vertical_distance * horizontal_coordinate) / horizontal_distance
        if node_coordinates[0] < horizontal_destination:
            horizontal_coordinate = node_coordinates[0] + horizontal_coordinate
        else:
            horizontal_coordinate = node_coordinates[0] - horizontal_coordinate
        if node_coordinates[1] < vertical_destination:
            vertical_coordinate = node_coordinates[1] + vertical_coordinate
        else:
            vertical_coordinate = node_coordinates[1] - vertical_coordinate
        return (horizontal_coordinate, vertical_coordinate)

    def _step_move(self, node_id, node_speed, node_coordinates):
        """
        Computes a node's position at the current simulation step. If its
        trajectory is parallel to the horizontal or vertical axis of the
        simulation area, the :meth:`_steady_trajectory` method is used,
        otherwise the :meth:`_diagonal_trajectory` method is used.

        *Parameters*:
            - **node_id** (`int`): an identifier of the node;
            - **node_speed**: an object representing the node's speed;
            - **node_coordinates** (`list`): values of the node's horizontal
              and vertical coordinates at the previous simulation step.

        *Returns*:
            (`tuple`) current values of the node's horizontal and vertical
            coordinates.
        """
        horizontal_destination = self._destinations[node_id]['destination'][0]
        vertical_destination = self._destinations[node_id]['destination'][1]
        if node_coordinates[0] == horizontal_destination \
           and node_coordinates[1] == vertical_destination:
            return (horizontal_destination, vertical_destination)
        step_distance = fabs(node_speed.current) * self._time.simulation_period
        if node_coordinates[0] == horizontal_destination:
            return \
                (horizontal_destination,
                 self._parallel_trajectory(
                     node_coordinates[1], vertical_destination, step_distance))
        if node_coordinates[1] == vertical_destination:
            return \
                (self._parallel_trajectory(
                     node_coordinates[0], horizontal_destination,
                     step_distance),
                 vertical_destination)
        return self._diagonal_trajectory(node_id, node_coordinates,
                                         step_distance)

    def _pause(self, node_id, node_coordinates):
        """
        Decreases the current value of a node's pause time and returns the
        result of type `float`, or `None` if the pause time has expired.

        *Parameters*:
            - **node_id** (`int`): an identifier of the node;
            - **node_coordinates** (`list`): values of the node's horizontal
              and vertical coordinates at the previous simulation step.
        """
        self._destinations[node_id]['pause time'] -= \
            self._time.simulation_period
        if self._destinations[node_id]['pause time'] <= 0:
            self._destinations[node_id]['pause time'] = None
        else:
            if __debug__ and self.logger.isEnabledFor('DEBUG'):
                msg = 'The node #%d is still in its destination position' \
                      ' (%f, %f) with the pause time equal to %f'
                self.logger.debug(msg %
                    (node_id, node_coordinates[0], node_coordinates[1],
                     self._destinations[node_id]['pause time']))
        return self._destinations[node_id]['pause time']

    def get_current_position(self, node_id, node_speed, node_coordinates):
        """
        Calculates and returns a node's position at the current simulation step
        in accordance with the Random Waypoint mobility model.

        A distance of the route traveled by the node, between the current and
        previous simulation steps, is calculated as the product of the current
        node's speed and the *simulation period* (see: :mod:`sim2net._time`
        module).  Therefore, it is assumed that this method is called at every
        simulation step.

        *Parameters*:
            - **node_id** (`int`): an identifier of the node;
            - **node_speed**: an object representing the node's speed;
            - **node_coordinates** (`list`): values of the node's horizontal
              and vertical coordinates at the previous simulation step.

        *Returns*:
            A tuple containing current values of the node's horizontal and
            vertical coordinates.
        """
        # pause time?
        if self._destinations[node_id]['pause time'] is not None:
            if self._pause(node_id, node_coordinates) is None:
                self._assign_new_destination(node_id, node_speed)
            return node_coordinates
        # movement
        coordinates = self._step_move(node_id, node_speed, node_coordinates)
        assert 0 <= coordinates[0] <= self._area.width \
               and 0 <= coordinates[1] <= self._area.height, \
               'The new coordinates (%f, %f) exceed dimensions of the' \
               ' simulation area!' % coordinates
        if (coordinates[0] == self._destinations[node_id]['destination'][0]
            and
            coordinates[1] == self._destinations[node_id]['destination'][1]):
            # print "%.30f    %.30f" % (coordinates[0], coordinates[1])
            if self._assign_new_pause_time(node_id) is not None:
                self._destinations[node_id]['destination'] = [None, None]
            else:
                self._assign_new_destination(node_id, node_speed)
        elif __debug__ and self.logger.isEnabledFor('DEBUG'):
            msg = 'The current position of the node #%d is (%f, %f) with the' \
                  ' current speed equal to %f'
            self.logger.debug(msg % (node_id, coordinates[0], coordinates[1],
                                     fabs(node_speed.current)))
        # print "%.30f    %.30f" % (coordinates[0], coordinates[1])
        return coordinates class GilbertElliott(PacketLoss):
    """
    This class implements the Gilbert-Elliott packet loss model.
    """

    #: Default value of the `p` parameter.
    __DEFAULT_P = 0.00001333

    #: Default value of the `r` parameter.
    __DEFAULT_R = 0.00601795

    #: Default value of the `h` parameter.
    __DEFAULT_H = 0.55494900

    #: Default value of the `k` parameter.
    __DEFAULT_K = 0.99999900


    def __init__(self, prhk=None):
        """
        *Parameters*:
            - **prhk** (`tuple`): a `tuple` that contains four model
              parameters: :math:`0\\leqslant p,r,h,k\\leqslant 1`, respectively
              (each of type `float`).  The parameters default to the following
              values:

              * :math:`p=0.00001333`,
              * :math:`r=0.00601795`,
              * :math:`h=0.55494900`,
              * :math:`k=0.99999900`;

              (which leads to error rate equal to :math:`0.098\\%` and the mean
              packet loss rate equal to :math:`0.1\\%` ([HH08]_)).

        *Raises*:
            - **ValueError**: raised when the given value any model parameter
              is less than zero or greater that one.

        (At the beginning the model is in the ``G`` state.)
        """
        super(GilbertElliott, self).__init__(PacketLoss.__name__)
        if prhk is None:
            p = float(GilbertElliott.__DEFAULT_P)
            r = float(GilbertElliott.__DEFAULT_R)
            b = float(1.0 - GilbertElliott.__DEFAULT_H)
            g = float(1.0 - GilbertElliott.__DEFAULT_K)
        else:
            for param in range(4):
                check_argument_type(GilbertElliott.__name__,
                                    'prhk[' + str(param) + ']', float,
                                    prhk[param], self.logger)
                if prhk[param] < 0.0 or prhk[param] > 1.0:
                    raise ValueError('Parameter "prhk[%d]": a value of the' \
                                     ' model parameter cannot be less than' \
                                     ' zero and greater than one but %f given!'
                                     % (param, float(prhk[param])))
            p = float(prhk[0])
            r = float(prhk[1])
            b = float(1.0 - prhk[2])
            g = float(1.0 - prhk[3])
        # ( current state: 'G' or 'B',
        #   transition probability,
        #   current packet error rate )
        self.__state_g = ('G', p, g)
        self.__state_b = ('B', r, b)
        self.__current_state = self.__state_g

    def packet_loss(self):
        """
        Returns information about whether a transmitted packet has been lost or
        can be successfully received by destination node(s) according to the
        Gilbert-Elliott packet loss model.

        *Returns*:
            (`bool`) `True` if the packet has been lost, or `False` otherwise.
        """
        transition = self.random_generator.uniform(0.0, 1.0)
        if transition <= self.__current_state[1]:
            if self.__current_state[0] == 'G':
                self.__current_state = self.__state_b
            else:
                self.__current_state = self.__state_g
        loss = self.random_generator.uniform(0.0, 1.0)
        if loss <= self.__current_state[2]:
            return True
        return False class Grid(Placement):
    """
    This class implements the grid placement model, in which a given number of
    nodes are placed at intersections of a square or rectangular grid within a
    simulator area.
    """

    def __init__(self, area, nodes_number, transmission_range):
        """
        *Parameters*:
            - **area**: an object representing the simulation area;
            - **nodes_number** (`int`): a number of nodes to place within the
              simulation area;
            - **transmission_range** (`float`): a value of the transmission (or
              communication) radius of nodes, that is, the distance from a
              transmitter at which the signal strength remains above the
              minimum usable level.

        *Raises*:
            - **ValueError**: raised when: the given number of nodes or
              transmission range is less or equal to 0, or when the given value
              of the *area* parameter is `None`.
        """
        if area is None:
            raise ValueError('Parameter "area": a simulation area object'
                             ' expected but "None" value given!')
        super(Grid, self).__init__(Grid.__name__)
        self.__area = area
        check_argument_type(Grid.__name__, 'nodes_number', int, nodes_number,
                            self.logger)
        if nodes_number <= 0:
            raise ValueError('Parameter "nodes_number": the number of nodes'
                             ' cannot be less or equal to zero but %d given!' %
                             int(nodes_number))
        self.__nodes_number = int(nodes_number)
        check_argument_type(Grid.__name__, 'transmission_range', float,
                            transmission_range, self.logger)
        if transmission_range <= 0.0:
            raise ValueError('Parameter "transmission_range": a value of the'
                             ' transmission range cannot be less or equal to'
                             ' 0 but %f given!' % float(transmission_range))
        self.__transmission_range = float(transmission_range)

    def __adjust_grid_dimensions(self, columns, rows):
        """
        Adjusts the given grid dimensions to the size of the simulation area.
        If the area shape is square and the grid shape is rectangular, the
        longer side of the grid is placed along the horizontal x-axis of the
        simulation area.  If both shapes are rectangular, the longer side of
        the grid is placed along the longer size of the simulation area.

        *Parameters*:
            - **columns** (`int`): a number of grid columns;
            - **rows** (`int`): a number of grid rows.

        *Returns*:
            A number of grid columns and rows as a tuple.
        """
        if columns == rows:
            return (columns, rows)
        if self.__area.width == self.__area.height:
            if columns > rows:
                return (columns, rows)
            else:
                return (rows, columns)
        if self.__area.width > self.__area.height:
            if columns > rows:
                return (columns, rows)
            else:
                return (rows, columns)
        if self.__area.width < self.__area.height:
            if columns > rows:
                return (rows, columns)
            else:
                return (columns, rows)

    def __get_grid_dimensions(self):
        """
        Calculates dimensions of the grid based on the number of nodes.  If the
        number has a square root, the grid shape will be a square, otherwise it
        will be a rectangular.  In the worst case if the number of nodes is
        prime, the number of rows (or columns) will be equal to one.

        *Returns*:
            A number of grid columns and rows as a tuple.
        """
        if self.__nodes_number is None or self.__nodes_number <= 0:
            return (0.0, 0.0)
        dimensions = modf(sqrt(self.__nodes_number))
        if dimensions[0] == 0:
            return (int(dimensions[1]), int(dimensions[1]))
        for value in range(int(dimensions[1]), 0, -1):
            if modf(float(self.__nodes_number) / float(value))[0] == 0:
                return \
                    self.__adjust_grid_dimensions(value,
                                                  self.__nodes_number / value)

    def __get_nodes_distance(self, columns, rows):
        """
        Calculates a distance between nodes in the same row and column based on
        the their transmission ranges.  The distance is also adjust to fit the
        dimensions of the simulation area.

        *Returns*:
            A distance between nodes in the grid of type `float`.
        """
        distance = sqrt(0.5 * (self.__transmission_range *
                               self.__transmission_range))
        if distance * (columns + 1) > self.__area.width:
            distance = float(self.__area.width) / (float(columns) + 1.0)
        if distance * (rows + 1) > self.__area.height:
            distance = float(self.__area.height) / (float(rows) + 1.0)
        return distance

    def __get_horizontal_coordinates(self, columns, rows, distance):
        """
        Generates horizontal coordinates of nodes based on the number of
        columns, rows and the distance between nodes.

        *Returns*:
            A list of horizontal coordinates.
        """
        coordinates = []
        offset = 0.5 * (self.__area.width - ((columns + 1) * distance))
        for row in range(0, rows):
            coordinate = offset
            for column in range(0, columns):
                coordinate = coordinate + distance
                assert 0 <= coordinate <= self.__area.width, \
                    'The computed horizontal coordinate %f exceeds' \
                    ' dimensions of the simulation area!' % coordinate
                coordinates.append(coordinate)
        return coordinates

    def __get_vertical_coordinates(self, columns, rows, distance):
        """
        Generates vertical coordinates of nodes based on the number of
        columns, rows and the distance between nodes.

        *Returns*:
            A list of vertical coordinates.
        """
        coordinates = []
        offset = 0.5 * (self.__area.height - ((rows + 1) * distance))
        coordinate = offset
        for row in range(0, rows):
            coordinate = coordinate + distance
            assert 0 <= coordinate <= self.__area.height, \
                'The computed vertical coordinate %f exceeds dimensions' \
                ' of the simulation area!' % coordinate
            for column in range(0, columns):
                coordinates.append(coordinate)
        return coordinates

    def get_placement(self):
        """
        Generates grid placement coordinates for the given number of nodes and
        its transmission ranges and returns the result as a dictionary.

        *Returns*:
            A list of tuples of horizontal and vertical coordinates for each
            host.
        """
        columns, rows = self.__get_grid_dimensions()
        distance = self.__get_nodes_distance(columns, rows)
        horizontal_coordinates = \
            self.__get_horizontal_coordinates(columns, rows, distance)
        vertical_coordinates = \
            self.__get_vertical_coordinates(columns, rows, distance)
        self.logger.debug('Initial placement coordinates has been'
                          ' generated: %d nodes within %dx%d %s simulation'
                          ' area' %
                          (self.__nodes_number, self.__area.width,
                           self.__area.height,
                           self.__area.__class__.__name__.lower()))
        return zip(horizontal_coordinates, vertical_coordinates) class Normal(Placement):
    """
    This class implements the normal placement model, in which a given number
    of nodes are placed over a simulation area with the normal probability
    distribution.
    """

    def __init__(self, area, nodes_number, standard_deviation=0.2):
        """
        *Parameters*:
            - **area**: an object representing the simulation area;
            - **nodes_number** (`int`): a number of nodes to place over the
              simulation area;
            - **standard_deviation** (`float`): a value of the standard
              deviation (default: `0.2`).

        *Raises*:
            - **ValueError**: raised when the number of nodes is less or equal
              to 0, or when the given value of the *area* parameter is `None`.
        """
        if area is None:
            raise ValueError('Parameter "area": a simulation area object'
                             ' expected but "None" value given!')
        super(Normal, self).__init__(Normal.__name__)
        self.__area = area
        check_argument_type(Normal.__name__, 'nodes_number', int, nodes_number,
                            self.logger)
        if nodes_number <= 0:
            raise ValueError('Parameter "nodes_number": the number of nodes'
                             ' cannot be less or equal to zero but %d given!' %
                             int(nodes_number))
        self.__nodes_number = int(nodes_number)
        check_argument_type(Normal.__name__, 'standard_deviation', float,
                            standard_deviation, self.logger)
        self.__standard_deviation = float(standard_deviation)

    def get_placement(self):
        """
        Generates normal (Gaussian) placement coordinates for the given number
        of nodes and returns the result as a dictionary.

        The means used here are computed as follows: :math:`\\frac{1}{2}\\times
        area~width` and :math:`\\frac{1}{2}\\times area~height`.

        *Returns*:
            A list of tuples of horizontal and vertical coordinates for each
            host.
        """
        if self.__area is None or self.__nodes_number is None \
                or self.__standard_deviation is None:
            return None
        while True:
            horizontal_coordinates = []
            while True:
                coordinate = self.random_generator.normal(
                    float(self.__area.width) / 2.0, self.__standard_deviation)
                if 0 <= coordinate <= self.__area.width:
                    horizontal_coordinates.append(coordinate)
                if len(horizontal_coordinates) == self.__nodes_number:
                    break
            vertical_coordinates = []
            while True:
                coordinate = self.random_generator.normal(
                    float(self.__area.height) / 2.0, self.__standard_deviation)
                if 0 <= coordinate <= self.__area.height:
                    vertical_coordinates.append(coordinate)
                if len(vertical_coordinates) == self.__nodes_number:
                    break
            if Placement.position_conflict(horizontal_coordinates,
                                           vertical_coordinates) == -1:
                break
        self.logger.debug('Initial placement coordinates has been'
                          ' generated: %d nodes within %dx%d %s simulation'
                          ' area' %
                          (self.__nodes_number, self.__area.width,
                           self.__area.height,
                           self.__area.__class__.__name__.lower()))
        return zip(horizontal_coordinates, vertical_coordinates) class Uniform(Placement):
    """
    This class implements implements the uniform placement model, in which  a
    given number of nodes are placed over a simulation area with the uniform
    probability distribution.
    """

    def __init__(self, area, nodes_number):
        """
        *Parameters*:
            - **area**: an object representing the simulation area;
            - **nodes_number** (`int`): a number of nodes to place over the
              simulation area.

        *Raises*:
            - **ValueError**: raised when the number of nodes is less or equal
              to 0, or when the given value of the *area* parameter is `None`.
        """
        if area is None:
            raise ValueError('Parameter "area": a simulation area object'
                             ' expected but "None" value given!')
        super(Uniform, self).__init__(Uniform.__name__)
        self.__area = area
        check_argument_type(Uniform.__name__, 'nodes_number', int,
                            nodes_number, self.logger)
        if nodes_number <= 0:
            raise ValueError('Parameter "nodes_number": the number of nodes'
                             ' cannot be less or equal to zero but %d given!' %
                             int(nodes_number))
        self.__nodes_number = int(nodes_number)

    def get_placement(self):
        """
        Generates uniform placement coordinates for the given number
        of nodes and returns the result as a dictionary.

        *Returns*:
            A list of tuples of horizontal and vertical coordinates for each
            host.
        """
        if self.__area is None or self.__nodes_number is None:
            return None
        while True:
            horizontal_coordinates = \
                [self.random_generator.uniform(0, self.__area.width)
                    for coordinate in range(0, self.__nodes_number)]
            vertical_coordinates = \
                [self.random_generator.uniform(0, self.__area.height)
                    for coordinate in range(0, self.__nodes_number)]
            if Placement.position_conflict(horizontal_coordinates,
                                           vertical_coordinates) == -1:
                break
        self.logger.debug('Initial placement coordinates has been'
                          ' generated: %d nodes within %dx%d %s simulation'
                          ' area' %
                          (self.__nodes_number, self.__area.width,
                           self.__area.height,
                           self.__area.__class__.__name__.lower()))
        return zip(horizontal_coordinates, vertical_coordinates) class PathLoss(Propagation):
    """
    This class implements simplified path loss model in which the
    signal-to-noise ration is calculated on the given value of the transmission
    range of nodes.
    """

    def __init__(self, transmission_range):
        """
        *Parameters*:
            - **transmission_range** (`float`): a value of the transmission (or
              communication) radius of nodes, that is, the distance from a
              transmitter at which the signal strength remains above the
              minimum usable level.

        *Raises*:
            - **ValueError**: raised when the given transmission range is less
              or equal to 0.
        """
        super(PathLoss, self).__init__(PathLoss.__name__)
        check_argument_type(PathLoss.__name__, 'transmission_range', float,
                            transmission_range, self.logger)
        if transmission_range <= 0.0:
            raise ValueError('Parameter "transmission_range": a value of the' \
                             ' transmission range cannot be less or equal to' \
                             ' 0 but %f given!' % float(transmission_range))
        self.__transmission_range = float(transmission_range)

    def __distance(self, source_coordinates, destination_coordinates):
        """
        Calculates the distance between source and destination nodes in
        Cartesian space.

        *Parameters*:
            - **source_coordinates** (`list`): values of the source node's
              horizontal and vertical coordinates at the current simulation
              step;
            - **destination_coordinates** (`list`): values of the destination
              node's horizontal and vertical coordinates at the current
              simulation step.

        *Returns*:
            The distance between source and destination nodes in Cartesian
            space of type `float`.
        """
        return \
            sqrt(pow(source_coordinates[0] - destination_coordinates[0], 2) \
                 + pow(source_coordinates[1] - destination_coordinates[1], 2))

    def get_neighbors(self, coordinates):
        """
        Calculates identifiers of all nodes in a network that would be able to
        receive a wireless signal transmitted from a source node, according to
        the implemented propagation model.  All nodes in the network are
        considered, one by one, as the source node.

        *Parameters*:
           - **coordinates** (`list`): a list of coordinates of all nodes in
             the simulated network at the current simulation step.

        *Returns*:
            A `list` that in position ``i`` is a list of all nodes that would
            be able to receive a wireless signal transmitted by a node whose
            identifier is equal to ``i``.

        *Examples*:

        .. testsetup::

            from sim2net.propagation.path_loss import PathLoss

        .. doctest::

            >>> pathloss = PathLoss(1.0)
            >>> coordinates = [[1.0, 2.0], [1.5, 2.5], [2.0, 3.0], [2.5, 3.5]]
            >>> print pathloss.get_neighbors(coordinates)
            [[1], [0, 2], [1, 3], [2]]
            >>> coordinates = [[1.0, 2.0], [1.1, 2.1], [1.2, 2.2], [1.3, 2.3]]
            >>> print pathloss.get_neighbors(coordinates)
            [[1, 2, 3], [0, 2, 3], [0, 1, 3], [0, 1, 2]]
        """
        neighbors = list()
        for node in range(0, len(coordinates)):
            neighbors.append(list())
        for source_node in range(0, len(coordinates)):
            for destination_node in range(source_node + 1, len(coordinates)):
                if self.__distance(coordinates[source_node],
                                   coordinates[destination_node]) \
                   <= self.__transmission_range:
                    neighbors[source_node].append(destination_node)
                    neighbors[destination_node].append(source_node)
        if __debug__ and self.logger.isEnabledFor('DEBUG'):
            self.logger.debug('Neighboring nodes has been computed for %d' \
                              ' nodes' % len(coordinates))
        return neighbors class Constant(Speed):
    """
    This class implements a constant node speed fixed at a given value.
    """

    def __init__(self, speed):
        """
        *Parameters*:
            - **speed** (`float`): a value of the node speed.

        *Example*:

        .. testsetup::

            from sim2net.speed.constant import Constant

        .. doctest::

            >>> speed = Constant(5.0)
            >>> speed.current
            5.0
            >>> speed.get_new()
            5.0
            >>> speed = Constant(-5.0)
            >>> speed.current
            5.0
            >>> speed.get_new()
            5.0
        """
        super(Constant, self).__init__(Constant.__name__)
        check_argument_type(Constant.__name__, 'speed', float, speed,
                            self.logger)
        self.__current_speed = fabs(float(speed))

    @property
    def current(self):
        """
        (*Property*)  The absolute value of the current speed of type `float`.
        """
        return self.__current_speed

    def get_new(self):
        """
        Returns the absolute value of the given node speed of type `float`.
        """
        return self.current class Normal(Speed):
    """
    This class implements the normal speed distribution that assigns node's
    speeds with the Gaussian probability distribution.
    """

    def __init__(self, mean=0.0, standard_deviation=0.2):
        """
        (Defaults to **standard normal distribution**.)

        *Parameters*:
            - **mean** (`float`): a value of the expectation (default: `0.0`);
            - **standard_deviation** (`float`): a value of the standard
              deviation (default: `0.2`).
        """
        super(Normal, self).__init__(Normal.__name__)
        check_argument_type(Normal.__name__, 'mean', float, mean, self.logger)
        self.__mean = float(mean)
        check_argument_type(Normal.__name__, 'standard_deviation', float,
                            standard_deviation, self.logger)
        self.__standard_deviation = float(standard_deviation)
        self.__current_speed = None
        self.get_new()

    @property
    def mean(self):
        """
        (*Property*)  A value of the expectation of type `float`.
        """
        return self.__mean

    @property
    def current(self):
        """
        (*Property*)  A value of the current speed of type `float` (or `None`
        if the value has yet not been assigned).
        """
        return self.__current_speed

    def get_new(self):
        """
        Assigns a new speed value.

        .. warning::

            Depending on distribution parameters, negative values may be
            randomly selected.

        *Returns*:
            (`float`) the absolute value of a new speed.
        """
        self.__current_speed = \
            self.random_generator.normal(self.__mean,
                                         self.__standard_deviation)
        return self.__current_speed class Uniform(Speed):
    """
    This class implements the uniform speed distribution that assigns node's
    speeds from a given range with equal probability.
    """

    def __init__(self, minimal_speed, maximal_speed):
        """
        *Parameters*:
            - **minimal_speed** (`float`): a value of a node's minimal speed;
            - **maximal_speed** (`float`): a value of a node's maximal speed.
        """
        super(Uniform, self).__init__(Uniform.__name__)
        check_argument_type(Uniform.__name__, 'minimal_speed', float,
                            minimal_speed, self.logger)
        self.__minimal_speed = float(minimal_speed)
        check_argument_type(Uniform.__name__, 'maximal_speed', float,
                            maximal_speed, self.logger)
        self.__maximal_speed = float(maximal_speed)
        if self.__minimal_speed == self.__maximal_speed:
            self.__current_speed = float(self.__minimal_speed)
        else:
            self.__current_speed = None
            self.get_new()

    @property
    def current(self):
        """
        (*Property*)  A value of the current speed of type `float` (or `None`
        if the value has yet not been assigned).
        """
        return self.__current_speed

    def get_new(self):
        """
        Assigns a new speed value.

        .. warning::

            Depending on distribution parameters, negative values may be
            randomly selected.

        *Returns*:
            (`float`) the absolute value of a new speed.
        """
        if self.__minimal_speed == self.__maximal_speed:
            return self.__current_speed
        self.__current_speed = \
            self.random_generator.uniform(self.__minimal_speed,
                                          self.__maximal_speed)
        return self.__current_speed
from sim2net.area.rectangle import Rectangle
from sim2net.area.square import Square
from sim2net.failure.crash import Crash
from sim2net.mobility.gauss_markov import GaussMarkov
from sim2net.mobility.nomadic_community import NomadicCommunity
from sim2net.mobility.random_direction import RandomDirection
from sim2net.mobility.random_waypoint import RandomWaypoint
from sim2net.packet_loss.gilbert_elliott import GilbertElliott
from sim2net.placement.grid import Grid as PlacementGrid
from sim2net.placement.normal import Normal as PlacementNormal
from sim2net.placement.uniform import Uniform as PlacementUniform
from sim2net.propagation.path_loss import PathLoss
from sim2net.speed.constant import Constant as SpeedConstant
from sim2net.speed.normal import Normal as SpeedNormal
from sim2net.speed.uniform import Uniform as SpeedUniform
# -*- coding: utf-8 -*


# For bug reports, feature and support requests please visit
# <https://github.com/mkalewski/sim2net/issues>.

"""
sim2net -- simulation configuration file.

If in any doubt, refer to the technical documentations that is available on the
Internet:  <https://sim2net.readthedocs.org/en/latest/>.
"""




## EDIT BELOW -----------------------------------------------------------------

## One of: 'critical', 'error', 'warning', 'info', or 'debug'
logger_level = 'warning'

## Simulation frequency:
simulation_frequency = 4

## Total simulation steps:
total_simulation_steps = 40

## Number of nodes:
nodes_number = 2

## area
area = [Square, {'side': 100.0}]
#area = [Rectangle, {'width':100.0, 'height': 100.0}]

## placement
placement = [PlacementGrid]
#placement = [PlacementNormal, {'standard_deviation': 0.2}]
#placement = [PlacementUniform]

## speed

speed = [SpeedConstant, {'speed': 1.0}]