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))""")
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))""")
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
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
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))
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
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.")
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 = []
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
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
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
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
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
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
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
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 )
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)
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
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
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
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))