Пример #1
0
def eigenvector_centrality(G, max_iter=100, tol=1.0e-6, nstart=None,
                           weight='weight'):
    """Compute the eigenvector centrality for the graph G.

    Eigenvector centrality computes the centrality for a node based on the
    centrality of its neighbors. The eigenvector centrality for node `i` is

    .. math::

        \mathbf{Ax} = \lambda \mathbf{x}

    where `A` is the adjacency matrix of the graph G with eigenvalue `\lambda`.
    By virtue of the Perron–Frobenius theorem, there is a unique and positive
    solution if `\lambda` is the largest eigenvalue associated with the
    eigenvector of the adjacency matrix `A` ([2]_).

    Parameters
    ----------
    G : graph
      A networkx graph

    max_iter : integer, optional
      Maximum number of iterations in power method.

    tol : float, optional
      Error tolerance used to check convergence in power method iteration.

    nstart : dictionary, optional
      Starting value of eigenvector iteration for each node.

    weight : None or string, optional
      If None, all edge weights are considered equal.
      Otherwise holds the name of the edge attribute used as weight.

    Returns
    -------
    nodes : dictionary
       Dictionary of nodes with eigenvector centrality as the value.

    Examples
    --------
    >>> G = nx.path_graph(4)
    >>> centrality = nx.eigenvector_centrality(G)
    >>> print(['%s %0.2f'%(node,centrality[node]) for node in centrality])
    ['0 0.37', '1 0.60', '2 0.60', '3 0.37']

    See Also
    --------
    eigenvector_centrality_numpy
    pagerank
    hits

    Notes
    ------
    The measure was introduced by [1]_.

    The eigenvector calculation is done by the power iteration method and has
    no guarantee of convergence. The iteration will stop after ``max_iter``
    iterations or an error tolerance of ``number_of_nodes(G)*tol`` has been
    reached.

    For directed graphs this is "left" eigenvector centrality which corresponds
    to the in-edges in the graph. For out-edges eigenvector centrality
    first reverse the graph with ``G.reverse()``.

    References
    ----------
    .. [1] Phillip Bonacich:
       Power and Centrality: A Family of Measures.
       American Journal of Sociology 92(5):1170–1182, 1986
       http://www.leonidzhukov.net/hse/2014/socialnetworks/papers/Bonacich-Centrality.pdf
    .. [2] Mark E. J. Newman:
       Networks: An Introduction.
       Oxford University Press, USA, 2010, pp. 169.

    """
    from math import sqrt
    if type(G) == nx.MultiGraph or type(G) == nx.MultiDiGraph:
        raise nx.NetworkXException("Not defined for multigraphs.")

    if len(G) == 0:
        raise nx.NetworkXException("Empty graph.")

    if nstart is None:
        # choose starting vector with entries of 1/len(G)
        x = dict([(n,1.0/len(G)) for n in G])
    else:
        x = nstart
    # normalize starting vector
    s = 1.0/sum(x.values())
    for k in x:
        x[k] *= s
    nnodes = G.number_of_nodes()
    # make up to max_iter iterations
    for i in range(max_iter):
        xlast = x
        x = dict.fromkeys(xlast, 0)
        # do the multiplication y^T = x^T A
        for n in x:
            for nbr in G[n]:
                x[nbr] += xlast[n] * G[n][nbr].get(weight, 1)
        # normalize vector
        try:
            s = 1.0/sqrt(sum(v**2 for v in x.values()))
        # this should never be zero?
        except ZeroDivisionError:
            s = 1.0
        for n in x:
            x[n] *= s
        # check convergence
        err = sum([abs(x[n]-xlast[n]) for n in x])
        if err < nnodes*tol:
            return x

    raise nx.NetworkXError("""eigenvector_centrality():
power iteration failed to converge in %d iterations."%(i+1))""")
Пример #2
0
def eigenvector_centrality(G,max_iter=100,tol=1.0e-6,nstart=None):
    """Compute the eigenvector centrality for the graph G.

    Uses the power method to find the eigenvector for the 
    largest eigenvalue of the adjacency matrix of G.

    Parameters
    ----------
    G : graph
      A networkx graph 

    max_iter : interger, optional
      Maximum number of iterations in power method.

    tol : float, optional
      Error tolerance used to check convergence in power method iteration.

    nstart : dictionary, optional
      Starting value of eigenvector iteration for each node. 

    Returns
    -------
    nodes : dictionary
       Dictionary of nodes with eigenvector centrality as the value.

    Examples
    --------
    >>> G=nx.path_graph(4)
    >>> centrality=nx.eigenvector_centrality(G)
    >>> print(['%s %0.2f'%(node,centrality[node]) for node in centrality])
    ['0 0.37', '1 0.60', '2 0.60', '3 0.37']

    Notes
    ------
    The eigenvector calculation is done by the power iteration method
    and has no guarantee of convergence.  The iteration will stop
    after max_iter iterations or an error tolerance of
    number_of_nodes(G)*tol has been reached.

    For directed graphs this is "right" eigevector centrality.  For
    "left" eigenvector centrality, first reverse the graph with
    G.reverse().

    See Also
    --------
    eigenvector_centrality_numpy
    pagerank
    hits
    """
    from math import sqrt
    if type(G) == nx.MultiGraph or type(G) == nx.MultiDiGraph:
        raise Exception(\
            "eigenvector_centrality() not defined for multigraphs.")

    if len(G)==0:
        raise nx.NetworkXException(\
            "eigenvector_centrality_numpy(): empty graph.")

    if nstart is None:
        # choose starting vector with entries of 1/len(G) 
        x=dict([(n,1.0/len(G)) for n in G])
    else:
        x=nstart
    # normalize starting vector
    s=1.0/sum(x.values())
    for k in x: x[k]*=s
    nnodes=G.number_of_nodes()
    # make up to max_iter iterations        
    for i in range(max_iter):
        xlast=x
        x=dict.fromkeys(xlast, 0)
        # do the multiplication y=Ax
        for n in x:
            for nbr in G[n]:
                x[n]+=xlast[nbr]*G[n][nbr].get('weight',1)
        # normalize vector
        try:
            s=1.0/sqrt(sum(v**2 for v in x.values()))
        except ZeroDivisionError:
            s=1.0
        for n in x: x[n]*=s
        # check convergence            
        err=sum([abs(x[n]-xlast[n]) for n in x])
        if err < nnodes*tol:
            return x

    raise nx.NetworkXError("""eigenvector_centrality(): 
power iteration failed to converge in %d iterations."%(i+1))""")
Пример #3
0
def navigable_small_world_graph(n, p=1, q=1, r=2, dim=2, seed=None):
    r"""Returns a navigable small-world graph.

    A navigable small-world graph is a directed grid with additional long-range
    connections that are chosen randomly.

      [...] we begin with a set of nodes [...] that are identified with the set
      of lattice points in an $n \times n$ square,
      $\{(i, j): i \in \{1, 2, \ldots, n\}, j \in \{1, 2, \ldots, n\}\}$,
      and we define the *lattice distance* between two nodes $(i, j)$ and
      $(k, l)$ to be the number of "lattice steps" separating them:
      $d((i, j), (k, l)) = |k - i| + |l - j|$.

      For a universal constant $p >= 1$, the node $u$ has a directed edge to
      every other node within lattice distance $p$---these are its *local
      contacts*. For universal constants $q >= 0$ and $r >= 0$ we also
      construct directed edges from $u$ to $q$ other nodes (the *long-range
      contacts*) using independent random trials; the $i$th directed edge from
      $u$ has endpoint $v$ with probability proportional to $[d(u,v)]^{-r}$.

      -- [1]_

    Parameters
    ----------
    n : int
        The length of one side of the lattice; the number of nodes in
        the graph is therefore $n^2$.
    p : int
        The diameter of short range connections. Each node is joined with every
        other node within this lattice distance.
    q : int
        The number of long-range connections for each node.
    r : float
        Exponent for decaying probability of connections.  The probability of
        connecting to a node at lattice distance $d$ is $1/d^r$.
    dim : int
        Dimension of grid
    seed : integer, random_state, or None (default)
        Indicator of random number generation state.
        See :ref:`Randomness<randomness>`.

    References
    ----------
    .. [1] J. Kleinberg. The small-world phenomenon: An algorithmic
       perspective. Proc. 32nd ACM Symposium on Theory of Computing, 2000.
    """
    if p < 1:
        raise nx.NetworkXException("p must be >= 1")
    if q < 0:
        raise nx.NetworkXException("q must be >= 0")
    if r < 0:
        raise nx.NetworkXException("r must be >= 1")

    G = nx.DiGraph()
    nodes = list(product(range(n), repeat=dim))
    for p1 in nodes:
        probs = [0]
        for p2 in nodes:
            if p1 == p2:
                continue
            d = sum((abs(b - a) for a, b in zip(p1, p2)))
            if d <= p:
                G.add_edge(p1, p2)
            probs.append(d**-r)
        cdf = list(accumulate(probs))
        for _ in range(q):
            target = nodes[bisect_left(cdf, seed.uniform(0, cdf[-1]))]
            G.add_edge(p1, target)
    return G
Пример #4
0
def eigenvector_centrality_numpy(G, weight='weight'):
    """Compute the eigenvector centrality for the graph G.

    Eigenvector centrality computes the centrality for a node based on the
    centrality of its neighbors. The eigenvector centrality for node `i` is

    .. math::

        \mathbf{Ax} = \lambda \mathbf{x}

    where `A` is the adjacency matrix of the graph G with eigenvalue `\lambda`.
    By virtue of the Perron–Frobenius theorem, there is a unique and positive
    solution if `\lambda` is the largest eigenvalue associated with the
    eigenvector of the adjacency matrix `A` ([2]_).

    Parameters
    ----------
    G : graph
      A networkx graph

    weight : None or string, optional
      The name of the edge attribute used as weight.
      If None, all edge weights are considered equal.

    Returns
    -------
    nodes : dictionary
       Dictionary of nodes with eigenvector centrality as the value.

    Examples
    --------
    >>> G = nx.path_graph(4)
    >>> centrality = nx.eigenvector_centrality_numpy(G)
    >>> print(['%s %0.2f'%(node,centrality[node]) for node in centrality])
    ['0 0.37', '1 0.60', '2 0.60', '3 0.37']

    See Also
    --------
    eigenvector_centrality
    pagerank
    hits

    Notes
    ------
    The measure was introduced by [1]_.

    This algorithm uses the SciPy sparse eigenvalue solver (ARPACK) to
    find the largest eigenvalue/eigenvector pair.

    For directed graphs this is "left" eigenvector centrality which corresponds
    to the in-edges in the graph. For out-edges eigenvector centrality
    first reverse the graph with G.reverse().

    References
    ----------
    .. [1] Phillip Bonacich:
       Power and Centrality: A Family of Measures.
       American Journal of Sociology 92(5):1170–1182, 1986
       http://www.leonidzhukov.net/hse/2014/socialnetworks/papers/Bonacich-Centrality.pdf
    .. [2] Mark E. J. Newman:
       Networks: An Introduction.
       Oxford University Press, USA, 2010, pp. 169.
    """
    import scipy as sp
    from scipy.sparse import linalg
    if len(G) == 0:
        raise nx.NetworkXException('Empty graph.')
    M = nx.to_scipy_sparse_matrix(G, nodelist=G.nodes(), weight=weight,
                                  dtype=float)
    eigenvalue, eigenvector = linalg.eigs(M.T, k=1, which='LR')
    largest = eigenvector.flatten().real
    norm = sp.sign(largest.sum())*sp.linalg.norm(largest)
    centrality = dict(zip(G,map(float,largest/norm)))
    return centrality
Пример #5
0
def bidirectional_dijkstra(G, source, target, weight = 'weight'):
    """Dijkstra's algorithm for shortest paths using bidirectional search. 

    Parameters
    ----------
    G : NetworkX graph

    source : node
       Starting node.

    target : node
       Ending node.

    weight: string, optional (default='weight')
       Edge data key corresponding to the edge weight

    Returns
    -------
    length : number
        Shortest path length.

    Returns a tuple of two dictionaries keyed by node.
    The first dictionary stores distance from the source.
    The second stores the path from the source to that node.

    Raises
    ------
    NetworkXNoPath
        If no path exists between source and target.

    Examples
    --------
    >>> G=nx.path_graph(5)
    >>> length,path=nx.bidirectional_dijkstra(G,0,4)
    >>> print(length)
    4
    >>> print(path)
    [0, 1, 2, 3, 4]
    
    Notes
    -----
    Edge weight attributes must be numerical.
    Distances are calculated as sums of weighted edges traversed.

    In practice  bidirectional Dijkstra is much more than twice as fast as 
    ordinary Dijkstra.

    Ordinary Dijkstra expands nodes in a sphere-like manner from the
    source. The radius of this sphere will eventually be the length 
    of the shortest path. Bidirectional Dijkstra will expand nodes 
    from both the source and the target, making two spheres of half 
    this radius. Volume of the first sphere is pi*r*r while the  
    others are 2*pi*r/2*r/2, making up half the volume. 
    
    This algorithm is not guaranteed to work if edge weights
    are negative or are floating point numbers
    (overflows and roundoff errors can cause problems). 

    See Also
    --------
    shortest_path
    shortest_path_length
    """
    if source is None or target is None:
        raise nx.NetworkXException(
            "Bidirectional Dijkstra called with no source or target")
    if source == target: return (0, [source])
    #Init:   Forward             Backward
    dists =  [{},                {}]# dictionary of final distances
    paths =  [{source:[source]}, {target:[target]}] # dictionary of paths 
    fringe = [[],                []] #heap of (distance, node) tuples for extracting next node to expand
    seen =   [{source:0},        {target:0} ]#dictionary of distances to nodes seen 
    #initialize fringe heap
    heapq.heappush(fringe[0], (0, source)) 
    heapq.heappush(fringe[1], (0, target))
    #neighs for extracting correct neighbor information
    if G.is_directed():
        neighs = [G.successors_iter, G.predecessors_iter]
    else:
        neighs = [G.neighbors_iter, G.neighbors_iter]
    #variables to hold shortest discovered path
    #finaldist = 1e30000
    finalpath = []
    dir = 1
    while fringe[0] and fringe[1]:
        # choose direction 
        # dir == 0 is forward direction and dir == 1 is back
        dir = 1-dir
        # extract closest to expand
        (dist, v )= heapq.heappop(fringe[dir]) 
        if v in dists[dir]:
            # Shortest path to v has already been found 
            continue
        # update distance
        dists[dir][v] = dist #equal to seen[dir][v]
        if v in dists[1-dir]:
            # if we have scanned v in both directions we are done 
            # we have now discovered the shortest path
            return (finaldist,finalpath)

        for w in neighs[dir](v):
            if(dir==0): #forward
                if G.is_multigraph():
                    minweight=min((dd.get(weight,1)
                               for k,dd in G[v][w].items()))
                else:
                    minweight=G[v][w].get(weight,1)
                vwLength = dists[dir][v] + minweight #G[v][w].get(weight,1)
            else: #back, must remember to change v,w->w,v
                if G.is_multigraph():
                    minweight=min((dd.get(weight,1)
                               for k,dd in G[w][v].items()))
                else:
                    minweight=G[w][v].get(weight,1)
                vwLength = dists[dir][v] + minweight #G[w][v].get(weight,1)
            
            if w in dists[dir]:
                if vwLength < dists[dir][w]:
                    raise ValueError("Contradictory paths found: negative weights?")
            elif w not in seen[dir] or vwLength < seen[dir][w]:
                # relaxing        
                seen[dir][w] = vwLength
                heapq.heappush(fringe[dir], (vwLength,w)) 
                paths[dir][w] = paths[dir][v]+[w]
                if w in seen[0] and w in seen[1]:
                    #see if this path is better than than the already
                    #discovered shortest path
                    totaldist = seen[0][w] + seen[1][w] 
                    if finalpath == [] or finaldist > totaldist:
                        finaldist = totaldist
                        revpath = paths[1][w][:]
                        revpath.reverse()
                        finalpath = paths[0][w] + revpath[1:]
    raise nx.NetworkXNoPath("No path between %s and %s." % (source, target))
Пример #6
0
def blockmodel(G, partitions, multigraph=False):
    """Returns a reduced graph constructed using the generalized block modeling
    technique.

    The blockmodel technique collapses nodes into blocks based on a
    given partitioning of the node set.  Each partition of nodes
    (block) is represented as a single node in the reduced graph.

    Edges between nodes in the block graph are added according to the
    edges in the original graph.  If the parameter multigraph is False
    (the default) a single edge is added with a weight equal to the
    sum of the edge weights between nodes in the original graph
    The default is a weight of 1 if weights are not specified.  If the
    parameter multigraph is True then multiple edges are added each
    with the edge data from the original graph.

    Parameters
    ----------
    G : graph
        A networkx Graph or DiGraph
    partitions : list of lists, or list of sets 
        The partition of the nodes.  Must be non-overlapping.
    multigraph : bool, optional
        If True return a MultiGraph with the edge data of the original
        graph applied to each corresponding edge in the new graph.
        If False return a Graph with the sum of the edge weights, or a
        count of the edges if the original graph is unweighted.

    Returns
    -------
    blockmodel : a Networkx graph object
    
    Examples
    --------
    >>> G=nx.path_graph(6)
    >>> partition=[[0,1],[2,3],[4,5]]
    >>> M=nx.blockmodel(G,partition)

    References
    ----------
    .. [1] Patrick Doreian, Vladimir Batagelj, and Anuska Ferligoj
    	"Generalized Blockmodeling",Cambridge University Press, 2004.
    """
    # Create sets of node partitions
    part = list(map(set, partitions))

    # Check for overlapping node partitions
    u = set()
    for p1, p2 in zip(part[:-1], part[1:]):
        u.update(p1)
        #if not u.isdisjoint(p2):  # Code Python github 2.6 required
        if len(u.intersection(p2)) > 0:
            raise nx.NetworkXException("Overlapping node partitions.")

    # Initialize blockmodel graph
    if multigraph:
        if G.is_directed():
            M = nx.MultiDiGraph()
        else:
            M = nx.MultiGraph()
    else:
        if G.is_directed():
            M = nx.DiGraph()
        else:
            M = nx.Graph()

    # Add nodes and properties to blockmodel
    # The blockmodel nodes are node-induced subgraphs of G
    # Label them with integers starting at 0
    for i, p in zip(range(len(part)), part):
        M.add_node(i)
        # The node-induced subgraph is stored as the node 'graph' attribute
        SG = G.subgraph(p)
        M.node[i]['graph'] = SG
        M.node[i]['nnodes'] = SG.number_of_nodes()
        M.node[i]['nedges'] = SG.number_of_edges()
        M.node[i]['density'] = nx.density(SG)

    # Create mapping between original node labels and new blockmodel node labels
    block_mapping = {}
    for n in M:
        nodes_in_block = M.node[n]['graph'].nodes()
        block_mapping.update(dict.fromkeys(nodes_in_block, n))

    # Add edges to block graph
    for u, v, d in G.edges(data=True):
        bmu = block_mapping[u]
        bmv = block_mapping[v]
        if bmu == bmv:  # no self loops
            continue
        if multigraph:
            # For multigraphs add an edge for each edge in original graph
            M.add_edge(bmu, bmv, attr_dict=d)
        else:
            # For graphs and digraphs add single weighted edge
            weight = d.get('weight',
                           1.0)  # default to 1 if no weight specified
            if M.has_edge(bmu, bmv):
                M[bmu][bmv]['weight'] += weight
            else:
                M.add_edge(bmu, bmv, weight=weight)
    return M
Пример #7
0
def check_embedding(G, embedding):
    """Raises an exception if the combinatorial embedding is not correct

    Parameters
    ----------
    G : NetworkX graph
    embedding : a dict mapping nodes to a list of edges
        This specifies the ordering of the outgoing edges from a node for
        a combinatorial embedding

    Notes
    -----
    Checks the following things:
        - The type of the embedding is a dict
        - Every node in the graph has to be contained in the embedding
        - The original graph actually contains the adjacency structure
            from the embedding
        - A node is not contained twice in the neighbor list from a node
            in the embedding
        - The cycles around each face are correct (no unexpected cycle)
        - Every outgoing edge in the embedding also has an incoming edge
            in the embedding
        - Checks that all edges in the original graph have been counted
        - Checks that euler's formula holds for the number of edges, faces,
            and nodes for each component

    The number of faces is determined by using the combinatorial embedding
    to follow a path around face. While following the path around the face
    every edge on the way (in this direction) is marked such that the
    face is not counted twice.
    """

    if not isinstance(embedding, dict):
        raise nx.NetworkXException("Bad embedding. Not of type dict")

    # calculate connected components
    connected_components = nx.connected_components(G)

    # check all components
    for component in connected_components:
        if len(component) == 1:
            if len(embedding[list(component)[0]]) != 0:
                # the node should not have a neighbor
                raise nx.NetworkXException(
                    "Bad embedding. Single node component has neighbors.")
            else:
                continue

        component_subgraph = nx.subgraph(G, component)
        # count the number of faces
        number_faces = 0
        # keep track of which faces have already been counted
        # set of edges where the face to the left was already counted
        edges_counted = set()
        for starting_node in component:
            if starting_node not in embedding:
                raise nx.NetworkXException(
                    "Bad embedding. The embedding is missing a node.")
            # keep track of all neighbors of the starting node to ensure no
            # neighbor is contained twice
            neighbor_set = set()
            # calculate all faces around starting_node
            for face_idx in range(0, len(embedding[starting_node])):
                if count_face(embedding, starting_node, face_idx,
                              component_subgraph, neighbor_set, edges_counted):
                    number_faces += 1

        # Length of edges_counted must be even
        if len(edges_counted) % 2 != 0:
            raise nx.NetworkXException(
                "Bad embedding. Counted an uneven number of half edges")
        number_edges = len(edges_counted) // 2
        number_nodes = len(component)

        # Check that all edges have been counted
        for u, v in component_subgraph.edges:
            if u != v and ((u, v) not in edges_counted or
                           (v, u) not in edges_counted):
                raise nx.NetworkXException("Bad planar embedding. "
                                           "An edge has not been counted")

        if number_nodes - number_edges + number_faces != 2:
            # number of faces don't match the expected value (euler's formula)
            raise nx.NetworkXException(
                "Bad planar embedding. "
                "Number of faces does not match euler's formula.")
Пример #8
0
    def _init(self, attr, default, kind, style, preserve_attrs, seed):
        if kind not in KINDS:
            raise nx.NetworkXException("Unknown value for `kind`.")

        # Store inputs.
        self.attr = attr
        self.default = default
        self.kind = kind
        self.style = style

        # Determine how we are going to transform the weights.
        if kind == "min":
            self.trans = trans = _min_weight
        else:
            self.trans = trans = _max_weight

        if attr is None:
            # Generate a random attr the graph probably won't have.
            attr = random_string(seed=seed)

        # This is the actual attribute used by the algorithm.
        self._attr = attr

        # This attribute is used to store whether a particular edge is still
        # a candidate. We generate a random attr to remove clashes with
        # preserved edges
        self.candidate_attr = "candidate_" + random_string(seed=seed)

        # The object we manipulate at each step is a multidigraph.
        self.G = G = MultiDiGraph_EdgeKey()
        for key, (u, v, data) in enumerate(self.G_original.edges(data=True)):
            d = {attr: trans(data.get(attr, default))}

            if preserve_attrs:
                for (d_k, d_v) in data.items():
                    if d_k != attr:
                        d[d_k] = d_v

            G.add_edge(u, v, key, **d)

        self.level = 0

        # These are the "buckets" from the paper.
        #
        # As in the paper, G^i are modified versions of the original graph.
        # D^i and E^i are nodes and edges of the maximal edges that are
        # consistent with G^i. These are dashed edges in figures A-F of the
        # paper. In this implementation, we store D^i and E^i together as a
        # graph B^i. So we will have strictly more B^i than the paper does.
        self.B = MultiDiGraph_EdgeKey()
        self.B.edge_index = {}
        self.graphs = []  # G^i
        self.branchings = []  # B^i
        self.uf = nx.utils.UnionFind()

        # A list of lists of edge indexes. Each list is a circuit for graph G^i.
        # Note the edge list will not, in general, be a circuit in graph G^0.
        self.circuits = []
        # Stores the index of the minimum edge in the circuit found in G^i and B^i.
        # The ordering of the edges seems to preserve the weight ordering from G^0.
        # So even if the circuit does not form a circuit in G^0, it is still true
        # that the minimum edge of the circuit in G^i is still the minimum edge
        # in circuit G^0 (depsite their weights being different).
        self.minedge_circuit = []
Пример #9
0
def non_randomness(G, k=None):
    """Compute the non-randomness of graph G.

    The first returned value nr is the sum of non-randomness values of all
    edges within the graph (where the non-randomness of an edge tends to be
    small when the two nodes linked by that edge are from two different
    communities).

    The second computed value nr_rd is a relative measure that indicates
    to what extent graph G is different from random graphs in terms
    of probability. When it is close to 0, the graph tends to be more
    likely generated by an Erdos Renyi model.

    Parameters
    ----------
    G : NetworkX graph
        Graph must be binary, symmetric, connected, and without self-loops.

    k : int
        The number of communities in G.
        If k is not set, the function will use a default community
        detection algorithm to set it.

    Returns
    -------
    non-randomness : (float, float) tuple
        Non-randomness, Relative non-randomness w.r.t.
        Erdos Renyi random graphs.

    Examples
    --------
    >>> G = nx.karate_club_graph()
    >>> nr, nr_rd = nx.non_randomness(G, 2)

    Notes
    -----
    This computes Eq. (4.4) and (4.5) in Ref. [1]_.

    References
    ----------
     .. [1] Xiaowei Ying and Xintao Wu,
            On Randomness Measures for Social Networks,
            SIAM International Conference on Data Mining. 2009
    """

    if not nx.is_connected(G):
        raise nx.NetworkXException("Non connected graph.")
    if len(list(nx.selfloop_edges(G))) > 0:
        raise nx.NetworkXError('Graph must not contain self-loops')

    if k is None:
        k = len(tuple(nx.community.label_propagation_communities(G)))

    try:
        import numpy as np
    except ImportError:
        msg = "non_randomness requires NumPy: http://scipy.org/"
        raise ImportError(msg)

    # eq. 4.4
    nr = np.real(np.sum(np.linalg.eigvals(nx.to_numpy_matrix(G))[:k]))

    n = G.number_of_nodes()
    m = G.number_of_edges()
    p = (2 * k * m) / (n * (n - k))

    # eq. 4.5
    nr_rd = (nr - ((n - 2 * k) * p + k)) / math.sqrt(2 * k * p * (1 - p))

    return nr, nr_rd
Пример #10
0
def second_order_centrality(G):
    """Compute the second order centrality for nodes of G.

    The second order centrality of a given node is the standard deviation of
    the return times to that node of a perpetual random walk on G:

    Parameters
    ----------
    G : graph
      A NetworkX connected and undirected graph.

    Returns
    -------
    nodes : dictionary
       Dictionary keyed by node with second order centrality as the value.

    Examples
    --------
    >>> G = nx.star_graph(10)
    >>> soc = nx.second_order_centrality(G)
    >>> print(sorted(soc.items(), key=lambda x:x[1])[0][0]) # pick first id
    0

    Raises
    ------
    NetworkXException
        If the graph G is empty, non connected or has negative weights.

    See Also
    --------
    betweenness_centrality

    Notes
    -----
    Lower values of second order centrality indicate higher centrality.

    The algorithm is from Kermarrec, Le Merrer, Sericola and Trédan [1]_.

    This code implements the analytical version of the algorithm, i.e.,
    there is no simulation of a random walk process involved. The random walk
    is here unbiased (corresponding to eq 6 of the paper [1]_), thus the
    centrality values are the standard deviations for random walk return times
    on the transformed input graph G (equal in-degree at each nodes by adding
    self-loops).

    Complexity of this implementation, made to run locally on a single machine,
    is O(n^3), with n the size of G, which makes it viable only for small
    graphs.

    References
    ----------
    .. [1] Anne-Marie Kermarrec, Erwan Le Merrer, Bruno Sericola, Gilles Trédan
       "Second order centrality: Distributed assessment of nodes criticity in
       complex networks", Elsevier Computer Communications 34(5):619-628, 2011.
    """

    try:
        import numpy as np
    except ImportError:
        raise ImportError('Requires NumPy: http://scipy.org/')

    n = len(G)

    if n == 0:
        raise nx.NetworkXException("Empty graph.")
    if not nx.is_connected(G):
        raise nx.NetworkXException("Non connected graph.")
    if any(d.get('weight', 0) < 0 for u, v, d in G.edges(data=True)):
        raise nx.NetworkXException("Graph has negative edge weights.")

    # balancing G for Metropolis-Hastings random walks
    G = nx.DiGraph(G)
    in_deg = dict(G.in_degree(weight='weight'))
    d_max = max(in_deg.values())
    for i, deg in in_deg.items():
        if deg < d_max:
            G.add_edge(i, i, weight=d_max-deg)

    P = nx.to_numpy_matrix(G)
    P = P / P.sum(axis=1)  # to transition probability matrix

    def _Qj(P, j):
        P = P.copy()
        P[:, j] = 0
        return P

    M = np.empty([n, n])

    for i in range(n):
        M[:, i] = np.linalg.solve(np.identity(n) - _Qj(P, i),
                                  np.ones([n, 1])[:, 0])  # eq 3

    return dict(zip(G.nodes,
                    [np.sqrt((2*np.sum(M[:, i])-n*(n+1))) for i in range(n)]
                    ))  # eq 6
Пример #11
0
def view_pygraphviz(G, edgelabel=None, prog='neato', args='',
                       suffix='', filename=None):
    """Views the graph G using the specified layout algorithm.

    Parameters
    ----------
    G : NetworkX graph
        The machine to draw.
    edgelabel : str, callable, None
        If a string, then it specifes the edge attribute to be displayed
        on the edge labels. If a callable, then it is called for each
        edge and it should return the string to be displayed on the edges.
        The function signature of `edgelabel` should be edgelabel(data),
        where `data` is the edge attribute dictionary.
    prog : string
        Name of Graphviz layout program.
    args : str
        Additional arguments to pass to the Graphviz layout program.
    suffix : str
        If `filename` is None, we save to a temporary file.  The value of
        `suffix` will appear at the tail end of the temporary filename.
    filename : str, None
        The filename used to save the image.  If None, save to a temporary
        file.  File formats are the same as those from pygraphviz.agraph.draw.

    Returns
    -------
    filename : str
        The filename of the generated image.
    A : PyGraphviz graph
        The PyGraphviz graph instance used to generate the image.

    Notes
    -----
    If this function is called in succession too quickly, sometimes the
    image is not displayed. So you might consider time.sleep(.5) between
    calls if you experience problems.

    """
    if not len(G):
        raise nx.NetworkXException("An empty graph cannot be drawn.")

    import pygraphviz

    # If we are providing default values for graphviz, these must be set
    # before any nodes or edges are added to the PyGraphviz graph object.
    # The reason for this is that default values only affect incoming objects.
    # If you change the default values after the objects have been added,
    # then they inherit no value and are set only if explicitly set.

    # to_agraph() uses these values.
    attrs = ['edge', 'node', 'graph']
    for attr in attrs:
        if attr not in G.graph:
            G.graph[attr] = {}

    # These are the default values.
    edge_attrs = {'fontsize': '10'}
    node_attrs = {'style': 'filled',
                  'fillcolor': '#0000FF40',
                  'height': '0.75',
                  'width': '0.75',
                  'shape': 'circle'}
    graph_attrs = {}

    def update_attrs(which, attrs):
        # Update graph attributes. Return list of those which were added.
        added = []
        for k,v in attrs.items():
            if k not in G.graph[which]:
                G.graph[which][k] = v
                added.append(k)

    def clean_attrs(which, added):
        # Remove added attributes
        for attr in added:
            del G.graph[which][attr]
        if not G.graph[which]:
            del G.graph[which]

    # Update all default values
    added_edge = update_attrs('edge', edge_attrs)
    added_node = update_attrs('node', node_attrs)
    added_graph = update_attrs('graph', graph_attrs)

    # Convert to agraph, so we inherit default values
    A = to_agraph(G)

    # Remove the default values we added to the original graph.
    clean_attrs('edge', edge_attrs)
    clean_attrs('node', node_attrs)
    clean_attrs('graph', graph_attrs)

    # If the user passed in an edgelabel, we update the labels for all edges.
    if edgelabel is not None:
        if not hasattr(edgelabel, '__call__'):
            def func(data):
                return ''.join(["  ", str(data[edgelabel]), "  "])
        else:
            func = edgelabel

        # update all the edge labels
        if G.is_multigraph():
            for u,v,key,data in G.edges_iter(keys=True, data=True):
                # PyGraphviz doesn't convert the key to a string. See #339
                edge = A.get_edge(u,v,str(key))
                edge.attr['label'] = str(func(data))
        else:
            for u,v,data in G.edges_iter(data=True):
                edge = A.get_edge(u,v)
                edge.attr['label'] = str(func(data))

    if filename is None:
        ext = 'png'
        if suffix:
            suffix = '_%s.%s' % (suffix, ext)
        else:
            suffix = '.%s' % (ext,)
        fd, filename = tempfile.mkstemp(suffix=suffix)
        path = (fd, filename)
    else:
        path = (filename,)

    display_pygraphviz(A, path=path, prog=prog, args=args)

    return filename, A
Пример #12
0
def view_pygraphviz(G,
                    edgelabel=None,
                    prog="dot",
                    args="",
                    suffix="",
                    path=None):
    """Views the graph G using the specified layout algorithm.

    Parameters
    ----------
    G : NetworkX graph
        The machine to draw.
    edgelabel : str, callable, None
        If a string, then it specifes the edge attribute to be displayed
        on the edge labels. If a callable, then it is called for each
        edge and it should return the string to be displayed on the edges.
        The function signature of `edgelabel` should be edgelabel(data),
        where `data` is the edge attribute dictionary.
    prog : string
        Name of Graphviz layout program.
    args : str
        Additional arguments to pass to the Graphviz layout program.
    suffix : str
        If `filename` is None, we save to a temporary file.  The value of
        `suffix` will appear at the tail end of the temporary filename.
    path : str, None
        The filename used to save the image.  If None, save to a temporary
        file.  File formats are the same as those from pygraphviz.agraph.draw.

    Returns
    -------
    path : str
        The filename of the generated image.
    A : PyGraphviz graph
        The PyGraphviz graph instance used to generate the image.

    Notes
    -----
    If this function is called in succession too quickly, sometimes the
    image is not displayed. So you might consider time.sleep(.5) between
    calls if you experience problems.

    """
    if not len(G):
        raise nx.NetworkXException("An empty graph cannot be drawn.")

    # If we are providing default values for graphviz, these must be set
    # before any nodes or edges are added to the PyGraphviz graph object.
    # The reason for this is that default values only affect incoming objects.
    # If you change the default values after the objects have been added,
    # then they inherit no value and are set only if explicitly set.

    # to_agraph() uses these values.
    attrs = ["edge", "node", "graph"]
    for attr in attrs:
        if attr not in G.graph:
            G.graph[attr] = {}

    # These are the default values.
    edge_attrs = {"fontsize": "10"}
    node_attrs = {
        "style": "filled",
        "fillcolor": "#0000FF40",
        "height": "0.75",
        "width": "0.75",
        "shape": "circle",
    }
    graph_attrs = {}

    def update_attrs(which, attrs):
        # Update graph attributes. Return list of those which were added.
        added = []
        for k, v in attrs.items():
            if k not in G.graph[which]:
                G.graph[which][k] = v
                added.append(k)

    def clean_attrs(which, added):
        # Remove added attributes
        for attr in added:
            del G.graph[which][attr]
        if not G.graph[which]:
            del G.graph[which]

    # Update all default values
    update_attrs("edge", edge_attrs)
    update_attrs("node", node_attrs)
    update_attrs("graph", graph_attrs)

    # Convert to agraph, so we inherit default values
    A = to_agraph(G)

    # Remove the default values we added to the original graph.
    clean_attrs("edge", edge_attrs)
    clean_attrs("node", node_attrs)
    clean_attrs("graph", graph_attrs)

    # If the user passed in an edgelabel, we update the labels for all edges.
    if edgelabel is not None:
        if not hasattr(edgelabel, "__call__"):

            def func(data):
                return "".join(["  ", str(data[edgelabel]), "  "])

        else:
            func = edgelabel

        # update all the edge labels
        if G.is_multigraph():
            for u, v, key, data in G.edges(keys=True, data=True):
                # PyGraphviz doesn't convert the key to a string. See #339
                edge = A.get_edge(u, v, str(key))
                edge.attr["label"] = str(func(data))
        else:
            for u, v, data in G.edges(data=True):
                edge = A.get_edge(u, v)
                edge.attr["label"] = str(func(data))

    if path is None:
        ext = "png"
        if suffix:
            suffix = f"_{suffix}.{ext}"
        else:
            suffix = f".{ext}"
        path = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
    else:
        # Assume the decorator worked and it is a file-object.
        pass

    display_pygraphviz(A, path=path, prog=prog, args=args)

    return path.name, A
Пример #13
0
def stochastic_block_model(sizes,
                           p,
                           nodelist=None,
                           seed=None,
                           directed=False,
                           selfloops=False,
                           sparse=True):
    """Return a stochastic block model graph.

    This model partitions the nodes in blocks of arbitrary sizes, and places
    edges between pairs of nodes independently, with a probability that depends
    on the blocks.

    Parameters
    ----------
    sizes : list of ints
        Sizes of blocks
    p : list of list of floats
        Element (r,s) gives the density of edges going from the nodes
        of group r to nodes of group s.
        p must match the number of groups (len(sizes) == len(p)),
        and it must be symmetric if the graph is undirected.
    nodelist : list, optional
        The block tags are assigned according to the node identifiers
        in nodelist. If nodelist is None, then the ordering is the
        range [0,sum(sizes)-1].
    seed : integer, random_state, or None (default)
        Indicator of random number generation state.
        See :ref:`Randomness<randomness>`.
    directed : boolean optional, default=False
        Whether to create a directed graph or not.
    selfloops : boolean optional, default=False
        Whether to include self-loops or not.
    sparse: boolean optional, default=True
        Use the sparse heuristic to speed up the generator.

    Returns
    -------
    g : NetworkX Graph or DiGraph
        Stochastic block model graph of size sum(sizes)

    Raises
    ------
    NetworkXError
      If probabilities are not in [0,1].
      If the probability matrix is not square (directed case).
      If the probability matrix is not symmetric (undirected case).
      If the sizes list does not match nodelist or the probability matrix.
      If nodelist contains duplicate.

    Examples
    --------
    >>> sizes = [75, 75, 300]
    >>> probs = [[0.25, 0.05, 0.02],
    ...          [0.05, 0.35, 0.07],
    ...          [0.02, 0.07, 0.40]]
    >>> g = nx.stochastic_block_model(sizes, probs, seed=0)
    >>> len(g)
    450
    >>> H = nx.quotient_graph(g, g.graph['partition'], relabel=True)
    >>> for v in H.nodes(data=True):
    ...     print(round(v[1]['density'], 3))
    ...
    0.245
    0.348
    0.405
    >>> for v in H.edges(data=True):
    ...     print(round(v[2]['weight'] / (sizes[v[0]] * sizes[v[1]]), 3))
    ...
    0.051
    0.022
    0.07

    See Also
    --------
    random_partition_graph
    planted_partition_graph
    gaussian_random_partition_graph
    gnp_random_graph

    References
    ----------
    .. [1] Holland, P. W., Laskey, K. B., & Leinhardt, S.,
           "Stochastic blockmodels: First steps",
           Social networks, 5(2), 109-137, 1983.
    """
    # Check if dimensions match
    if len(sizes) != len(p):
        raise nx.NetworkXException("'sizes' and 'p' do not match.")
    # Check for probability symmetry (undirected) and shape (directed)
    for row in p:
        if len(p) != len(row):
            raise nx.NetworkXException("'p' must be a square matrix.")
    if not directed:
        p_transpose = [list(i) for i in zip(*p)]
        for i in zip(p, p_transpose):
            for j in zip(i[0], i[1]):
                if abs(j[0] - j[1]) > 1e-08:
                    raise nx.NetworkXException("'p' must be symmetric.")
    # Check for probability range
    for row in p:
        for prob in row:
            if prob < 0 or prob > 1:
                raise nx.NetworkXException("Entries of 'p' not in [0,1].")
    # Check for nodelist consistency
    if nodelist is not None:
        if len(nodelist) != sum(sizes):
            raise nx.NetworkXException("'nodelist' and 'sizes' do not match.")
        if len(nodelist) != len(set(nodelist)):
            raise nx.NetworkXException("nodelist contains duplicate.")
    else:
        nodelist = range(0, sum(sizes))

    # Setup the graph conditionally to the directed switch.
    block_range = range(len(sizes))
    if directed:
        g = nx.DiGraph()
        block_iter = itertools.product(block_range, block_range)
    else:
        g = nx.Graph()
        block_iter = itertools.combinations_with_replacement(block_range, 2)
    # Split nodelist in a partition (list of sets).
    size_cumsum = [sum(sizes[0:x]) for x in range(0, len(sizes) + 1)]
    g.graph['partition'] = [
        set(nodelist[size_cumsum[x]:size_cumsum[x + 1]])
        for x in range(0,
                       len(size_cumsum) - 1)
    ]
    # Setup nodes and graph name
    for block_id, nodes in enumerate(g.graph['partition']):
        for node in nodes:
            g.add_node(node, block=block_id)

    g.name = "stochastic_block_model"

    # Test for edge existence
    parts = g.graph['partition']
    for i, j in block_iter:
        if i == j:
            if directed:
                if selfloops:
                    edges = itertools.product(parts[i], parts[i])
                else:
                    edges = itertools.permutations(parts[i], 2)
            else:
                edges = itertools.combinations(parts[i], 2)
                if selfloops:
                    edges = itertools.chain(edges, zip(parts[i], parts[i]))
            for e in edges:
                if seed.random() < p[i][j]:
                    g.add_edge(*e)
        else:
            edges = itertools.product(parts[i], parts[j])
        if sparse:
            if p[i][j] == 1:  # Test edges cases p_ij = 0 or 1
                for e in edges:
                    g.add_edge(*e)
            elif p[i][j] > 0:
                while True:
                    try:
                        logrand = math.log(seed.random())
                        skip = math.floor(logrand / math.log(1 - p[i][j]))
                        # consume "skip" edges
                        next(itertools.islice(edges, skip, skip), None)
                        e = next(edges)
                        g.add_edge(*e)  # __safe
                    except StopIteration:
                        break
        else:
            for e in edges:
                if seed.random() < p[i][j]:
                    g.add_edge(*e)  # __safe
    return g
Пример #14
0
def min_edge_cover(G, matching_algorithm=None):
    """Returns a set of edges which constitutes
    the minimum edge cover of the graph.

    A smallest edge cover can be found in polynomial time by finding
    a maximum matching and extending it greedily so that all nodes
    are covered.

    Parameters
    ----------
    G : NetworkX graph
        An undirected bipartite graph.

    matching_algorithm : function
        A function that returns a maximum cardinality matching in a
        given bipartite graph. The function must take one input, the
        graph ``G``, and return a dictionary mapping each node to its
        mate. If not specified,
        :func:`~networkx.algorithms.bipartite.matching.hopcroft_karp_matching`
        will be used. Other possibilities include
        :func:`~networkx.algorithms.bipartite.matching.eppstein_matching`,
        or matching algorithms in the
        :mod:`networkx.algorithms.matching` module.

    Returns
    -------
    min_cover : set

        It contains all the edges of minimum edge cover
        in form of tuples. It contains both the edges `(u, v)` and `(v, u)`
        for given nodes `u` and `v` among the edges of minimum edge cover.

    Notes
    -----
    An edge cover of a graph is a set of edges such that every node of
    the graph is incident to at least one edge of the set.
    The minimum edge cover is an edge covering of smallest cardinality.

    Due to its implementation, the worst-case running time of this algorithm
    is bounded by the worst-case running time of the function
    ``matching_algorithm``.

    Minimum edge cover for bipartite graph can also be found using the
    function present in :mod:`networkx.algorithms.bipartite.covering`
    """
    if nx.number_of_isolates(G) > 0:
        # ``min_cover`` does not exist as there is an isolated node
        raise nx.NetworkXException(
            "Graph has a node with no edge incident on it, "
            "so no edge cover exists.")
    if matching_algorithm is None:
        matching_algorithm = partial(nx.max_weight_matching,
                                     maxcardinality=True)
    maximum_matching = matching_algorithm(G)
    # ``min_cover`` is superset of ``maximum_matching``
    try:
        min_cover = set(
            maximum_matching.items())  # bipartite matching case returns dict
    except AttributeError:
        min_cover = maximum_matching
    # iterate for uncovered nodes
    uncovered_nodes = set(G) - {v
                                for u, v in min_cover
                                } - {u
                                     for u, v in min_cover}
    for v in uncovered_nodes:
        # Since `v` is uncovered, each edge incident to `v` will join it
        # with a covered node (otherwise, if there were an edge joining
        # uncovered nodes `u` and `v`, the maximum matching algorithm
        # would have found it), so we can choose an arbitrary edge
        # incident to `v`. (This applies only in a simple graph, not a
        # multigraph.)
        u = arbitrary_element(G[v])
        min_cover.add((u, v))
        min_cover.add((v, u))
    return min_cover
Пример #15
0
def count_face(embedding, starting_node, face_idx, component_subgraph,
               neighbor_set, edges_counted):
    """Checks if face was not counted and marks it so it is not counted twice

    The parameters starting_node and face_idx uniquely define a face by
    following the path that starts in starting_node, where we take the
    half-edge from the embedding at embedding[starting_node][face_idx].
    We check that this face was not already counted by checking that no
    half-edge on the path is already contained in edges_counted. We prevent
    that the face is counted twice by adding all half-edges to edges-counted.

    Parameters
    ----------
    embedding: dict
        The embedding that defines the faces
    starting_node:
        A node on the face
    face_idx: int
        Index of the half-edge in embedding[starting_node]
    component_subgraph: NetworkX graph
        The current component of the original graph
        (to verify the edges from the embedding are actually present)
    neighbor_set: set
        Set of all neighbors of starting_node that have already been visited
    edges_counted: set
        Set of all half-edges that belong to a face that has been counted

    Returns
    -------
    is_new_face: bool
        The face has not been counted
    """
    outgoing_node = embedding[starting_node][face_idx]
    incoming_node = embedding[starting_node][face_idx - 1]

    # 1. Check that the edges exists in the original graph
    # (check both directions in case of diGraph)
    has_outgoing_edge = component_subgraph.has_edge(starting_node,
                                                    outgoing_node)
    has_outgoing_edge_reversed = component_subgraph.has_edge(
        outgoing_node, starting_node)
    if not has_outgoing_edge and not has_outgoing_edge_reversed:
        raise nx.NetworkXException(
            "Bad planar embedding."
            "The embedding contains an edge not present in the original graph")

    # 2. Check that a neighbor node is not contained twice in the adj list
    if outgoing_node in neighbor_set:
        raise nx.NetworkXException(
            "Bad planar embedding. "
            "A node is contained twice in the adjacency list.")
    neighbor_set.add(outgoing_node)

    # 3. Check if the face has already been calculated
    if (starting_node, outgoing_node) in edges_counted:
        # This face was already counted
        return False
    edges_counted.add((starting_node, outgoing_node))

    # 4. Add all edges to edges_counted which have this face to their left
    visited_nodes = set()  # Keep track of visited nodes
    current_node = starting_node
    next_node = outgoing_node
    while next_node != starting_node:
        # cycle is not completed yet

        # check that we have not visited the current node yet
        # (starting_node lies outside of the cycle).
        if current_node in visited_nodes:
            raise nx.NetworkXException(
                "Bad planar embedding. A node is contained "
                "twice in a cycle aound a face.")
        visited_nodes.add(current_node)

        # obtain outgoing edge from next node
        try:
            incoming_idx = embedding[next_node].index(current_node)
        except ValueError:
            raise nx.NetworkXException(
                "Bad planar embedding. No incoming edge for an outgoing edge.")
        # outgoing edge is to the right of the incoming idx
        # (or the idx rolls over to 0)
        if incoming_idx == len(embedding[next_node]) - 1:
            outgoing_idx = 0
        else:
            outgoing_idx = incoming_idx + 1

        # set next edge
        current_node = next_node
        next_node = embedding[next_node][outgoing_idx]
        current_edge = (current_node, next_node)

        # all edges around this face should not have been counted
        if current_edge in edges_counted:
            raise nx.NetworkXException(
                "Bad planar embedding. "
                "The number of faces could not be determined.")

        # remember that this edge has been counted
        edges_counted.add(current_edge)

    # 5. Check if the incoming node is correct
    assert_equals(
        current_node, incoming_node, "Bad planar embedding. "
        "A path did not end at the expected incoming node.")

    # 6. Count this face
    return True
Пример #16
0
def quotient_graph(
    G,
    partition,
    edge_relation=None,
    node_data=None,
    edge_data=None,
    relabel=False,
    create_using=None,
):
    """Returns the quotient graph of `G` under the specified equivalence
    relation on nodes.

    Parameters
    ----------
    G : NetworkX graph
        The graph for which to return the quotient graph with the
        specified node relation.

    partition : function, or dict or list of lists, tuples or sets
        If a function, this function must represent an equivalence
        relation on the nodes of `G`. It must take two arguments *u*
        and *v* and return True exactly when *u* and *v* are in the
        same equivalence class. The equivalence classes form the nodes
        in the returned graph.

        If a dict of lists/tuples/sets, the keys can be any meaningful
        block labels, but the values must be the block lists/tuples/sets
        (one list/tuple/set per block), and the blocks must form a valid
        partition of the nodes of the graph. That is, each node must be
        in exactly one block of the partition.

        If a list of sets, the list must form a valid partition of
        the nodes of the graph. That is, each node must be in exactly
        one block of the partition.

    edge_relation : Boolean function with two arguments
        This function must represent an edge relation on the *blocks* of
        the `partition` of `G`. It must take two arguments, *B* and *C*,
        each one a set of nodes, and return True exactly when there should be
        an edge joining block *B* to block *C* in the returned graph.

        If `edge_relation` is not specified, it is assumed to be the
        following relation. Block *B* is related to block *C* if and
        only if some node in *B* is adjacent to some node in *C*,
        according to the edge set of `G`.

    edge_data : function
        This function takes two arguments, *B* and *C*, each one a set
        of nodes, and must return a dictionary representing the edge
        data attributes to set on the edge joining *B* and *C*, should
        there be an edge joining *B* and *C* in the quotient graph (if
        no such edge occurs in the quotient graph as determined by
        `edge_relation`, then the output of this function is ignored).

        If the quotient graph would be a multigraph, this function is
        not applied, since the edge data from each edge in the graph
        `G` appears in the edges of the quotient graph.

    node_data : function
        This function takes one argument, *B*, a set of nodes in `G`,
        and must return a dictionary representing the node data
        attributes to set on the node representing *B* in the quotient graph.
        If None, the following node attributes will be set:

        * 'graph', the subgraph of the graph `G` that this block
          represents,
        * 'nnodes', the number of nodes in this block,
        * 'nedges', the number of edges within this block,
        * 'density', the density of the subgraph of `G` that this
          block represents.

    relabel : bool
        If True, relabel the nodes of the quotient graph to be
        nonnegative integers. Otherwise, the nodes are identified with
        :class:`frozenset` instances representing the blocks given in
        `partition`.

    create_using : NetworkX graph constructor, optional (default=nx.Graph)
       Graph type to create. If graph instance, then cleared before populated.

    Returns
    -------
    NetworkX graph
        The quotient graph of `G` under the equivalence relation
        specified by `partition`. If the partition were given as a
        list of :class:`set` instances and `relabel` is False,
        each node will be a :class:`frozenset` corresponding to the same
        :class:`set`.

    Raises
    ------
    NetworkXException
        If the given partition is not a valid partition of the nodes of
        `G`.

    Examples
    --------
    The quotient graph of the complete bipartite graph under the "same
    neighbors" equivalence relation is `K_2`. Under this relation, two nodes
    are equivalent if they are not adjacent but have the same neighbor set.

    >>> G = nx.complete_bipartite_graph(2, 3)
    >>> same_neighbors = lambda u, v: (
    ...     u not in G[v] and v not in G[u] and G[u] == G[v]
    ... )
    >>> Q = nx.quotient_graph(G, same_neighbors)
    >>> K2 = nx.complete_graph(2)
    >>> nx.is_isomorphic(Q, K2)
    True

    The quotient graph of a directed graph under the "same strongly connected
    component" equivalence relation is the condensation of the graph (see
    :func:`condensation`). This example comes from the Wikipedia article
    *`Strongly connected component`_*.

    >>> G = nx.DiGraph()
    >>> edges = [
    ...     "ab",
    ...     "be",
    ...     "bf",
    ...     "bc",
    ...     "cg",
    ...     "cd",
    ...     "dc",
    ...     "dh",
    ...     "ea",
    ...     "ef",
    ...     "fg",
    ...     "gf",
    ...     "hd",
    ...     "hf",
    ... ]
    >>> G.add_edges_from(tuple(x) for x in edges)
    >>> components = list(nx.strongly_connected_components(G))
    >>> sorted(sorted(component) for component in components)
    [['a', 'b', 'e'], ['c', 'd', 'h'], ['f', 'g']]
    >>>
    >>> C = nx.condensation(G, components)
    >>> component_of = C.graph["mapping"]
    >>> same_component = lambda u, v: component_of[u] == component_of[v]
    >>> Q = nx.quotient_graph(G, same_component)
    >>> nx.is_isomorphic(C, Q)
    True

    Node identification can be represented as the quotient of a graph under the
    equivalence relation that places the two nodes in one block and each other
    node in its own singleton block.

    >>> K24 = nx.complete_bipartite_graph(2, 4)
    >>> K34 = nx.complete_bipartite_graph(3, 4)
    >>> C = nx.contracted_nodes(K34, 1, 2)
    >>> nodes = {1, 2}
    >>> is_contracted = lambda u, v: u in nodes and v in nodes
    >>> Q = nx.quotient_graph(K34, is_contracted)
    >>> nx.is_isomorphic(Q, C)
    True
    >>> nx.is_isomorphic(Q, K24)
    True

    The blockmodeling technique described in [1]_ can be implemented as a
    quotient graph.

    >>> G = nx.path_graph(6)
    >>> partition = [{0, 1}, {2, 3}, {4, 5}]
    >>> M = nx.quotient_graph(G, partition, relabel=True)
    >>> list(M.edges())
    [(0, 1), (1, 2)]

    Here is the sample example but using partition as a dict of block sets.

    >>> G = nx.path_graph(6)
    >>> partition = {0: {0, 1}, 2: {2, 3}, 4: {4, 5}}
    >>> M = nx.quotient_graph(G, partition, relabel=True)
    >>> list(M.edges())
    [(0, 1), (1, 2)]

    Partitions can be represented in various ways:
    ::

        (0) a list/tuple/set of block lists/tuples/sets
        (1) a dict with block labels as keys and blocks lists/tuples/sets as values
        (2) a dict with block lists/tuples/sets as keys and block labels as values
        (3) a function from nodes in the original iterable to block labels
        (4) an equivalence relation function on the target iterable

    As `quotient_graph` is designed to accept partitions represented as (0), (1) or
    (4) only, the `equivalence_classes` function can be used to get the partitions
    in the right form, in order to call `quotient_graph`.

    .. _Strongly connected component: https://en.wikipedia.org/wiki/Strongly_connected_component

    References
    ----------
    .. [1] Patrick Doreian, Vladimir Batagelj, and Anuska Ferligoj.
           *Generalized Blockmodeling*.
           Cambridge University Press, 2004.

    """
    # If the user provided an equivalence relation as a function to compute
    # the blocks of the partition on the nodes of G induced by the
    # equivalence relation.
    if callable(partition):
        # equivalence_classes always return partition of whole G.
        partition = equivalence_classes(G, partition)
        if not nx.community.is_partition(G, partition):
            raise nx.NetworkXException(
                "Input `partition` is not an equivalence relation for nodes of G"
            )
        return _quotient_graph(
            G, partition, edge_relation, node_data, edge_data, relabel, create_using
        )

    # If the partition is a dict, it is assumed to be one where the keys are
    # user-defined block labels, and values are block lists, tuples or sets.
    if isinstance(partition, dict):
        partition = [block for block in partition.values()]

    # If the user provided partition as a collection of sets. Then we
    # need to check if partition covers all of G nodes. If the answer
    # is 'No' then we need to prepare suitable subgraph view.
    partition_nodes = set().union(*partition)
    if len(partition_nodes) != len(G):
        G = G.subgraph(partition_nodes)
    # Each node in the graph/subgraph must be in exactly one block.
    if not nx.community.is_partition(G, partition):
        raise NetworkXException("each node must be in exactly one part of `partition`")
    return _quotient_graph(
        G, partition, edge_relation, node_data, edge_data, relabel, create_using
    )
Пример #17
0
def makeStartEndNetwork(fileName, start, middle, end, longPathsFirst, maxDepth, maxDepthEnd, maxCount, maxCountEnd, selectedDate, dicNames, creator_ids):
    data = extractGraphData(getGraphData())["pred_desc_id_map"]

    completeGraph = makeCompleteGraph(data, creator_ids, dicNames, selectedDate)

    # check if start, middle and end are in the graph
    nodeNotPresent = "Nodes not in graph: {0}"
    if not any(startName in completeGraph for startName in start):
        raise nx.NetworkXException(nodeNotPresent.format(start))
    if not any(endName in completeGraph for endName in end):
        raise nx.NetworkXException(nodeNotPresent.format(end))
    if middle and not any(middleName in completeGraph for middleName in middle):
        raise nx.NetworkXException(nodeNotPresent.format(middle))

    startEndPathPairs = []
    startMiddlePathPairs = []
    middleEndPathPairs = []
    if not middle:
        for startName in start:
            for endName in end:
                startEndPathPairs.append([startName, endName])
    else:
        for startName in start:
            for middleName in middle:
                startMiddlePathPairs.append([startName, middleName])
        for middleName in middle:
            for endName in end:
                middleEndPathPairs.append([middleName, endName])

    # make a network of only the paths
    startEndGraph = nx.DiGraph()

    def addPathPairs(fromGraph, toGraph, pathPairs, maxDepth, maxCount):
        counter = 1
        for pathPair in pathPairs:
            pathList = list(nx.all_simple_paths(fromGraph, pathPair[0], pathPair[1], maxDepth))
            pathList.sort(key=len)
            if longPathsFirst:
                pathList.reverse()
            for path in pathList:
                if counter > maxCount: return
                print("Path length: ", len(path) - 1)
                toGraph.add_path(path)
                counter = counter + 1

    addPathPairs(completeGraph, startEndGraph, startEndPathPairs, maxDepth, maxCount)
    addPathPairs(completeGraph, startEndGraph, startMiddlePathPairs, maxDepth, maxCount)
    addPathPairs(completeGraph, startEndGraph, middleEndPathPairs, maxDepthEnd, maxCountEnd)

    # add any nodes that were not present in the paths
    for startName in start:
        if not startName in startEndGraph.node:
            add_node_from(completeGraph, startEndGraph, startName)
    for middleName in middle:
        if not middleName in startEndGraph.node:
            add_node_from(completeGraph, startEndGraph, middleName)
    for endName in end:
        if not endName in startEndGraph.node:
            add_node_from(completeGraph, startEndGraph, endName)

    # node data isn't preserved when building the new graph
    for newNode in startEndGraph.node:
        startEndGraph.node[newNode] = completeGraph.node[newNode]
    for startNode in startEndGraph.edge:
        for endNode in startEndGraph.edge[startNode]:
            startEndGraph.edge[startNode][endNode] = completeGraph.edge[startNode][endNode]

    # change start, middle and end node colors
    if len(startEndGraph.node) > 0:
        for startName in start:
            if startName in startEndGraph.node:
                startEndGraph.node[startName]["start"] = True;
        for endName in end:
            if endName in startEndGraph.node:
                startEndGraph.node[endName]["end"] = True;
        for middleName in middle:
            if middleName in startEndGraph.node:
                startEndGraph.node[middleName]["middle"] = True;
    else:
        raise nx.NetworkXNoPath("No paths between these nodes exists")

    # output graph as JSON file
    writeGraph(startEndGraph, fileName)
Пример #18
0
def greedy_branching(G, attr="weight", default=1, kind="max", seed=None):
    """
    Returns a branching obtained through a greedy algorithm.

    This algorithm is wrong, and cannot give a proper optimal branching.
    However, we include it for pedagogical reasons, as it can be helpful to
    see what its outputs are.

    The output is a branching, and possibly, a spanning arborescence. However,
    it is not guaranteed to be optimal in either case.

    Parameters
    ----------
    G : DiGraph
        The directed graph to scan.
    attr : str
        The attribute to use as weights. If None, then each edge will be
        treated equally with a weight of 1.
    default : float
        When `attr` is not None, then if an edge does not have that attribute,
        `default` specifies what value it should take.
    kind : str
        The type of optimum to search for: 'min' or 'max' greedy branching.
    seed : integer, random_state, or None (default)
        Indicator of random number generation state.
        See :ref:`Randomness<randomness>`.

    Returns
    -------
    B : directed graph
        The greedily obtained branching.

    """
    if kind not in KINDS:
        raise nx.NetworkXException("Unknown value for `kind`.")

    if kind == "min":
        reverse = False
    else:
        reverse = True

    if attr is None:
        # Generate a random string the graph probably won't have.
        attr = random_string(seed=seed)

    edges = [(u, v, data.get(attr, default))
             for (u, v, data) in G.edges(data=True)]

    # We sort by weight, but also by nodes to normalize behavior across runs.
    try:
        edges.sort(key=itemgetter(2, 0, 1), reverse=reverse)
    except TypeError:
        # This will fail in Python 3.x if the nodes are of varying types.
        # In that case, we use the arbitrary order.
        edges.sort(key=itemgetter(2), reverse=reverse)

    # The branching begins with a forest of no edges.
    B = nx.DiGraph()
    B.add_nodes_from(G)

    # Now we add edges greedily so long we maintain the branching.
    uf = nx.utils.UnionFind()
    for i, (u, v, w) in enumerate(edges):
        if uf[u] == uf[v]:
            # Adding this edge would form a directed cycle.
            continue
        elif B.in_degree(v) == 1:
            # The edge would increase the degree to be greater than one.
            continue
        else:
            # If attr was None, then don't insert weights...
            data = {}
            if attr is not None:
                data[attr] = w
            B.add_edge(u, v, **data)
            uf.union(u, v)

    return B
Пример #19
0
def connected_double_edge_swap(G, nswap=1):
    """Attempt nswap double-edge swaps on the graph G.

    Returns count of successful swaps.  Enforces connectivity.
    The graph G is modified in place.

    Notes
    -----
    A double-edge swap removes two randomly choseen edges u-v and x-y
    and creates the new edges u-x and v-y::

     u--v            u  v
            becomes  |  |
     x--y            x  y


    If either the edge u-x or v-y already exist no swap is performed so
    the actual count of swapped edges is always <= nswap

    The initial graph G must be connected and the resulting graph is connected.

    References
    ----------
    .. [1] C. Gkantsidis and M. Mihail and E. Zegura,
           The Markov chain simulation method for generating connected
           power law random graphs, 2003.
           http://citeseer.ist.psu.edu/gkantsidis03markov.html
    """
    import math
    if not networkx.is_connected(G):
        raise networkx.NetworkXException("Graph not connected")

    n = 0
    swapcount = 0
    deg = G.degree(with_labels=True)
    dk = deg.keys()  # key labels
    ideg = G.degree()
    cdf = networkx.utils.cumulative_distribution(G.degree())
    if len(cdf) < 4:
        raise networkx.NetworkXError("Graph has less than four nodes.")
    window = 1
    while n < nswap:
        wcount = 0
        swapped = []
        while wcount < window and n < nswap:
            # pick two randon edges without creating edge list
            # chose source nodes from discrete distribution
            (ui, xi) = networkx.utils.discrete_sequence(2, cdistribution=cdf)
            if ui == xi: continue  # same source, skip
            u = dk[ui]  # convert index to label
            x = dk[xi]
            v = random.choice(
                G.neighbors(u))  # choose target uniformly from nbrs
            y = random.choice(G.neighbors(
                x))  # Note: dan't use G[u] because choice can't use dict
            if v == y: continue  # same target, skip
            if (not G.has_edge(u, x)) and (not G.has_edge(v, y)):
                G.remove_edge(u, v)
                G.remove_edge(x, y)
                G.add_edge(u, x)
                G.add_edge(v, y)
                swapped.append((u, v, x, y))
                swapcount += 1
            n += 1
            wcount += 1
        if networkx.is_connected(G):  # increase window
            window += 1
        else:  # undo changes from previous window, decrease window
            while swapped:
                (u, v, x, y) = swapped.pop()
                G.add_edge(u, v)
                G.add_edge(x, y)
                G.remove_edge(u, x)
                G.remove_edge(v, y)
                swapcount -= 1
            window = int(math.ceil(float(window) / 2))
        assert G.degree() == ideg
    return swapcount
Пример #20
0
def navigable_small_world_graph(n, p=1, q=1, r=2, dim=2, seed=None):
    """Return a navigable small-world graph.

    A navigable small-world graph is a directed grid with additional long-range
    connections that are chosen randomly.

      [...] we begin with a set of nodes [...] that are identified with the set
      of lattice points in an `n \times n` square, `\{(i, j): i \in \{1, 2,
      \ldots, n\}, j \in \{1, 2, \ldots, n\}\}`, and we define the *lattice
      distance* between two nodes `(i, j)` and `(k, l)` to be the number of
      "lattice steps" separating them: `d((i, j), (k, l)) = |k - i| + |l - j|`.
      For a universal constant `p \geq 1`, the node `u` has a directed edge to
      every other node within lattice distance `p` --- these are its *local
      contacts*. For universal constants `q \ge 0` and `r \ge 0` we also
      construct directed edges from `u` to `q` other nodes (the *long-range
      contacts*) using independent random trials; the `i`th directed edge from
      `u` has endpoint `v` with probability proportional to `[d(u,v)]^{-r}`.

      -- [1]_

    Parameters
    ----------
    n : int
        The number of nodes.
    p : int
        The diameter of short range connections. Each node is joined with every
        other node within this lattice distance.
    q : int
        The number of long-range connections for each node.
    r : float
        Exponent for decaying probability of connections.  The probability of
        connecting to a node at lattice distance `d` is `1/d^r`.
    dim : int
        Dimension of grid
    seed : int, optional
        Seed for random number generator (default=None).

    References
    ----------
    .. [1] J. Kleinberg. The small-world phenomenon: An algorithmic
       perspective. Proc. 32nd ACM Symposium on Theory of Computing, 2000.
    """
    if (p < 1):
        raise nx.NetworkXException("p must be >= 1")
    if (q < 0):
        raise nx.NetworkXException("q must be >= 0")
    if (r < 0):
        raise nx.NetworkXException("r must be >= 1")
    if not seed is None:
        random.seed(seed)
    G = nx.DiGraph()
    nodes = list(product(range(n), repeat=dim))
    for p1 in nodes:
        probs = [0]
        for p2 in nodes:
            if p1 == p2:
                continue
            d = sum((abs(b - a) for a, b in zip(p1, p2)))
            if d <= p:
                G.add_edge(p1, p2)
            probs.append(d**-r)
        cdf = list(nx.utils.accumulate(probs))
        for _ in range(q):
            target = nodes[bisect_left(cdf, random.uniform(0, cdf[-1]))]
            G.add_edge(p1, target)
    return G
Пример #21
0
def planar_layout(G, scale=1, center=None, dim=2):
    """Position nodes without edge intersections.

    Parameters
    ----------
    G : NetworkX graph or list of nodes
        A position will be assigned to every node in G. If G is of type
        PlanarEmbedding, the positions are selected accordingly.

    Parameters
    ----------
    G : NetworkX graph or list of nodes
        A position will be assigned to every node in G. If G is of type
        nx.PlanarEmbedding, the positions are selected accordingly.

    scale : number (default: 1)
        Scale factor for positions.

    center : array-like or None
        Coordinate pair around which to center the layout.

    dim : int
        Dimension of layout.

    Returns
    -------
    pos : dict
        A dictionary of positions keyed by node

    Raises
    ------
    NetworkXException
        If G is not planar

    Examples
    --------
    >>> G = nx.path_graph(4)
    >>> pos = nx.planar_layout(G)
    """
    import numpy as np

    if dim != 2:
        raise ValueError('can only handle 2 dimensions')

    G, center = _process_params(G, center, dim)

    if len(G) == 0:
        return {}

    if isinstance(G, nx.PlanarEmbedding):
        embedding = G
    else:
        is_planar, embedding = nx.check_planarity(G)
        if not is_planar:
            raise nx.NetworkXException("G is not planar.")
    pos = nx.combinatorial_embedding_to_pos(embedding)
    node_list = list(embedding)
    pos = np.row_stack((pos[x] for x in node_list))
    pos = pos.astype(np.float64)
    pos = rescale_layout(pos, scale=scale) + center
    return dict(zip(node_list, pos))