Spaces:
Paused
Paused
| """Graph diameter, radius, eccentricity and other properties.""" | |
| import networkx as nx | |
| from networkx.utils import not_implemented_for | |
| __all__ = [ | |
| "eccentricity", | |
| "diameter", | |
| "radius", | |
| "periphery", | |
| "center", | |
| "barycenter", | |
| "resistance_distance", | |
| "kemeny_constant", | |
| ] | |
| def _extrema_bounding(G, compute="diameter", weight=None): | |
| """Compute requested extreme distance metric of undirected graph G | |
| Computation is based on smart lower and upper bounds, and in practice | |
| linear in the number of nodes, rather than quadratic (except for some | |
| border cases such as complete graphs or circle shaped graphs). | |
| Parameters | |
| ---------- | |
| G : NetworkX graph | |
| An undirected graph | |
| compute : string denoting the requesting metric | |
| "diameter" for the maximal eccentricity value, | |
| "radius" for the minimal eccentricity value, | |
| "periphery" for the set of nodes with eccentricity equal to the diameter, | |
| "center" for the set of nodes with eccentricity equal to the radius, | |
| "eccentricities" for the maximum distance from each node to all other nodes in G | |
| weight : string, function, or None | |
| If this is a string, then edge weights will be accessed via the | |
| edge attribute with this key (that is, the weight of the edge | |
| joining `u` to `v` will be ``G.edges[u, v][weight]``). If no | |
| such edge attribute exists, the weight of the edge is assumed to | |
| be one. | |
| If this is a function, the weight of an edge is the value | |
| returned by the function. The function must accept exactly three | |
| positional arguments: the two endpoints of an edge and the | |
| dictionary of edge attributes for that edge. The function must | |
| return a number. | |
| If this is None, every edge has weight/distance/cost 1. | |
| Weights stored as floating point values can lead to small round-off | |
| errors in distances. Use integer weights to avoid this. | |
| Weights should be positive, since they are distances. | |
| Returns | |
| ------- | |
| value : value of the requested metric | |
| int for "diameter" and "radius" or | |
| list of nodes for "center" and "periphery" or | |
| dictionary of eccentricity values keyed by node for "eccentricities" | |
| Raises | |
| ------ | |
| NetworkXError | |
| If the graph consists of multiple components | |
| ValueError | |
| If `compute` is not one of "diameter", "radius", "periphery", "center", or "eccentricities". | |
| Notes | |
| ----- | |
| This algorithm was proposed in [1]_ and discussed further in [2]_ and [3]_. | |
| References | |
| ---------- | |
| .. [1] F. W. Takes, W. A. Kosters, | |
| "Determining the diameter of small world networks." | |
| Proceedings of the 20th ACM international conference on Information and knowledge management, 2011 | |
| https://dl.acm.org/doi/abs/10.1145/2063576.2063748 | |
| .. [2] F. W. Takes, W. A. Kosters, | |
| "Computing the Eccentricity Distribution of Large Graphs." | |
| Algorithms, 2013 | |
| https://www.mdpi.com/1999-4893/6/1/100 | |
| .. [3] M. Borassi, P. Crescenzi, M. Habib, W. A. Kosters, A. Marino, F. W. Takes, | |
| "Fast diameter and radius BFS-based computation in (weakly connected) real-world graphs: With an application to the six degrees of separation games. " | |
| Theoretical Computer Science, 2015 | |
| https://www.sciencedirect.com/science/article/pii/S0304397515001644 | |
| """ | |
| # init variables | |
| degrees = dict(G.degree()) # start with the highest degree node | |
| minlowernode = max(degrees, key=degrees.get) | |
| N = len(degrees) # number of nodes | |
| # alternate between smallest lower and largest upper bound | |
| high = False | |
| # status variables | |
| ecc_lower = dict.fromkeys(G, 0) | |
| ecc_upper = dict.fromkeys(G, N) | |
| candidates = set(G) | |
| # (re)set bound extremes | |
| minlower = N | |
| maxlower = 0 | |
| minupper = N | |
| maxupper = 0 | |
| # repeat the following until there are no more candidates | |
| while candidates: | |
| if high: | |
| current = maxuppernode # select node with largest upper bound | |
| else: | |
| current = minlowernode # select node with smallest lower bound | |
| high = not high | |
| # get distances from/to current node and derive eccentricity | |
| dist = nx.shortest_path_length(G, source=current, weight=weight) | |
| if len(dist) != N: | |
| msg = "Cannot compute metric because graph is not connected." | |
| raise nx.NetworkXError(msg) | |
| current_ecc = max(dist.values()) | |
| # print status update | |
| # print ("ecc of " + str(current) + " (" + str(ecc_lower[current]) + "/" | |
| # + str(ecc_upper[current]) + ", deg: " + str(dist[current]) + ") is " | |
| # + str(current_ecc)) | |
| # print(ecc_upper) | |
| # (re)set bound extremes | |
| maxuppernode = None | |
| minlowernode = None | |
| # update node bounds | |
| for i in candidates: | |
| # update eccentricity bounds | |
| d = dist[i] | |
| ecc_lower[i] = low = max(ecc_lower[i], max(d, (current_ecc - d))) | |
| ecc_upper[i] = upp = min(ecc_upper[i], current_ecc + d) | |
| # update min/max values of lower and upper bounds | |
| minlower = min(ecc_lower[i], minlower) | |
| maxlower = max(ecc_lower[i], maxlower) | |
| minupper = min(ecc_upper[i], minupper) | |
| maxupper = max(ecc_upper[i], maxupper) | |
| # update candidate set | |
| if compute == "diameter": | |
| ruled_out = { | |
| i | |
| for i in candidates | |
| if ecc_upper[i] <= maxlower and 2 * ecc_lower[i] >= maxupper | |
| } | |
| elif compute == "radius": | |
| ruled_out = { | |
| i | |
| for i in candidates | |
| if ecc_lower[i] >= minupper and ecc_upper[i] + 1 <= 2 * minlower | |
| } | |
| elif compute == "periphery": | |
| ruled_out = { | |
| i | |
| for i in candidates | |
| if ecc_upper[i] < maxlower | |
| and (maxlower == maxupper or ecc_lower[i] > maxupper) | |
| } | |
| elif compute == "center": | |
| ruled_out = { | |
| i | |
| for i in candidates | |
| if ecc_lower[i] > minupper | |
| and (minlower == minupper or ecc_upper[i] + 1 < 2 * minlower) | |
| } | |
| elif compute == "eccentricities": | |
| ruled_out = set() | |
| else: | |
| msg = "compute must be one of 'diameter', 'radius', 'periphery', 'center', 'eccentricities'" | |
| raise ValueError(msg) | |
| ruled_out.update(i for i in candidates if ecc_lower[i] == ecc_upper[i]) | |
| candidates -= ruled_out | |
| # for i in ruled_out: | |
| # print("removing %g: ecc_u: %g maxl: %g ecc_l: %g maxu: %g"% | |
| # (i,ecc_upper[i],maxlower,ecc_lower[i],maxupper)) | |
| # print("node %g: ecc_u: %g maxl: %g ecc_l: %g maxu: %g"% | |
| # (4,ecc_upper[4],maxlower,ecc_lower[4],maxupper)) | |
| # print("NODE 4: %g"%(ecc_upper[4] <= maxlower)) | |
| # print("NODE 4: %g"%(2 * ecc_lower[4] >= maxupper)) | |
| # print("NODE 4: %g"%(ecc_upper[4] <= maxlower | |
| # and 2 * ecc_lower[4] >= maxupper)) | |
| # updating maxuppernode and minlowernode for selection in next round | |
| for i in candidates: | |
| if ( | |
| minlowernode is None | |
| or ( | |
| ecc_lower[i] == ecc_lower[minlowernode] | |
| and degrees[i] > degrees[minlowernode] | |
| ) | |
| or (ecc_lower[i] < ecc_lower[minlowernode]) | |
| ): | |
| minlowernode = i | |
| if ( | |
| maxuppernode is None | |
| or ( | |
| ecc_upper[i] == ecc_upper[maxuppernode] | |
| and degrees[i] > degrees[maxuppernode] | |
| ) | |
| or (ecc_upper[i] > ecc_upper[maxuppernode]) | |
| ): | |
| maxuppernode = i | |
| # print status update | |
| # print (" min=" + str(minlower) + "/" + str(minupper) + | |
| # " max=" + str(maxlower) + "/" + str(maxupper) + | |
| # " candidates: " + str(len(candidates))) | |
| # print("cand:",candidates) | |
| # print("ecc_l",ecc_lower) | |
| # print("ecc_u",ecc_upper) | |
| # wait = input("press Enter to continue") | |
| # return the correct value of the requested metric | |
| if compute == "diameter": | |
| return maxlower | |
| if compute == "radius": | |
| return minupper | |
| if compute == "periphery": | |
| p = [v for v in G if ecc_lower[v] == maxlower] | |
| return p | |
| if compute == "center": | |
| c = [v for v in G if ecc_upper[v] == minupper] | |
| return c | |
| if compute == "eccentricities": | |
| return ecc_lower | |
| return None | |
| def eccentricity(G, v=None, sp=None, weight=None): | |
| """Returns the eccentricity of nodes in G. | |
| The eccentricity of a node v is the maximum distance from v to | |
| all other nodes in G. | |
| Parameters | |
| ---------- | |
| G : NetworkX graph | |
| A graph | |
| v : node, optional | |
| Return value of specified node | |
| sp : dict of dicts, optional | |
| All pairs shortest path lengths as a dictionary of dictionaries | |
| weight : string, function, or None (default=None) | |
| If this is a string, then edge weights will be accessed via the | |
| edge attribute with this key (that is, the weight of the edge | |
| joining `u` to `v` will be ``G.edges[u, v][weight]``). If no | |
| such edge attribute exists, the weight of the edge is assumed to | |
| be one. | |
| If this is a function, the weight of an edge is the value | |
| returned by the function. The function must accept exactly three | |
| positional arguments: the two endpoints of an edge and the | |
| dictionary of edge attributes for that edge. The function must | |
| return a number. | |
| If this is None, every edge has weight/distance/cost 1. | |
| Weights stored as floating point values can lead to small round-off | |
| errors in distances. Use integer weights to avoid this. | |
| Weights should be positive, since they are distances. | |
| Returns | |
| ------- | |
| ecc : dictionary | |
| A dictionary of eccentricity values keyed by node. | |
| Examples | |
| -------- | |
| >>> G = nx.Graph([(1, 2), (1, 3), (1, 4), (3, 4), (3, 5), (4, 5)]) | |
| >>> dict(nx.eccentricity(G)) | |
| {1: 2, 2: 3, 3: 2, 4: 2, 5: 3} | |
| >>> dict(nx.eccentricity(G, v=[1, 5])) # This returns the eccentricity of node 1 & 5 | |
| {1: 2, 5: 3} | |
| """ | |
| # if v is None: # none, use entire graph | |
| # nodes=G.nodes() | |
| # elif v in G: # is v a single node | |
| # nodes=[v] | |
| # else: # assume v is a container of nodes | |
| # nodes=v | |
| order = G.order() | |
| e = {} | |
| for n in G.nbunch_iter(v): | |
| if sp is None: | |
| length = nx.shortest_path_length(G, source=n, weight=weight) | |
| L = len(length) | |
| else: | |
| try: | |
| length = sp[n] | |
| L = len(length) | |
| except TypeError as err: | |
| raise nx.NetworkXError('Format of "sp" is invalid.') from err | |
| if L != order: | |
| if G.is_directed(): | |
| msg = ( | |
| "Found infinite path length because the digraph is not" | |
| " strongly connected" | |
| ) | |
| else: | |
| msg = "Found infinite path length because the graph is not" " connected" | |
| raise nx.NetworkXError(msg) | |
| e[n] = max(length.values()) | |
| if v in G: | |
| return e[v] # return single value | |
| return e | |
| def diameter(G, e=None, usebounds=False, weight=None): | |
| """Returns the diameter of the graph G. | |
| The diameter is the maximum eccentricity. | |
| Parameters | |
| ---------- | |
| G : NetworkX graph | |
| A graph | |
| e : eccentricity dictionary, optional | |
| A precomputed dictionary of eccentricities. | |
| weight : string, function, or None | |
| If this is a string, then edge weights will be accessed via the | |
| edge attribute with this key (that is, the weight of the edge | |
| joining `u` to `v` will be ``G.edges[u, v][weight]``). If no | |
| such edge attribute exists, the weight of the edge is assumed to | |
| be one. | |
| If this is a function, the weight of an edge is the value | |
| returned by the function. The function must accept exactly three | |
| positional arguments: the two endpoints of an edge and the | |
| dictionary of edge attributes for that edge. The function must | |
| return a number. | |
| If this is None, every edge has weight/distance/cost 1. | |
| Weights stored as floating point values can lead to small round-off | |
| errors in distances. Use integer weights to avoid this. | |
| Weights should be positive, since they are distances. | |
| Returns | |
| ------- | |
| d : integer | |
| Diameter of graph | |
| Examples | |
| -------- | |
| >>> G = nx.Graph([(1, 2), (1, 3), (1, 4), (3, 4), (3, 5), (4, 5)]) | |
| >>> nx.diameter(G) | |
| 3 | |
| See Also | |
| -------- | |
| eccentricity | |
| """ | |
| if usebounds is True and e is None and not G.is_directed(): | |
| return _extrema_bounding(G, compute="diameter", weight=weight) | |
| if e is None: | |
| e = eccentricity(G, weight=weight) | |
| return max(e.values()) | |
| def periphery(G, e=None, usebounds=False, weight=None): | |
| """Returns the periphery of the graph G. | |
| The periphery is the set of nodes with eccentricity equal to the diameter. | |
| Parameters | |
| ---------- | |
| G : NetworkX graph | |
| A graph | |
| e : eccentricity dictionary, optional | |
| A precomputed dictionary of eccentricities. | |
| weight : string, function, or None | |
| If this is a string, then edge weights will be accessed via the | |
| edge attribute with this key (that is, the weight of the edge | |
| joining `u` to `v` will be ``G.edges[u, v][weight]``). If no | |
| such edge attribute exists, the weight of the edge is assumed to | |
| be one. | |
| If this is a function, the weight of an edge is the value | |
| returned by the function. The function must accept exactly three | |
| positional arguments: the two endpoints of an edge and the | |
| dictionary of edge attributes for that edge. The function must | |
| return a number. | |
| If this is None, every edge has weight/distance/cost 1. | |
| Weights stored as floating point values can lead to small round-off | |
| errors in distances. Use integer weights to avoid this. | |
| Weights should be positive, since they are distances. | |
| Returns | |
| ------- | |
| p : list | |
| List of nodes in periphery | |
| Examples | |
| -------- | |
| >>> G = nx.Graph([(1, 2), (1, 3), (1, 4), (3, 4), (3, 5), (4, 5)]) | |
| >>> nx.periphery(G) | |
| [2, 5] | |
| See Also | |
| -------- | |
| barycenter | |
| center | |
| """ | |
| if usebounds is True and e is None and not G.is_directed(): | |
| return _extrema_bounding(G, compute="periphery", weight=weight) | |
| if e is None: | |
| e = eccentricity(G, weight=weight) | |
| diameter = max(e.values()) | |
| p = [v for v in e if e[v] == diameter] | |
| return p | |
| def radius(G, e=None, usebounds=False, weight=None): | |
| """Returns the radius of the graph G. | |
| The radius is the minimum eccentricity. | |
| Parameters | |
| ---------- | |
| G : NetworkX graph | |
| A graph | |
| e : eccentricity dictionary, optional | |
| A precomputed dictionary of eccentricities. | |
| weight : string, function, or None | |
| If this is a string, then edge weights will be accessed via the | |
| edge attribute with this key (that is, the weight of the edge | |
| joining `u` to `v` will be ``G.edges[u, v][weight]``). If no | |
| such edge attribute exists, the weight of the edge is assumed to | |
| be one. | |
| If this is a function, the weight of an edge is the value | |
| returned by the function. The function must accept exactly three | |
| positional arguments: the two endpoints of an edge and the | |
| dictionary of edge attributes for that edge. The function must | |
| return a number. | |
| If this is None, every edge has weight/distance/cost 1. | |
| Weights stored as floating point values can lead to small round-off | |
| errors in distances. Use integer weights to avoid this. | |
| Weights should be positive, since they are distances. | |
| Returns | |
| ------- | |
| r : integer | |
| Radius of graph | |
| Examples | |
| -------- | |
| >>> G = nx.Graph([(1, 2), (1, 3), (1, 4), (3, 4), (3, 5), (4, 5)]) | |
| >>> nx.radius(G) | |
| 2 | |
| """ | |
| if usebounds is True and e is None and not G.is_directed(): | |
| return _extrema_bounding(G, compute="radius", weight=weight) | |
| if e is None: | |
| e = eccentricity(G, weight=weight) | |
| return min(e.values()) | |
| def center(G, e=None, usebounds=False, weight=None): | |
| """Returns the center of the graph G. | |
| The center is the set of nodes with eccentricity equal to radius. | |
| Parameters | |
| ---------- | |
| G : NetworkX graph | |
| A graph | |
| e : eccentricity dictionary, optional | |
| A precomputed dictionary of eccentricities. | |
| weight : string, function, or None | |
| If this is a string, then edge weights will be accessed via the | |
| edge attribute with this key (that is, the weight of the edge | |
| joining `u` to `v` will be ``G.edges[u, v][weight]``). If no | |
| such edge attribute exists, the weight of the edge is assumed to | |
| be one. | |
| If this is a function, the weight of an edge is the value | |
| returned by the function. The function must accept exactly three | |
| positional arguments: the two endpoints of an edge and the | |
| dictionary of edge attributes for that edge. The function must | |
| return a number. | |
| If this is None, every edge has weight/distance/cost 1. | |
| Weights stored as floating point values can lead to small round-off | |
| errors in distances. Use integer weights to avoid this. | |
| Weights should be positive, since they are distances. | |
| Returns | |
| ------- | |
| c : list | |
| List of nodes in center | |
| Examples | |
| -------- | |
| >>> G = nx.Graph([(1, 2), (1, 3), (1, 4), (3, 4), (3, 5), (4, 5)]) | |
| >>> list(nx.center(G)) | |
| [1, 3, 4] | |
| See Also | |
| -------- | |
| barycenter | |
| periphery | |
| """ | |
| if usebounds is True and e is None and not G.is_directed(): | |
| return _extrema_bounding(G, compute="center", weight=weight) | |
| if e is None: | |
| e = eccentricity(G, weight=weight) | |
| radius = min(e.values()) | |
| p = [v for v in e if e[v] == radius] | |
| return p | |
| def barycenter(G, weight=None, attr=None, sp=None): | |
| r"""Calculate barycenter of a connected graph, optionally with edge weights. | |
| The :dfn:`barycenter` a | |
| :func:`connected <networkx.algorithms.components.is_connected>` graph | |
| :math:`G` is the subgraph induced by the set of its nodes :math:`v` | |
| minimizing the objective function | |
| .. math:: | |
| \sum_{u \in V(G)} d_G(u, v), | |
| where :math:`d_G` is the (possibly weighted) :func:`path length | |
| <networkx.algorithms.shortest_paths.generic.shortest_path_length>`. | |
| The barycenter is also called the :dfn:`median`. See [West01]_, p. 78. | |
| Parameters | |
| ---------- | |
| G : :class:`networkx.Graph` | |
| The connected graph :math:`G`. | |
| weight : :class:`str`, optional | |
| Passed through to | |
| :func:`~networkx.algorithms.shortest_paths.generic.shortest_path_length`. | |
| attr : :class:`str`, optional | |
| If given, write the value of the objective function to each node's | |
| `attr` attribute. Otherwise do not store the value. | |
| sp : dict of dicts, optional | |
| All pairs shortest path lengths as a dictionary of dictionaries | |
| Returns | |
| ------- | |
| list | |
| Nodes of `G` that induce the barycenter of `G`. | |
| Raises | |
| ------ | |
| NetworkXNoPath | |
| If `G` is disconnected. `G` may appear disconnected to | |
| :func:`barycenter` if `sp` is given but is missing shortest path | |
| lengths for any pairs. | |
| ValueError | |
| If `sp` and `weight` are both given. | |
| Examples | |
| -------- | |
| >>> G = nx.Graph([(1, 2), (1, 3), (1, 4), (3, 4), (3, 5), (4, 5)]) | |
| >>> nx.barycenter(G) | |
| [1, 3, 4] | |
| See Also | |
| -------- | |
| center | |
| periphery | |
| """ | |
| if sp is None: | |
| sp = nx.shortest_path_length(G, weight=weight) | |
| else: | |
| sp = sp.items() | |
| if weight is not None: | |
| raise ValueError("Cannot use both sp, weight arguments together") | |
| smallest, barycenter_vertices, n = float("inf"), [], len(G) | |
| for v, dists in sp: | |
| if len(dists) < n: | |
| raise nx.NetworkXNoPath( | |
| f"Input graph {G} is disconnected, so every induced subgraph " | |
| "has infinite barycentricity." | |
| ) | |
| barycentricity = sum(dists.values()) | |
| if attr is not None: | |
| G.nodes[v][attr] = barycentricity | |
| if barycentricity < smallest: | |
| smallest = barycentricity | |
| barycenter_vertices = [v] | |
| elif barycentricity == smallest: | |
| barycenter_vertices.append(v) | |
| return barycenter_vertices | |
| def _count_lu_permutations(perm_array): | |
| """Counts the number of permutations in SuperLU perm_c or perm_r""" | |
| perm_cnt = 0 | |
| arr = perm_array.tolist() | |
| for i in range(len(arr)): | |
| if i != arr[i]: | |
| perm_cnt += 1 | |
| n = arr.index(i) | |
| arr[n] = arr[i] | |
| arr[i] = i | |
| return perm_cnt | |
| def resistance_distance(G, nodeA=None, nodeB=None, weight=None, invert_weight=True): | |
| """Returns the resistance distance between every pair of nodes on graph G. | |
| The resistance distance between two nodes of a graph is akin to treating | |
| the graph as a grid of resistors with a resistance equal to the provided | |
| weight [1]_, [2]_. | |
| If weight is not provided, then a weight of 1 is used for all edges. | |
| If two nodes are the same, the resistance distance is zero. | |
| Parameters | |
| ---------- | |
| G : NetworkX graph | |
| A graph | |
| nodeA : node or None, optional (default=None) | |
| A node within graph G. | |
| If None, compute resistance distance using all nodes as source nodes. | |
| nodeB : node or None, optional (default=None) | |
| A node within graph G. | |
| If None, compute resistance distance using all nodes as target nodes. | |
| weight : string or None, optional (default=None) | |
| The edge data key used to compute the resistance distance. | |
| If None, then each edge has weight 1. | |
| invert_weight : boolean (default=True) | |
| Proper calculation of resistance distance requires building the | |
| Laplacian matrix with the reciprocal of the weight. Not required | |
| if the weight is already inverted. Weight cannot be zero. | |
| Returns | |
| ------- | |
| rd : dict or float | |
| If `nodeA` and `nodeB` are given, resistance distance between `nodeA` | |
| and `nodeB`. If `nodeA` or `nodeB` is unspecified (the default), a | |
| dictionary of nodes with resistance distances as the value. | |
| Raises | |
| ------ | |
| NetworkXNotImplemented | |
| If `G` is a directed graph. | |
| NetworkXError | |
| If `G` is not connected, or contains no nodes, | |
| or `nodeA` is not in `G` or `nodeB` is not in `G`. | |
| Examples | |
| -------- | |
| >>> G = nx.Graph([(1, 2), (1, 3), (1, 4), (3, 4), (3, 5), (4, 5)]) | |
| >>> round(nx.resistance_distance(G, 1, 3), 10) | |
| 0.625 | |
| Notes | |
| ----- | |
| The implementation is based on Theorem A in [2]_. Self-loops are ignored. | |
| Multi-edges are contracted in one edge with weight equal to the harmonic sum of the weights. | |
| References | |
| ---------- | |
| .. [1] Wikipedia | |
| "Resistance distance." | |
| https://en.wikipedia.org/wiki/Resistance_distance | |
| .. [2] D. J. Klein and M. Randic. | |
| Resistance distance. | |
| J. of Math. Chem. 12:81-95, 1993. | |
| """ | |
| import numpy as np | |
| if len(G) == 0: | |
| raise nx.NetworkXError("Graph G must contain at least one node.") | |
| if not nx.is_connected(G): | |
| raise nx.NetworkXError("Graph G must be strongly connected.") | |
| if nodeA is not None and nodeA not in G: | |
| raise nx.NetworkXError("Node A is not in graph G.") | |
| if nodeB is not None and nodeB not in G: | |
| raise nx.NetworkXError("Node B is not in graph G.") | |
| G = G.copy() | |
| node_list = list(G) | |
| # Invert weights | |
| if invert_weight and weight is not None: | |
| if G.is_multigraph(): | |
| for u, v, k, d in G.edges(keys=True, data=True): | |
| d[weight] = 1 / d[weight] | |
| else: | |
| for u, v, d in G.edges(data=True): | |
| d[weight] = 1 / d[weight] | |
| # Compute resistance distance using the Pseudo-inverse of the Laplacian | |
| # Self-loops are ignored | |
| L = nx.laplacian_matrix(G, weight=weight).todense() | |
| Linv = np.linalg.pinv(L, hermitian=True) | |
| # Return relevant distances | |
| if nodeA is not None and nodeB is not None: | |
| i = node_list.index(nodeA) | |
| j = node_list.index(nodeB) | |
| return Linv[i, i] + Linv[j, j] - Linv[i, j] - Linv[j, i] | |
| elif nodeA is not None: | |
| i = node_list.index(nodeA) | |
| d = {} | |
| for n in G: | |
| j = node_list.index(n) | |
| d[n] = Linv[i, i] + Linv[j, j] - Linv[i, j] - Linv[j, i] | |
| return d | |
| elif nodeB is not None: | |
| j = node_list.index(nodeB) | |
| d = {} | |
| for n in G: | |
| i = node_list.index(n) | |
| d[n] = Linv[i, i] + Linv[j, j] - Linv[i, j] - Linv[j, i] | |
| return d | |
| else: | |
| d = {} | |
| for n in G: | |
| i = node_list.index(n) | |
| d[n] = {} | |
| for n2 in G: | |
| j = node_list.index(n2) | |
| d[n][n2] = Linv[i, i] + Linv[j, j] - Linv[i, j] - Linv[j, i] | |
| return d | |
| def kemeny_constant(G, *, weight=None): | |
| """Returns the Kemeny constant of the given graph. | |
| The *Kemeny constant* (or Kemeny's constant) of a graph `G` | |
| can be computed by regarding the graph as a Markov chain. | |
| The Kemeny constant is then the expected number of time steps | |
| to transition from a starting state i to a random destination state | |
| sampled from the Markov chain's stationary distribution. | |
| The Kemeny constant is independent of the chosen initial state [1]_. | |
| The Kemeny constant measures the time needed for spreading | |
| across a graph. Low values indicate a closely connected graph | |
| whereas high values indicate a spread-out graph. | |
| If weight is not provided, then a weight of 1 is used for all edges. | |
| Since `G` represents a Markov chain, the weights must be positive. | |
| Parameters | |
| ---------- | |
| G : NetworkX graph | |
| weight : string or None, optional (default=None) | |
| The edge data key used to compute the Kemeny constant. | |
| If None, then each edge has weight 1. | |
| Returns | |
| ------- | |
| K : float | |
| The Kemeny constant of the graph `G`. | |
| Raises | |
| ------ | |
| NetworkXNotImplemented | |
| If the graph `G` is directed. | |
| NetworkXError | |
| If the graph `G` is not connected, or contains no nodes, | |
| or has edges with negative weights. | |
| Examples | |
| -------- | |
| >>> G = nx.complete_graph(5) | |
| >>> round(nx.kemeny_constant(G), 10) | |
| 3.2 | |
| Notes | |
| ----- | |
| The implementation is based on equation (3.3) in [2]_. | |
| Self-loops are allowed and indicate a Markov chain where | |
| the state can remain the same. Multi-edges are contracted | |
| in one edge with weight equal to the sum of the weights. | |
| References | |
| ---------- | |
| .. [1] Wikipedia | |
| "Kemeny's constant." | |
| https://en.wikipedia.org/wiki/Kemeny%27s_constant | |
| .. [2] Lovász L. | |
| Random walks on graphs: A survey. | |
| Paul Erdös is Eighty, vol. 2, Bolyai Society, | |
| Mathematical Studies, Keszthely, Hungary (1993), pp. 1-46 | |
| """ | |
| import numpy as np | |
| import scipy as sp | |
| if len(G) == 0: | |
| raise nx.NetworkXError("Graph G must contain at least one node.") | |
| if not nx.is_connected(G): | |
| raise nx.NetworkXError("Graph G must be connected.") | |
| if nx.is_negatively_weighted(G, weight=weight): | |
| raise nx.NetworkXError("The weights of graph G must be nonnegative.") | |
| # Compute matrix H = D^-1/2 A D^-1/2 | |
| A = nx.adjacency_matrix(G, weight=weight) | |
| n, m = A.shape | |
| diags = A.sum(axis=1) | |
| with np.errstate(divide="ignore"): | |
| diags_sqrt = 1.0 / np.sqrt(diags) | |
| diags_sqrt[np.isinf(diags_sqrt)] = 0 | |
| DH = sp.sparse.csr_array(sp.sparse.spdiags(diags_sqrt, 0, m, n, format="csr")) | |
| H = DH @ (A @ DH) | |
| # Compute eigenvalues of H | |
| eig = np.sort(sp.linalg.eigvalsh(H.todense())) | |
| # Compute the Kemeny constant | |
| return np.sum(1 / (1 - eig[:-1])) | |