about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/networkx/algorithms/centrality/reaching.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/networkx/algorithms/centrality/reaching.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-4a52a71956a8d46fcb7294ac71734504bb09bcc2.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/networkx/algorithms/centrality/reaching.py')
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/centrality/reaching.py209
1 files changed, 209 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/centrality/reaching.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/centrality/reaching.py
new file mode 100644
index 00000000..378e8a05
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/centrality/reaching.py
@@ -0,0 +1,209 @@
+"""Functions for computing reaching centrality of a node or a graph."""
+
+import networkx as nx
+from networkx.utils import pairwise
+
+__all__ = ["global_reaching_centrality", "local_reaching_centrality"]
+
+
+def _average_weight(G, path, weight=None):
+    """Returns the average weight of an edge in a weighted path.
+
+    Parameters
+    ----------
+    G : graph
+      A networkx graph.
+
+    path: list
+      A list of vertices that define the path.
+
+    weight : None or string, optional (default=None)
+      If None, edge weights are ignored.  Then the average weight of an edge
+      is assumed to be the multiplicative inverse of the length of the path.
+      Otherwise holds the name of the edge attribute used as weight.
+    """
+    path_length = len(path) - 1
+    if path_length <= 0:
+        return 0
+    if weight is None:
+        return 1 / path_length
+    total_weight = sum(G.edges[i, j][weight] for i, j in pairwise(path))
+    return total_weight / path_length
+
+
+@nx._dispatchable(edge_attrs="weight")
+def global_reaching_centrality(G, weight=None, normalized=True):
+    """Returns the global reaching centrality of a directed graph.
+
+    The *global reaching centrality* of a weighted directed graph is the
+    average over all nodes of the difference between the local reaching
+    centrality of the node and the greatest local reaching centrality of
+    any node in the graph [1]_. For more information on the local
+    reaching centrality, see :func:`local_reaching_centrality`.
+    Informally, the local reaching centrality is the proportion of the
+    graph that is reachable from the neighbors of the node.
+
+    Parameters
+    ----------
+    G : DiGraph
+        A networkx DiGraph.
+
+    weight : None or string, optional (default=None)
+        Attribute to use for edge weights. If ``None``, each edge weight
+        is assumed to be one. A higher weight implies a stronger
+        connection between nodes and a *shorter* path length.
+
+    normalized : bool, optional (default=True)
+        Whether to normalize the edge weights by the total sum of edge
+        weights.
+
+    Returns
+    -------
+    h : float
+        The global reaching centrality of the graph.
+
+    Examples
+    --------
+    >>> G = nx.DiGraph()
+    >>> G.add_edge(1, 2)
+    >>> G.add_edge(1, 3)
+    >>> nx.global_reaching_centrality(G)
+    1.0
+    >>> G.add_edge(3, 2)
+    >>> nx.global_reaching_centrality(G)
+    0.75
+
+    See also
+    --------
+    local_reaching_centrality
+
+    References
+    ----------
+    .. [1] Mones, Enys, Lilla Vicsek, and Tamás Vicsek.
+           "Hierarchy Measure for Complex Networks."
+           *PLoS ONE* 7.3 (2012): e33799.
+           https://doi.org/10.1371/journal.pone.0033799
+    """
+    if nx.is_negatively_weighted(G, weight=weight):
+        raise nx.NetworkXError("edge weights must be positive")
+    total_weight = G.size(weight=weight)
+    if total_weight <= 0:
+        raise nx.NetworkXError("Size of G must be positive")
+    # If provided, weights must be interpreted as connection strength
+    # (so higher weights are more likely to be chosen). However, the
+    # shortest path algorithms in NetworkX assume the provided "weight"
+    # is actually a distance (so edges with higher weight are less
+    # likely to be chosen). Therefore we need to invert the weights when
+    # computing shortest paths.
+    #
+    # If weight is None, we leave it as-is so that the shortest path
+    # algorithm can use a faster, unweighted algorithm.
+    if weight is not None:
+
+        def as_distance(u, v, d):
+            return total_weight / d.get(weight, 1)
+
+        shortest_paths = nx.shortest_path(G, weight=as_distance)
+    else:
+        shortest_paths = nx.shortest_path(G)
+
+    centrality = local_reaching_centrality
+    # TODO This can be trivially parallelized.
+    lrc = [
+        centrality(G, node, paths=paths, weight=weight, normalized=normalized)
+        for node, paths in shortest_paths.items()
+    ]
+
+    max_lrc = max(lrc)
+    return sum(max_lrc - c for c in lrc) / (len(G) - 1)
+
+
+@nx._dispatchable(edge_attrs="weight")
+def local_reaching_centrality(G, v, paths=None, weight=None, normalized=True):
+    """Returns the local reaching centrality of a node in a directed
+    graph.
+
+    The *local reaching centrality* of a node in a directed graph is the
+    proportion of other nodes reachable from that node [1]_.
+
+    Parameters
+    ----------
+    G : DiGraph
+        A NetworkX DiGraph.
+
+    v : node
+        A node in the directed graph `G`.
+
+    paths : dictionary (default=None)
+        If this is not `None` it must be a dictionary representation
+        of single-source shortest paths, as computed by, for example,
+        :func:`networkx.shortest_path` with source node `v`. Use this
+        keyword argument if you intend to invoke this function many
+        times but don't want the paths to be recomputed each time.
+
+    weight : None or string, optional (default=None)
+        Attribute to use for edge weights.  If `None`, each edge weight
+        is assumed to be one. A higher weight implies a stronger
+        connection between nodes and a *shorter* path length.
+
+    normalized : bool, optional (default=True)
+        Whether to normalize the edge weights by the total sum of edge
+        weights.
+
+    Returns
+    -------
+    h : float
+        The local reaching centrality of the node ``v`` in the graph
+        ``G``.
+
+    Examples
+    --------
+    >>> G = nx.DiGraph()
+    >>> G.add_edges_from([(1, 2), (1, 3)])
+    >>> nx.local_reaching_centrality(G, 3)
+    0.0
+    >>> G.add_edge(3, 2)
+    >>> nx.local_reaching_centrality(G, 3)
+    0.5
+
+    See also
+    --------
+    global_reaching_centrality
+
+    References
+    ----------
+    .. [1] Mones, Enys, Lilla Vicsek, and Tamás Vicsek.
+           "Hierarchy Measure for Complex Networks."
+           *PLoS ONE* 7.3 (2012): e33799.
+           https://doi.org/10.1371/journal.pone.0033799
+    """
+    # Corner case: graph with single node containing a self-loop
+    if (total_weight := G.size(weight=weight)) > 0 and len(G) == 1:
+        raise nx.NetworkXError(
+            "local_reaching_centrality of a single node with self-loop not well-defined"
+        )
+    if paths is None:
+        if nx.is_negatively_weighted(G, weight=weight):
+            raise nx.NetworkXError("edge weights must be positive")
+        if total_weight <= 0:
+            raise nx.NetworkXError("Size of G must be positive")
+        if weight is not None:
+            # Interpret weights as lengths.
+            def as_distance(u, v, d):
+                return total_weight / d.get(weight, 1)
+
+            paths = nx.shortest_path(G, source=v, weight=as_distance)
+        else:
+            paths = nx.shortest_path(G, source=v)
+    # If the graph is unweighted, simply return the proportion of nodes
+    # reachable from the source node ``v``.
+    if weight is None and G.is_directed():
+        return (len(paths) - 1) / (len(G) - 1)
+    if normalized and weight is not None:
+        norm = G.size(weight=weight) / G.size()
+    else:
+        norm = 1
+    # TODO This can be trivially parallelized.
+    avgw = (_average_weight(G, path, weight=weight) for path in paths.values())
+    sum_avg_weight = sum(avgw) / norm
+    return sum_avg_weight / (len(G) - 1)