def generate_transition_matrix(g, seed=None):
    """Generates a random transition matrix for the graph ``g``.

    Parameters
    ----------
    g : :any:`networkx.DiGraph`, :class:`numpy.ndarray`, dict, etc.
        Any object that :any:`DiGraph<networkx.DiGraph>` accepts.
    seed : int (optional)
        An integer used to initialize numpy's psuedo-random number
        generator.

    Returns
    -------
    mat : :class:`~numpy.ndarray`
        Returns a transition matrix where ``mat[i, j]`` is the
        probability of transitioning from vertex ``i`` to vertex ``j``.
        If there is no edge connecting vertex ``i`` to vertex ``j``
        then ``mat[i, j] = 0``.
    """
    g = _test_graph(g)

    if isinstance(seed, numbers.Integral):
        np.random.seed(seed)

    nV = g.number_of_nodes()
    mat = np.zeros((nV, nV))

    for v in g.nodes():
        ind = [e[1] for e in sorted(g.out_edges(v))]
        deg = len(ind)
        if deg == 1:
            mat[v, ind] = 1
        elif deg > 1:
            probs = np.ceil(np.random.rand(deg) * 100) / 100.
            if np.isclose(np.sum(probs), 0):
                probs[np.random.randint(deg)] = 1

            mat[v, ind] = probs / np.sum(probs)

    return mat
def generate_transition_matrix(g, seed=None):
    """Generates a random transition matrix for the graph ``g``.

    Parameters
    ----------
    g : :any:`networkx.DiGraph`, :class:`numpy.ndarray`, dict, etc.
        Any object that :any:`DiGraph<networkx.DiGraph>` accepts.
    seed : int (optional)
        An integer used to initialize numpy's psuedo-random number
        generator.

    Returns
    -------
    mat : :class:`~numpy.ndarray`
        Returns a transition matrix where ``mat[i, j]`` is the
        probability of transitioning from vertex ``i`` to vertex ``j``.
        If there is no edge connecting vertex ``i`` to vertex ``j``
        then ``mat[i, j] = 0``.
    """
    g = _test_graph(g)

    if isinstance(seed, numbers.Integral):
        np.random.seed(seed)

    nV = g.number_of_nodes()
    mat = np.zeros((nV, nV))

    for v in g.nodes():
        ind = [e[1] for e in g.out_edges(v)]
        deg = len(ind)
        if deg == 1:
            mat[v, ind] = 1
        elif deg > 1:
            probs = np.ceil(np.random.rand(deg) * 100) / 100.
            if np.isclose(np.sum(probs), 0):
                probs[np.random.randint(deg)] = 1

            mat[v, ind] = probs / np.sum(probs)

    return mat
def add_edge_lengths(g):
    """Add add the edge lengths as a :any:`DiGraph<networkx.DiGraph>`
    for the graph.

    Uses the ``pos`` vertex property to get the location of each
    vertex. These are then used to calculate the length of an edge
    between two vertices.

    Parameters
    ----------
    g : :any:`networkx.DiGraph`, :class:`numpy.ndarray`, dict, \
        ``None``, etc.
        Any object that networkx can turn into a
        :any:`DiGraph<networkx.DiGraph>`

    Returns
    -------
    :class:`.QueueNetworkDiGraph`
        Returns the a graph with the ``edge_length`` edge property.

    Raises
    ------
    TypeError
        Raised when the parameter ``g`` is not of a type that can be
        made into a :any:`networkx.DiGraph`.

    """
    g = _test_graph(g)
    g.new_edge_property('edge_length')

    for e in g.edges():
        latlon1 = g.vp(e[1], 'pos')
        latlon2 = g.vp(e[0], 'pos')
        g.set_ep(e, 'edge_length',
                 np.round(_calculate_distance(latlon1, latlon2), 3))

    return g
def add_edge_lengths(g):
    """Add add the edge lengths as a :any:`DiGraph<networkx.DiGraph>`
    for the graph.

    Uses the ``pos`` vertex property to get the location of each
    vertex. These are then used to calculate the length of an edge
    between two vertices.

    Parameters
    ----------
    g : :any:`networkx.DiGraph`, :class:`numpy.ndarray`, dict, \
        ``None``, etc.
        Any object that networkx can turn into a
        :any:`DiGraph<networkx.DiGraph>`

    Returns
    -------
    :class:`.QueueNetworkDiGraph`
        Returns the a graph with the ``edge_length`` edge property.

    Raises
    ------
    TypeError
        Raised when the parameter ``g`` is not of a type that can be
        made into a :any:`networkx.DiGraph`.

    """
    g = _test_graph(g)
    g.new_edge_property('edge_length')

    for e in g.edges():
        latlon1 = g.vp(e[1], 'pos')
        latlon2 = g.vp(e[0], 'pos')
        g.set_ep(e, 'edge_length', np.round(_calculate_distance(latlon1, latlon2), 3))

    return g
def _prepare_graph(g, g_colors, q_cls, q_arg, adjust_graph):
    """Prepares a graph for use in :class:`.QueueNetwork`.

    This function is called by ``__init__`` in the
    :class:`.QueueNetwork` class. It creates the :class:`.QueueServer`
    instances that sit on the edges, and sets various edge and node
    properties that are used when drawing the graph.

    Parameters
    ----------
    g : :any:`networkx.DiGraph`, :class:`numpy.ndarray`, dict, \
        ``None``,  etc.
        Any object that networkx can turn into a
        :any:`DiGraph<networkx.DiGraph>`
    g_colors : dict
        A dictionary of colors. The specific keys used are
        ``vertex_color`` and ``vertex_fill_color`` for vertices that
        do not have any loops. Set :class:`.QueueNetwork` for the
        default values passed.
    q_cls : dict
        A dictionary where the keys are integers that represent an edge
        type, and the values are :class:`.QueueServer` classes.
    q_args : dict
        A dictionary where the keys are integers that represent an edge
        type, and the values are the arguments that are used when
        creating an instance of that :class:`.QueueServer` class.
    adjust_graph : bool
        Specifies whether the graph will be adjusted using
        :func:`.adjacency2graph`.

    Returns
    -------
    g : :class:`.QueueNetworkDiGraph`
    queues : list
        A list of :class:`QueueServers<.QueueServer>` where
        ``queues[k]`` is the ``QueueServer`` that sets on the edge with
        edge index ``k``.

    Notes
    -----
    The graph ``g`` should have the ``edge_type`` edge property map.
    If it does not then an ``edge_type`` edge property is
    created and set to 1.

    The following properties are set by each queue: ``vertex_color``,
    ``vertex_fill_color``, ``vertex_fill_color``, ``edge_color``.
    See :class:`.QueueServer` for more on setting these values.

    The following properties are assigned as a properties to the graph;
    their default values for each edge or vertex is shown:

        * ``vertex_pen_width``: ``1``,
        * ``vertex_size``: ``8``,
        * ``edge_control_points``: ``[]``
        * ``edge_marker_size``: ``8``
        * ``edge_pen_width``: ``1.25``

    Raises
    ------
    TypeError
        Raised when the parameter ``g`` is not of a type that can be
        made into a :any:`networkx.DiGraph`.
    """
    g = _test_graph(g)

    if adjust_graph:
        pos = nx.get_node_attributes(g, 'pos')
        ans = nx.to_dict_of_dicts(g)
        g = adjacency2graph(ans, adjust=2, is_directed=g.is_directed())
        g = QueueNetworkDiGraph(g)
        if len(pos) > 0:
            g.set_pos(pos)

    g.new_vertex_property('vertex_color')
    g.new_vertex_property('vertex_fill_color')
    g.new_vertex_property('vertex_pen_width')
    g.new_vertex_property('vertex_size')

    g.new_edge_property('edge_control_points')
    g.new_edge_property('edge_color')
    g.new_edge_property('edge_marker_size')
    g.new_edge_property('edge_pen_width')

    queues = _set_queues(g, q_cls, q_arg, 'cap' in g.vertex_properties())

    if 'pos' not in g.vertex_properties():
        g.set_pos()

    for k, e in enumerate(g.edges()):
        g.set_ep(e, 'edge_pen_width', 1.25)
        g.set_ep(e, 'edge_marker_size', 8)
        if e[0] == e[1]:
            g.set_ep(e, 'edge_color', queues[k].colors['edge_loop_color'])
        else:
            g.set_ep(e, 'edge_color', queues[k].colors['edge_color'])

    for v in g.nodes():
        g.set_vp(v, 'vertex_pen_width', 1)
        g.set_vp(v, 'vertex_size', 8)
        e = (v, v)
        if g.is_edge(e):
            g.set_vp(v, 'vertex_color',
                     queues[g.edge_index[e]]._current_color(2))
            g.set_vp(v, 'vertex_fill_color',
                     queues[g.edge_index[e]]._current_color())
        else:
            g.set_vp(v, 'vertex_color', g_colors['vertex_color'])
            g.set_vp(v, 'vertex_fill_color', g_colors['vertex_fill_color'])

    return g, queues
def _prepare_graph(g, g_colors, q_cls, q_arg, adjust_graph):
    """Prepares a graph for use in :class:`.QueueNetwork`.

    This function is called by ``__init__`` in the
    :class:`.QueueNetwork` class. It creates the :class:`.QueueServer`
    instances that sit on the edges, and sets various edge and node
    properties that are used when drawing the graph.

    Parameters
    ----------
    g : :any:`networkx.DiGraph`, :class:`numpy.ndarray`, dict, \
        ``None``,  etc.
        Any object that networkx can turn into a
        :any:`DiGraph<networkx.DiGraph>`
    g_colors : dict
        A dictionary of colors. The specific keys used are
        ``vertex_color`` and ``vertex_fill_color`` for vertices that
        do not have any loops. Set :class:`.QueueNetwork` for the
        default values passed.
    q_cls : dict
        A dictionary where the keys are integers that represent an edge
        type, and the values are :class:`.QueueServer` classes.
    q_args : dict
        A dictionary where the keys are integers that represent an edge
        type, and the values are the arguments that are used when
        creating an instance of that :class:`.QueueServer` class.
    adjust_graph : bool
        Specifies whether the graph will be adjusted using
        :func:`.adjacency2graph`.

    Returns
    -------
    g : :class:`.QueueNetworkDiGraph`
    queues : list
        A list of :class:`QueueServers<.QueueServer>` where
        ``queues[k]`` is the ``QueueServer`` that sets on the edge with
        edge index ``k``.

    Notes
    -----
    The graph ``g`` should have the ``edge_type`` edge property map.
    If it does not then an ``edge_type`` edge property is
    created and set to 1.

    The following properties are set by each queue: ``vertex_color``,
    ``vertex_fill_color``, ``vertex_fill_color``, ``edge_color``.
    See :class:`.QueueServer` for more on setting these values.

    The following properties are assigned as a properties to the graph;
    their default values for each edge or vertex is shown:

        * ``vertex_pen_width``: ``1``,
        * ``vertex_size``: ``8``,
        * ``edge_control_points``: ``[]``
        * ``edge_marker_size``: ``8``
        * ``edge_pen_width``: ``1.25``

    Raises
    ------
    TypeError
        Raised when the parameter ``g`` is not of a type that can be
        made into a :any:`networkx.DiGraph`.
    """
    g = _test_graph(g)

    if adjust_graph:
        pos = nx.get_node_attributes(g, 'pos')
        ans = nx.to_dict_of_dicts(g)
        g = adjacency2graph(ans, adjust=2, is_directed=g.is_directed())
        g = QueueNetworkDiGraph(g)
        if len(pos) > 0:
            g.set_pos(pos)

    g.new_vertex_property('vertex_color')
    g.new_vertex_property('vertex_fill_color')
    g.new_vertex_property('vertex_pen_width')
    g.new_vertex_property('vertex_size')

    g.new_edge_property('edge_control_points')
    g.new_edge_property('edge_color')
    g.new_edge_property('edge_marker_size')
    g.new_edge_property('edge_pen_width')

    queues = _set_queues(g, q_cls, q_arg, 'cap' in g.vertex_properties())

    if 'pos' not in g.vertex_properties():
        g.set_pos()

    for k, e in enumerate(g.edges()):
        g.set_ep(e, 'edge_pen_width', 1.25)
        g.set_ep(e, 'edge_marker_size', 8)
        if e[0] == e[1]:
            g.set_ep(e, 'edge_color', queues[k].colors['edge_loop_color'])
        else:
            g.set_ep(e, 'edge_color', queues[k].colors['edge_color'])

    for v in g.nodes():
        g.set_vp(v, 'vertex_pen_width', 1)
        g.set_vp(v, 'vertex_size', 8)
        e = (v, v)
        if g.is_edge(e):
            g.set_vp(v, 'vertex_color', queues[g.edge_index[e]]._current_color(2))
            g.set_vp(v, 'vertex_fill_color', queues[g.edge_index[e]]._current_color())
        else:
            g.set_vp(v, 'vertex_color', g_colors['vertex_color'])
            g.set_vp(v, 'vertex_fill_color', g_colors['vertex_fill_color'])

    return g, queues
def set_types_rank(g, rank, pType2=0.1, pType3=0.1, seed=None, **kwargs):
    """Creates a stylized graph. Sets edge and types using `pagerank`_.

    This function sets the edge types of a graph to be either 1, 2, or
    3. It sets the vertices to type 2 by selecting the top
    ``pType2 * g.number_of_nodes()`` vertices given by the
    :func:`~networkx.pagerank` of the graph. A loop is added
    to all vertices identified this way (if one does not exist
    already). It then randomly sets vertices close to the type 2
    vertices as type 3, and adds loops to these vertices as well. These
    loops then have edge types the correspond to the vertices type. The
    rest of the edges are set to type 1.

    .. _pagerank: http://en.wikipedia.org/wiki/PageRank

    Parameters
    ----------
    g : :any:`networkx.DiGraph`, :class:`~numpy.ndarray`, dict, etc.
        Any object that :any:`DiGraph<networkx.DiGraph>` accepts.
    rank : :class:`numpy.ndarray`
        An ordering of the vertices.
    pType2 : float (optional, default: 0.1)
        Specifies the proportion of vertices that will be of type 2.
    pType3 : float (optional, default: 0.1)
        Specifies the proportion of vertices that will be of type 3 and
        that are near pType2 vertices.
    seed : int (optional)
        An integer used to initialize numpy's psuedo-random number
        generator.
    **kwargs :
        Unused.

    Returns
    -------
    :class:`.QueueNetworkDiGraph`
        Returns the a graph with an ``edge_type`` edge property.

    Raises
    ------
    TypeError
        Raised when the parameter ``g`` is not of a type that can be
        made into a :any:`DiGraph<networkx.DiGraph>`.
    """
    g = _test_graph(g)

    if isinstance(seed, numbers.Integral):
        np.random.seed(seed)

    tmp = np.sort(np.array(rank))
    nDests = int(np.ceil(g.number_of_nodes() * pType2))
    dests = np.where(rank >= tmp[-nDests])[0]

    if 'pos' not in g.vertex_properties():
        g.set_pos()

    dest_pos = np.array([g.vp(v, 'pos') for v in dests])
    nFCQ = int(pType3 * g.number_of_nodes())
    min_g_dist = np.ones(nFCQ) * np.infty
    ind_g_dist = np.ones(nFCQ, int)

    r, theta = np.random.random(nFCQ) / 500., np.random.random(nFCQ) * 360.
    xy_pos = np.array([r * np.cos(theta), r * np.sin(theta)]).transpose()
    g_pos = xy_pos + dest_pos[np.array(np.mod(np.arange(nFCQ), nDests), int)]

    for v in g.nodes():
        if v not in dests:
            tmp = np.array([_calculate_distance(g.vp(v, 'pos'), g_pos[k, :]) for k in range(nFCQ)])
            min_g_dist = np.min((tmp, min_g_dist), 0)
            ind_g_dist[min_g_dist == tmp] = v

    ind_g_dist = np.unique(ind_g_dist)
    fcqs = set(ind_g_dist[:min(nFCQ, len(ind_g_dist))])
    dests = set(dests)
    g.new_vertex_property('loop_type')

    for v in g.nodes():
        if v in dests:
            g.set_vp(v, 'loop_type', 3)
            if not g.is_edge((v, v)):
                g.add_edge(v, v)
        elif v in fcqs:
            g.set_vp(v, 'loop_type', 2)
            if not g.is_edge((v, v)):
                g.add_edge(v, v)

    g.new_edge_property('edge_type')
    for e in g.edges():
        g.set_ep(e, 'edge_type', 1)

    for v in g.nodes():
        if g.vp(v, 'loop_type') in [2, 3]:
            e = (v, v)
            if g.vp(v, 'loop_type') == 2:
                g.set_ep(e, 'edge_type', 2)
            else:
                g.set_ep(e, 'edge_type', 3)

    return g
def set_types_random(g, proportions=None, loop_proportions=None, seed=None,
                     **kwargs):
    """Randomly sets ``edge_type`` (edge type) properties of the graph.

    This function randomly assigns each edge a type. The probability of
    an edge being a specific type is proscribed in the
    ``proportions``, ``loop_proportions`` variables.

    Parameters
    ----------
    g : :any:`networkx.DiGraph`, :class:`numpy.ndarray`, dict, etc.
        Any object that :any:`DiGraph<networkx.DiGraph>` accepts.
    proportions : dict (optional, default: ``{k: 0.25 for k in range(1, 4)}``)
        A dictionary of edge types and proportions, where the keys are
        the types and the values are the proportion of non-loop edges
        that are expected to be of that type. The values can must sum
        to one.
    loop_proportions : dict (optional, default: ``{k: 0.25 for k in range(4)}``)
        A dictionary of edge types and proportions, where the keys are
        the types and the values are the proportion of loop edges
        that are expected to be of that type. The values can must sum
        to one.
    seed : int (optional)
        An integer used to initialize numpy's psuedorandom number
        generator.
    **kwargs :
        Unused.

    Returns
    -------
    :class:`.QueueNetworkDiGraph`
        Returns the a graph with an ``edge_type`` edge property.

    Raises
    ------
    TypeError
        Raised when the parameter ``g`` is not of a type that can be
        made into a :any:`networkx.DiGraph`.

    ValueError
        Raises a :exc:`~ValueError` if the ``pType`` values do not sum
        to one.

    Notes
    -----
    If ``pTypes`` is not explicitly specified in the arguments, then it
    defaults to four types in the graph (types 0, 1, 2, and 3). It sets
    non-loop edges to be either 1, 2, or 3 33\% chance, and loops are
    types 0, 1, 2, 3 with 25\% chance.
    """
    g = _test_graph(g)

    if isinstance(seed, numbers.Integral):
        np.random.seed(seed)

    if proportions is None:
        proportions = {k: 1. / 3 for k in range(1, 4)}

    if loop_proportions is None:
        loop_proportions = {k: 1. / 4 for k in range(4)}

    edges = [e for e in g.edges() if e[0] != e[1]]
    loops = [e for e in g.edges() if e[0] == e[1]]
    props = list(proportions.values())
    lprops = list(loop_proportions.values())

    if not np.isclose(sum(props), 1.0):
        raise ValueError("proportions values must sum to one.")
    if not np.isclose(sum(lprops), 1.0):
        raise ValueError("loop_proportions values must sum to one.")

    eTypes = {}
    types = list(proportions.keys())
    values = np.random.choice(types, size=len(edges), replace=True, p=props)

    for k, e in enumerate(edges):
        eTypes[e] = values[k]

    types = list(loop_proportions.keys())
    values = np.random.choice(types, size=len(loops), replace=True, p=lprops)

    for k, e in enumerate(loops):
        eTypes[e] = values[k]

    g.new_edge_property('edge_type')
    for e in g.edges():
        g.set_ep(e, 'edge_type', eTypes[e])

    return g
def set_types_rank(g, rank, pType2=0.1, pType3=0.1, seed=None, **kwargs):
    """Creates a stylized graph. Sets edge and types using `pagerank`_.

    This function sets the edge types of a graph to be either 1, 2, or
    3. It sets the vertices to type 2 by selecting the top
    ``pType2 * g.number_of_nodes()`` vertices given by the
    :func:`~networkx.pagerank` of the graph. A loop is added
    to all vertices identified this way (if one does not exist
    already). It then randomly sets vertices close to the type 2
    vertices as type 3, and adds loops to these vertices as well. These
    loops then have edge types the correspond to the vertices type. The
    rest of the edges are set to type 1.

    .. _pagerank: http://en.wikipedia.org/wiki/PageRank

    Parameters
    ----------
    g : :any:`networkx.DiGraph`, :class:`~numpy.ndarray`, dict, etc.
        Any object that :any:`DiGraph<networkx.DiGraph>` accepts.
    rank : :class:`numpy.ndarray`
        An ordering of the vertices.
    pType2 : float (optional, default: 0.1)
        Specifies the proportion of vertices that will be of type 2.
    pType3 : float (optional, default: 0.1)
        Specifies the proportion of vertices that will be of type 3 and
        that are near pType2 vertices.
    seed : int (optional)
        An integer used to initialize numpy's psuedo-random number
        generator.
    **kwargs :
        Unused.

    Returns
    -------
    :class:`.QueueNetworkDiGraph`
        Returns the a graph with an ``edge_type`` edge property.

    Raises
    ------
    TypeError
        Raised when the parameter ``g`` is not of a type that can be
        made into a :any:`DiGraph<networkx.DiGraph>`.
    """
    g = _test_graph(g)

    if isinstance(seed, numbers.Integral):
        np.random.seed(seed)

    tmp = np.sort(np.array(rank))
    nDests = int(np.ceil(g.number_of_nodes() * pType2))
    dests = np.where(rank >= tmp[-nDests])[0]

    if 'pos' not in g.vertex_properties():
        g.set_pos()

    dest_pos = np.array([g.vp(v, 'pos') for v in dests])
    nFCQ = int(pType3 * g.number_of_nodes())
    min_g_dist = np.ones(nFCQ) * np.infty
    ind_g_dist = np.ones(nFCQ, int)

    r, theta = np.random.random(nFCQ) / 500., np.random.random(nFCQ) * 360.
    xy_pos = np.array([r * np.cos(theta), r * np.sin(theta)]).transpose()
    g_pos = xy_pos + dest_pos[np.array(np.mod(np.arange(nFCQ), nDests), int)]

    for v in g.nodes():
        if v not in dests:
            tmp = np.array([
                _calculate_distance(g.vp(v, 'pos'), g_pos[k, :])
                for k in range(nFCQ)
            ])
            min_g_dist = np.min((tmp, min_g_dist), 0)
            ind_g_dist[min_g_dist == tmp] = v

    ind_g_dist = np.unique(ind_g_dist)
    fcqs = set(ind_g_dist[:min(nFCQ, len(ind_g_dist))])
    dests = set(dests)
    g.new_vertex_property('loop_type')

    for v in g.nodes():
        if v in dests:
            g.set_vp(v, 'loop_type', 3)
            if not g.is_edge((v, v)):
                g.add_edge(v, v)
        elif v in fcqs:
            g.set_vp(v, 'loop_type', 2)
            if not g.is_edge((v, v)):
                g.add_edge(v, v)

    g.new_edge_property('edge_type')
    for e in g.edges():
        g.set_ep(e, 'edge_type', 1)

    for v in g.nodes():
        if g.vp(v, 'loop_type') in [2, 3]:
            e = (v, v)
            if g.vp(v, 'loop_type') == 2:
                g.set_ep(e, 'edge_type', 2)
            else:
                g.set_ep(e, 'edge_type', 3)

    return g
def set_types_random(g,
                     proportions=None,
                     loop_proportions=None,
                     seed=None,
                     **kwargs):
    """Randomly sets ``edge_type`` (edge type) properties of the graph.

    This function randomly assigns each edge a type. The probability of
    an edge being a specific type is proscribed in the
    ``proportions``, ``loop_proportions`` variables.

    Parameters
    ----------
    g : :any:`networkx.DiGraph`, :class:`numpy.ndarray`, dict, etc.
        Any object that :any:`DiGraph<networkx.DiGraph>` accepts.
    proportions : dict (optional, default: ``{k: 0.25 for k in range(1, 4)}``)
        A dictionary of edge types and proportions, where the keys are
        the types and the values are the proportion of non-loop edges
        that are expected to be of that type. The values can must sum
        to one.
    loop_proportions : dict (optional, default: ``{k: 0.25 for k in range(4)}``)
        A dictionary of edge types and proportions, where the keys are
        the types and the values are the proportion of loop edges
        that are expected to be of that type. The values can must sum
        to one.
    seed : int (optional)
        An integer used to initialize numpy's psuedorandom number
        generator.
    **kwargs :
        Unused.

    Returns
    -------
    :class:`.QueueNetworkDiGraph`
        Returns the a graph with an ``edge_type`` edge property.

    Raises
    ------
    TypeError
        Raised when the parameter ``g`` is not of a type that can be
        made into a :any:`networkx.DiGraph`.

    ValueError
        Raises a :exc:`~ValueError` if the ``pType`` values do not sum
        to one.

    Notes
    -----
    If ``pTypes`` is not explicitly specified in the arguments, then it
    defaults to four types in the graph (types 0, 1, 2, and 3). It sets
    non-loop edges to be either 1, 2, or 3 33\% chance, and loops are
    types 0, 1, 2, 3 with 25\% chance.
    """
    g = _test_graph(g)

    if isinstance(seed, numbers.Integral):
        np.random.seed(seed)

    if proportions is None:
        proportions = {k: 1. / 3 for k in range(1, 4)}

    if loop_proportions is None:
        loop_proportions = {k: 1. / 4 for k in range(4)}

    edges = [e for e in g.edges() if e[0] != e[1]]
    loops = [e for e in g.edges() if e[0] == e[1]]
    props = list(proportions.values())
    lprops = list(loop_proportions.values())

    if not np.isclose(sum(props), 1.0):
        raise ValueError("proportions values must sum to one.")
    if not np.isclose(sum(lprops), 1.0):
        raise ValueError("loop_proportions values must sum to one.")

    eTypes = {}
    types = list(proportions.keys())
    values = np.random.choice(types, size=len(edges), replace=True, p=props)

    for k, e in enumerate(edges):
        eTypes[e] = values[k]

    types = list(loop_proportions.keys())
    values = np.random.choice(types, size=len(loops), replace=True, p=lprops)

    for k, e in enumerate(loops):
        eTypes[e] = values[k]

    g.new_edge_property('edge_type')
    for e in g.edges():
        g.set_ep(e, 'edge_type', eTypes[e])

    return g