| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095 |
- """Graph diameter, radius, eccentricity and other properties."""
- import math
- import networkx as nx
- from networkx.utils import not_implemented_for
- __all__ = [
- "eccentricity",
- "diameter",
- "harmonic_diameter",
- "radius",
- "periphery",
- "center",
- "barycenter",
- "resistance_distance",
- "kemeny_constant",
- "effective_graph_resistance",
- ]
- 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, math.inf)
- candidates = set(G)
- # (re)set bound extremes
- minlower = math.inf
- maxlower = 0
- minupper = math.inf
- 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
- @nx._dispatchable(edge_attrs="weight")
- 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
- @nx._dispatchable(edge_attrs="weight")
- 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.
- usebounds : bool, optional
- If `True`, use extrema bounding (see Notes) when computing the diameter
- for undirected graphs. Extrema bounding may accelerate the
- distance calculation for some graphs. `usebounds` is ignored if `G` is
- directed or if `e` is not `None`. Default is `False`.
- 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
- Notes
- -----
- When ``usebounds=True``, the computation makes use of smart lower
- and upper bounds and is often linear in the number of nodes, rather than
- quadratic (except for some border cases such as complete graphs or circle
- shaped-graphs).
- 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())
- @nx._dispatchable(edge_attrs="weight")
- def harmonic_diameter(G, sp=None, *, weight=None):
- """Returns the harmonic diameter of the graph G.
- The harmonic diameter of a graph is the harmonic mean of the distances
- between all pairs of distinct vertices. Graphs that are not strongly
- connected have infinite diameter and mean distance, making such
- measures not useful. Restricting the diameter or mean distance to
- finite distances yields paradoxical values (e.g., a perfect match
- would have diameter one). The harmonic mean handles gracefully
- infinite distances (e.g., a perfect match has harmonic diameter equal
- to the number of vertices minus one), making it possible to assign a
- meaningful value to all graphs.
- Note that in [1] the harmonic diameter is called "connectivity length":
- however, "harmonic diameter" is a more standard name from the
- theory of metric spaces. The name "harmonic mean distance" is perhaps
- a more descriptive name, but is not used in the literature, so we use the
- name "harmonic diameter" here.
- Parameters
- ----------
- G : NetworkX graph
- A graph
- sp : dict of dicts, optional
- All-pairs shortest path lengths as a dictionary of dictionaries
- weight : string, function, or None (default=None)
- If None, every edge has weight/distance 1.
- If a string, use this edge attribute as the edge weight.
- Any edge attribute not present defaults to 1.
- If 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.
- Returns
- -------
- hd : float
- Harmonic diameter of graph
- References
- ----------
- .. [1] Massimo Marchiori and Vito Latora, "Harmony in the small-world".
- *Physica A: Statistical Mechanics and Its Applications*
- 285(3-4), pages 539-546, 2000.
- <https://doi.org/10.1016/S0378-4371(00)00311-3>
- """
- order = G.order()
- sum_invd = 0
- for n in G:
- if sp is None:
- length = nx.single_source_dijkstra_path_length(G, n, weight=weight)
- else:
- try:
- length = sp[n]
- L = len(length)
- except TypeError as err:
- raise nx.NetworkXError('Format of "sp" is invalid.') from err
- for d in length.values():
- # Note that this will skip the zero distance from n to itself,
- # as it should be, but also zero-weight paths in weighted graphs.
- if d != 0:
- sum_invd += 1 / d
- if sum_invd != 0:
- return order * (order - 1) / sum_invd
- if order > 1:
- return math.inf
- return math.nan
- @nx._dispatchable(edge_attrs="weight")
- 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.
- usebounds : bool, optional
- If `True`, use extrema bounding (see Notes) when computing the periphery
- for undirected graphs. Extrema bounding may accelerate the
- distance calculation for some graphs. `usebounds` is ignored if `G` is
- directed or if `e` is not `None`. Default is `False`.
- 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
- Notes
- -----
- When ``usebounds=True``, the computation makes use of smart lower
- and upper bounds and is often linear in the number of nodes, rather than
- quadratic (except for some border cases such as complete graphs or circle
- shaped-graphs).
- 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
- @nx._dispatchable(edge_attrs="weight")
- 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.
- usebounds : bool, optional
- If `True`, use extrema bounding (see Notes) when computing the radius
- for undirected graphs. Extrema bounding may accelerate the
- distance calculation for some graphs. `usebounds` is ignored if `G` is
- directed or if `e` is not `None`. Default is `False`.
- 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
- Notes
- -----
- When ``usebounds=True``, the computation makes use of smart lower
- and upper bounds and is often linear in the number of nodes, rather than
- quadratic (except for some border cases such as complete graphs or circle
- shaped-graphs).
- 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())
- @nx._dispatchable(edge_attrs="weight")
- 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.
- usebounds : bool, optional
- If `True`, use extrema bounding (see Notes) when computing the center
- for undirected graphs. Extrema bounding may accelerate the
- distance calculation for some graphs. `usebounds` is ignored if `G` is
- directed or if `e` is not `None`. Default is `False`.
- 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
- Notes
- -----
- When ``usebounds=True``, the computation makes use of smart lower
- and upper bounds and is often linear in the number of nodes, rather than
- quadratic (except for some border cases such as complete graphs or circle
- shaped-graphs).
- 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
- --------
- :func:`~networkx.algorithms.tree.distance_measures.center` : tree center
- barycenter
- periphery
- :func:`~networkx.algorithms.tree.distance_measures.centroid` : tree centroid
- """
- 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 and weight is None and not G.is_directed() and nx.is_tree(G):
- return nx.tree.center(G)
- 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
- @nx._dispatchable(edge_attrs="weight", mutates_input={"attr": 2})
- 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
- :func:`~networkx.algorithms.tree.distance_measures.centroid` : tree centroid
- """
- if weight is None and attr is None and sp is None:
- if not G.is_directed() and nx.is_tree(G):
- return nx.tree.centroid(G)
- 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
- @not_implemented_for("directed")
- @nx._dispatchable(edge_attrs="weight")
- def resistance_distance(G, nodeA=None, nodeB=None, weight=None, invert_weight=True):
- """Returns the resistance distance between pairs of nodes in 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.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
- @not_implemented_for("directed")
- @nx._dispatchable(edge_attrs="weight")
- def effective_graph_resistance(G, weight=None, invert_weight=True):
- """Returns the Effective graph resistance of G.
- Also known as the Kirchhoff index.
- The effective graph resistance is defined as the sum
- of the resistance distance of every node pair in G [1]_.
- If weight is not provided, then a weight of 1 is used for all edges.
- The effective graph resistance of a disconnected graph is infinite.
- Parameters
- ----------
- G : NetworkX graph
- A graph
- weight : string or None, optional (default=None)
- The edge data key used to compute the effective graph resistance.
- 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
- -------
- RG : float
- The effective graph resistance of `G`.
- Raises
- ------
- NetworkXNotImplemented
- If `G` is a directed graph.
- NetworkXError
- If `G` does not contain any nodes.
- 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
- -----
- The implementation is based on Theorem 2.2 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] 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())
- @nx.utils.not_implemented_for("directed")
- @nx._dispatchable(edge_attrs="weight")
- 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
- -------
- 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.dia_array((diags_sqrt, 0), shape=(m, n)).tocsr()
- 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])))
|