Source code for networkx.algorithms.distance_measures

"""图的直径、半径、偏心率及其他属性。"""

import networkx as nx
from networkx.utils import not_implemented_for

__all__ = [
    "eccentricity",
    "diameter",
    "radius",
    "periphery",
    "center",
    "barycenter",
    "resistance_distance",
    "kemeny_constant",
    "effective_graph_resistance",
]


def _extrema_bounding(G, compute="diameter", weight=None):
    """计算无向图 G 的请求极值距离度量

计算基于智能的下界和上界,并且在实践中是线性的节点数量,而不是二次的(除了一些边界情况,如完全图或环形图)。

Parameters
----------
G : NetworkX 图
   一个无向图

compute : 表示请求度量的字符串
   "diameter" 表示最大偏心距值,
   "radius" 表示最小偏心距值,
   "periphery" 表示偏心距等于直径的节点集合,
   "center" 表示偏心距等于半径的节点集合,
   "eccentricities" 表示从每个节点到 G 中所有其他节点的最大距离

weight : 字符串、函数或 None
    如果这是一个字符串,则将通过具有此键的边属性访问边权重(即,连接 `u` 到 `v` 的边的权重将是 ``G.edges[u, v][weight]`` )。如果没有这样的边属性存在,则假设边的权重为 1。

    如果这是一个函数,边的权重是该函数返回的值。该函数必须接受三个位置参数:一条边的两个端点和该边的边属性字典。该函数必须返回一个数字。

    如果这是 None,则每条边的权重/距离/成本为 1。

    存储为浮点值的权重可能导致小的舍入误差。使用整数权重以避免这种情况。

    权重应该是正的,因为它们是距离。

Returns
-------
value : 请求度量的值
   "diameter" 和 "radius" 为整数,
   "center" 和 "periphery" 为节点列表,
   "eccentricities" 为以节点为键的偏心距值字典

Raises
------
NetworkXError
    如果图由多个组件组成
ValueError
    如果 `compute` 不是 "diameter"、"radius"、"periphery"、"center" 或 "eccentricities" 之一。

Notes
-----
此算法在 [1]_ 中提出,并在 [2]_ 和 [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


[docs] @nx._dispatchable(edge_attrs="weight") def eccentricity(G, v=None, sp=None, weight=None): """返回图 G 中节点的偏心率。 节点 v 的偏心率是从 v 到 G 中所有其他节点的最大距离。 Parameters ---------- G : NetworkX 图 一个图 v : 节点, 可选 指定节点的返回值 sp : 字典的字典, 可选 所有节点对的最短路径长度,作为一个字典的字典 weight : 字符串, 函数, 或 None (默认=None) 如果这是一个字符串,则将通过具有此键的边属性访问边权重(即,连接 `u` 到 `v` 的边的权重将是 ``G.edges[u, v][weight]`` )。如果不存在这样的边属性,则假定边的权重为 1。 如果这是一个函数,边的权重是该函数返回的值。该函数必须接受三个位置参数:一条边的两个端点和该边的边属性字典。该函数必须返回一个数字。 如果这是 None,则每条边的权重/距离/成本为 1。 存储为浮点值的权重可能导致小的舍入误差。使用整数权重可以避免这种情况。 权重应该是正数,因为它们是距离。 Returns ------- ecc : 字典 一个以节点为键的偏心率值字典。 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]) ... ) # 这返回节点 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
[docs] @nx._dispatchable(edge_attrs="weight") def diameter(G, e=None, usebounds=False, weight=None): """返回图 G 的直径。 直径是最大偏心率。 Parameters ---------- G : NetworkX 图 一个图 e : 偏心率字典, 可选 预先计算的偏心率字典。 weight : 字符串, 函数, 或 None 如果这是一个字符串, 则将通过具有此键的边属性访问边权重(即,连接 `u` 到 `v` 的边的权重将是 ``G.edges[u, v][weight]`` )。如果不存在这样的边属性,则假定边的权重为 1。 如果这是一个函数,边的权重是该函数返回的值。该函数必须接受三个位置参数:一条边的两个端点和该边的边属性字典。该函数必须返回一个数字。 如果这是 None,则每条边的权重/距离/成本为 1。 存储为浮点值的权重可能导致距离上的小舍入误差。使用整数权重以避免这种情况。 权重应为正数,因为它们是距离。 Returns ------- d : 整数 图的直径 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())
[docs] @nx._dispatchable(edge_attrs="weight") def periphery(G, e=None, usebounds=False, weight=None): """返回图 G 的外围节点集合。 外围节点是指偏心距等于直径的节点集合。 Parameters ---------- G : NetworkX 图 一个图 e : 偏心距字典, 可选 预先计算的偏心距字典。 weight : 字符串, 函数, 或 None 如果这是一个字符串,则将通过该键访问边属性来获取边权重(即,连接 `u` 到 `v` 的边的权重将是 ``G.edges[u, v][weight]`` )。如果不存在这样的边属性,则假定边的权重为 1。 如果这是一个函数,边的权重是该函数返回的值。该函数必须接受三个位置参数:一条边的两个端点和该边的属性字典。该函数必须返回一个数字。 如果这是 None,则每条边的权重/距离/成本为 1。 存储为浮点值的权重可能导致小的舍入误差。使用整数权重可以避免这种情况。 权重应该是正数,因为它们代表距离。 Returns ------- p : 列表 外围节点的列表 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
[docs] @nx._dispatchable(edge_attrs="weight") def radius(G, e=None, usebounds=False, weight=None): """返回图 G 的半径。 半径是离心率的最小值。 Parameters ---------- G : NetworkX 图 一个图 e : 离心率字典, 可选 预先计算的离心率字典。 weight : 字符串, 函数, 或 None 如果这是一个字符串, 则将通过具有此键的边属性访问边权重(即,连接 `u` 到 `v` 的边的权重将是 ``G.edges[u, v][weight]`` )。如果不存在这样的边属性,则假定边的权重为 1。 如果这是一个函数,边的权重是该函数返回的值。该函数必须接受三个位置参数:一条边的两个端点和该边的边属性字典。该函数必须返回一个数字。 如果这是 None,则每条边的权重/距离/成本为 1。 存储为浮点值的权重可能导致距离上的小舍入误差。使用整数权重可以避免这种情况。 权重应该是正数,因为它们是距离。 Returns ------- r : 整数 图的半径 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())
[docs] @nx._dispatchable(edge_attrs="weight") def center(G, e=None, usebounds=False, weight=None): """返回图 G 的中心。 中心是离心率等于半径的节点集合。 Parameters ---------- G : NetworkX 图 一个图 e : 离心率字典, 可选 预先计算的离心率字典。 weight : 字符串, 函数, 或 None 如果这是一个字符串, 则将通过该键访问边属性来获取边权重(即,连接 `u` 到 `v` 的边的权重将是 ``G.edges[u, v][weight]`` )。如果不存在这样的边属性,则假定边的权重为 1。 如果这是一个函数,边的权重是该函数返回的值。该函数必须接受三个位置参数:一条边的两个端点和该边的边属性字典。该函数必须返回一个数字。 如果这是 None,则每条边的权重/距离/成本为 1。 存储为浮点值的权重可能导致小的舍入误差。使用整数权重可以避免这种情况。 权重应该是正数,因为它们代表距离。 Returns ------- c : 列表 中心节点的列表 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
[docs] @nx._dispatchable(edge_attrs="weight", mutates_input={"attr": 2}) def barycenter(G, weight=None, attr=None, sp=None): r"""计算连通图的重心,可选地考虑边权重。 :dfn:`重心` 是一个 :func:`连通 <networkx.algorithms.components.is_connected>` 图 :math:`G` 的子图,由其节点 :math:`v` 组成的集合诱导,最小化目标函数 .. math:: \sum_{u \in V(G)} d_G(u, v), 其中 :math:`d_G` 是(可能加权的):func:`路径长度 <networkx.algorithms.shortest_paths.generic.shortest_path_length>` 。 重心也称为 :dfn:`中位数` 。参见 [West01]_,第 78 页。 Parameters ---------- G : :class:`networkx.Graph` 连通图 :math:`G` 。 weight : :class:`str` , 可选 传递给 :func:`~networkx.algorithms.shortest_paths.generic.shortest_path_length` 。 attr : :class:`str` , 可选 如果给定,将目标函数的值写入每个节点的 `attr` 属性。否则不存储该值。 sp : dict of dicts, 可选 所有对最短路径长度作为字典的字典 Returns ------- list `G` 的节点,诱导 `G` 的重心。 Raises ------ NetworkXNoPath 如果 `G` 是不连通的。 `G` 可能对 :func:`barycenter` 显示为不连通,如果 `sp` 给定但缺少任何对的最短路径长度。 ValueError 如果 `sp` 和 `weight` 同时给定。 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) if attr is not None: nx._clear_cache(G) return barycenter_vertices
[docs] @not_implemented_for("directed") @nx._dispatchable(edge_attrs="weight") def resistance_distance(G, nodeA=None, nodeB=None, weight=None, invert_weight=True): """返回图 G 中节点对之间的电阻距离。 图的两个节点之间的电阻距离类似于将图视为一个电阻器网格,其电阻等于提供的权重 [1]_, [2]_。 如果没有提供权重,则所有边的权重为 1。 如果两个节点相同,则电阻距离为零。 Parameters ---------- G : NetworkX 图 一个图 nodeA : 节点或 None, 可选 (默认=None) 图 G 中的一个节点。 如果为 None,则使用所有节点作为源节点计算电阻距离。 nodeB : 节点或 None, 可选 (默认=None) 图 G 中的一个节点。 如果为 None,则使用所有节点作为目标节点计算电阻距离。 weight : 字符串或 None, 可选 (默认=None) 用于计算电阻距离的边数据键。 如果为 None,则每条边的权重为 1。 invert_weight : 布尔值 (默认=True) 正确计算电阻距离需要使用权重的倒数构建拉普拉斯矩阵。如果权重已经倒数,则不需要。权重不能为零。 Returns ------- rd : 字典或浮点数 如果指定了 `nodeA` 和 `nodeB` ,则返回 `nodeA` 和 `nodeB` 之间的电阻距离。如果 `nodeA` 或 `nodeB` 未指定(默认),则返回一个字典,节点为键,电阻距离为值。 Raises ------ NetworkXNotImplemented 如果 `G` 是有向图。 NetworkXError 如果 `G` 不连通,或不包含节点, 或 `nodeA` 不在 `G` 中,或 `nodeB` 不在 `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 ----- 实现基于 [2]_ 中的定理 A。忽略自环。多重边合并为一条边,权重等于权重的调和和。 References ---------- .. [1] 维基百科 "电阻距离" https://en.wikipedia.org/wiki/Resistance_distance .. [2] D. J. Klein 和 M. Randic。 "电阻距离" J. 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.item(i, i) + Linv.item(j, j) - Linv.item(i, j) - Linv.item(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.item(i, i) + Linv.item(j, j) - Linv.item(i, j) - Linv.item(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.item(i, i) + Linv.item(j, j) - Linv.item(i, j) - Linv.item(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.item(i, i) + Linv.item(j, j) - Linv.item(i, j) - Linv.item(j, i) ) return d
[docs] @not_implemented_for("directed") @nx._dispatchable(edge_attrs="weight") def effective_graph_resistance(G, weight=None, invert_weight=True): """返回图 G 的有效图电阻。 也称为基尔霍夫指数。 有效图电阻定义为 G 中每对节点的电阻距离之和 [1]。 如果未提供权重,则所有边的权重为 1。 不连通图的有效图电阻为无穷大。 Parameters ---------- G : NetworkX 图 一个图 weight : 字符串或 None, 可选 (默认=None) 用于计算有效图电阻的边数据键。 如果为 None,则每条边的权重为 1。 invert_weight : 布尔值 (默认=True) 电阻距离的正确计算需要使用权重的倒数构建拉普拉斯矩阵。 如果权重已经倒数,则不需要。权重不能为零。 Returns ------- RG : float `G` 的有效图电阻。 Raises ------ NetworkXNotImplemented 如果 `G` 是有向图。 NetworkXError 如果 `G` 不包含任何节点。 Examples -------- >>> G = nx.Graph([(1, 2), (1, 3), (1, 4), (3, 4), (3, 5), (4, 5)]) >>> round(nx.effective_graph_resistance(G), 10) 10.25 Notes ----- 该实现基于 [2] 中的定理 2.2。忽略自环。 多重边被合并为一条边,权重等于权重的调和和。 References ---------- .. [1] Wolfram "Kirchhoff Index." https://mathworld.wolfram.com/KirchhoffIndex.html .. [2] W. Ellens, F. M. Spieksma, P. Van Mieghem, A. Jamakovic, R. E. Kooij. Effective graph resistance. Lin. Alg. Appl. 435:2491-2506, 2011. """ import numpy as np if len(G) == 0: raise nx.NetworkXError("Graph G must contain at least one node.") # Disconnected graphs have infinite Effective graph resistance if not nx.is_connected(G): return float("inf") # Invert weights G = G.copy() 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] # Get Laplacian eigenvalues mu = np.sort(nx.laplacian_spectrum(G, weight=weight)) # Compute Effective graph resistance based on spectrum of the Laplacian # Self-loops are ignored return float(np.sum(1 / mu[1:]) * G.number_of_nodes())
[docs] @nx.utils.not_implemented_for("directed") @nx._dispatchable(edge_attrs="weight") def kemeny_constant(G, *, weight=None): """返回给定图的Kemeny常数。 *Kemeny常数*(或Kemeny常数)是指将图 `G` 视为马尔可夫链时计算得到的常数。Kemeny常数是从起始状态i到从马尔可夫链的平稳分布中随机抽取的目标状态的预期时间步数。Kemeny常数与所选择的初始状态无关[1]。 Kemeny常数衡量了在图上传播所需的时间。低值表示图紧密连接,而高值表示图分散。 如果没有提供权重,则所有边的权重为1。 由于 `G` 表示一个马尔可夫链,权重必须为正。 Parameters ---------- G : NetworkX图 weight : 字符串或None, 可选 (默认=None) 用于计算Kemeny常数的边数据键。如果为None,则每条边的权重为1。 Returns ------- float 图 `G` 的Kemeny常数。 Raises ------ NetworkXNotImplemented 如果图 `G` 是有向的。 NetworkXError 如果图 `G` 不连通,或不包含节点,或有边权重为负。 Examples -------- >>> G = nx.complete_graph(5) >>> round(nx.kemeny_constant(G), 10) 3.2 Notes ----- 该实现基于[2]中的公式(3.3)。自环是允许的,表示状态可以保持不变的马尔可夫链。多重边被合并为一条边,权重等于各权重之和。 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 float(np.sum(1 / (1 - eig[:-1])))