python/graphscope/nx/algorithms/builtin.py (415 lines of code) (raw):
# -*- coding: utf-8 -*-
#
# This file is referred and derived from project NetworkX
#
# which has the following license:
#
# Copyright (C) 2004-2020, NetworkX Developers
# Aric Hagberg <hagberg@lanl.gov>
# Dan Schult <dschult@colgate.edu>
# Pieter Swart <swart@lanl.gov>
# All rights reserved.
#
# This file is part of NetworkX.
#
# NetworkX is distributed under a BSD license; see LICENSE.txt for more
# information.
#
import functools
import inspect
import json
import networkx.algorithms as nxa
from networkx.utils.decorators import not_implemented_for
import graphscope
from graphscope import nx
from graphscope.framework.app import AppAssets
from graphscope.framework.errors import InvalidArgumentError
from graphscope.nx.utils.compat import patch_docstring
from graphscope.proto import graph_def_pb2
# decorator function
def project_to_simple(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
graph = args[0]
if not hasattr(graph, "graph_type"):
raise InvalidArgumentError("Unsupported graph to project to simple.")
elif graph.graph_type in (
graph_def_pb2.DYNAMIC_PROPERTY,
graph_def_pb2.ARROW_PROPERTY,
):
weight = None
attribute = None
if "attribute" in inspect.getfullargspec(func)[0]:
attribute = kwargs.get("attribute", None)
if "weight" in inspect.getfullargspec(func)[0]:
# func has 'weight' argument
weight = kwargs.get("weight", None)
graph = graph._project_to_simple(v_prop=attribute, e_prop=weight)
return func(graph, *args[1:], **kwargs)
return wrapper
def context_to_dict(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
ctx = func(*args, **kwargs)
graph = args[0]
if graph.graph_type == graph_def_pb2.ARROW_FLATTENED:
d = dict()
df = ctx.to_dataframe(
{"label_id": "v.label_id", "id": "v.id", "value": "r"}
)
vertex_labels = graph.schema.vertex_labels
for row in df.itertuples():
if row.label_id != graph._default_label_id:
d[(vertex_labels[row.label_id], row.id)] = row.value
else:
d[row.id] = row.value
return d
return (
ctx.to_dataframe({"id": "v.id", "value": "r"})
.set_index("id")["value"]
.to_dict()
)
return wrapper
@context_to_dict
@project_to_simple
@not_implemented_for("multigraph")
def pagerank(G, alpha=0.85, max_iter=100, tol=1.0e-6, weight="weight"):
"""Returns the PageRank of the nodes in the graph.
PageRank computes a ranking of the nodes in the graph G based on
the structure of the incoming links. It was originally designed as
an algorithm to rank web pages.
Parameters
----------
G : graph
A networkx directed graph.
alpha : float, optional
Damping parameter for PageRank, default=0.85.
max_iter : integer, optional
Maximum number of iterations in power method eigenvalue solver.
tol : float, optional
Error tolerance used to check convergence in power method solver.
Returns
-------
pagerank : dataframe
Dataframe of nodes with PageRank as the value.
Examples
--------
>>> G = nx.DiGraph(nx.path_graph(4))
>>> pr = nx.pagerank(G, alpha=0.9)
Notes
-----
The eigenvector calculation is done by the power iteration method
and has no guarantee of convergence. The iteration will stop after
an error tolerance of ``len(G) * tol`` has been reached. If the
number of iterations exceed `max_iter`, computation just complete and
return the current result.
The PageRank algorithm was designed for directed graphs but this
algorithm does not check if the input graph is directed.
References
----------
.. [1] A. Langville and C. Meyer,
"A survey of eigenvector methods of web information retrieval."
http://citeseer.ist.psu.edu/713792.html
.. [2] Page, Lawrence; Brin, Sergey; Motwani, Rajeev and Winograd, Terry,
The PageRank citation ranking: Bringing order to the Web. 1999
http://dbpubs.stanford.edu:8090/pub/showDoc.Fulltext?lang=en&doc=1999-66&format=pdf
"""
return graphscope.pagerank_nx(G, alpha, max_iter, tol)
@not_implemented_for("multigraph")
@patch_docstring(nxa.hits)
def hits(G, max_iter=100, tol=1.0e-8, nstart=None, normalized=True):
# TODO(@weibin): raise PowerIterationFailedConvergence if hits fails to converge
# within the specified number of iterations.
@project_to_simple
def _hits(G, max_iter=100, tol=1.0e-8, normalized=True):
ctx = graphscope.hits(
G, tolerance=tol, max_round=max_iter, normalized=normalized
)
df = ctx.to_dataframe({"id": "v.id", "auth": "r.auth", "hub": "r.hub"})
return (
df.set_index("id")["hub"].to_dict(),
df.set_index("id")["auth"].to_dict(),
)
if nstart is not None:
# forward
return nxa.hits(G, max_iter, tol, nstart, normalized)
if max_iter == 0:
raise nx.PowerIterationFailedConvergence(max_iter)
if len(G) == 0:
return {}, {}
return _hits(G, max_iter, tol, normalized)
hits_scipy = hits
@context_to_dict
@project_to_simple
@patch_docstring(nxa.degree_centrality)
def degree_centrality(G):
return graphscope.degree_centrality(G, centrality_type="both")
@context_to_dict
@project_to_simple
@not_implemented_for("undirected")
@patch_docstring(nxa.in_degree_centrality)
def in_degree_centrality(G):
return graphscope.degree_centrality(G, centrality_type="in")
@context_to_dict
@project_to_simple
@not_implemented_for("undirected")
@patch_docstring(nxa.out_degree_centrality)
def out_degree_centrality(G):
return graphscope.degree_centrality(G, centrality_type="out")
@not_implemented_for("multigraph")
@patch_docstring(nxa.eigenvector_centrality)
def eigenvector_centrality(G, max_iter=100, tol=1e-06, nstart=None, weight=None):
# TODO(@weibin): raise PowerIterationFailedConvergence if eigenvector fails to converge
# within the specified number of iterations.
@context_to_dict
@project_to_simple
def _eigenvector_centrality(G, max_iter=100, tol=1e-06, weight=None):
return graphscope.eigenvector_centrality(
G, tolerance=tol, max_round=max_iter, weight=weight
)
if nstart is not None:
# forward the nxa.eigenvector_centrality
return nxa.eigenvector_centrality(G, max_iter, tol, nstart, weight)
if len(G) == 0:
raise nx.NetworkXPointlessConcept(
"cannot compute centrality for the null graph"
)
if max_iter == 0:
raise nx.PowerIterationFailedConvergence(max_iter)
return _eigenvector_centrality(G, max_iter=max_iter, tol=tol, weight=weight)
eigenvector_centrality_numpy = eigenvector_centrality
@not_implemented_for("multigraph")
@patch_docstring(nxa.katz_centrality)
def katz_centrality(
G,
alpha=0.1,
beta=1.0,
max_iter=100,
tol=1e-06,
nstart=None,
normalized=True,
weight=None,
):
# TODO(@weibin): raise PowerIterationFailedConvergence if katz fails to converge
# within the specified number of iterations.
@context_to_dict
@project_to_simple
def _katz_centrality(
G,
alpha=0.1,
beta=1.0,
max_iter=100,
tol=1e-06,
normalized=True,
weight=None,
):
return graphscope.katz_centrality(
G,
alpha=alpha,
beta=beta,
tolerance=tol,
max_round=max_iter,
normalized=normalized,
)
if nstart is not None or isinstance(beta, dict):
# forward the nxa.katz_centrality
return nxa.katz_centrality(
G, alpha, beta, max_iter, tol, nstart, normalized, weight
)
if len(G) == 0:
return {}
if not isinstance(beta, (int, float)):
raise nx.NetworkXError("beta should be number, not {}".format(type(beta)))
if max_iter == 0:
raise nx.PowerIterationFailedConvergence(max_iter)
return _katz_centrality(
G,
alpha=alpha,
beta=beta,
tol=tol,
max_iter=max_iter,
normalized=normalized,
weight=weight,
)
@project_to_simple
@patch_docstring(nxa.has_path)
def has_path(G, source, target):
ctx = AppAssets(algo="sssp_has_path", context="tensor")(G, source, target)
return ctx.to_numpy("r", axis=0)[0]
@project_to_simple
@patch_docstring(nxa.shortest_path)
def shortest_path(G, source=None, target=None, weight=None):
return AppAssets(algo="sssp_path", context="tensor")(G, source)
@context_to_dict
@project_to_simple
def single_source_dijkstra_path_length(G, source, weight=None):
"""Find shortest weighted path lengths in G from a source node.
Compute the shortest path length between source and all other
reachable nodes for a weighted graph.
Parameters
----------
G : networkx graph
source : node label
Starting node for path
weight : string
the 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]``).
Returns
-------
length : dataframe
Dataframe by node to shortest path length from source.
Examples
--------
>>> G = nx.path_graph(5)
>>> length = nx.single_source_dijkstra_path_length(G, 0)
Notes
-----
Edge weight attributes must be numerical.
Distances are calculated as sums of weighted edges traversed.
"""
return AppAssets(algo="sssp_projected", context="vertex_data")(G, source)
@patch_docstring(nxa.average_shortest_path_length)
def average_shortest_path_length(G, weight=None, method=None):
@project_to_simple
def _average_shortest_path_length(G, weight=None):
return graphscope.average_shortest_path_length(G, weight)
if method is not None:
return nxa.average_shortest_path_length(G, weight, method)
n = len(G)
# For the special case of the null graph. raise an exception, since
# there are no paths in the null graph.
if n == 0:
msg = (
"the null graph has no paths, thus there is no average"
"shortest path length"
)
raise nx.NetworkXPointlessConcept(msg)
# For the special case of the trivial graph, return zero immediately.
if n == 1:
return 0
return _average_shortest_path_length(G, weight=weight)
@project_to_simple
def bfs_edges(G, source, depth_limit=None):
"""edges in a breadth-first-search starting at source.
Parameters
----------
G : networkx graph
source : node
Specify starting node for breadth-first search; this function
iterates over only those edges in the component reachable from
this node.
depth_limit : int, optional(default=len(G))
Specify the maximum search depth
Returns
-------
edges: list
A list of edges in the breadth-first-search.
Examples
--------
To get the edges in a breadth-first search::
>>> G = nx.path_graph(3)
>>> list(nx.bfs_edges(G, 0))
[(0, 1), (1, 2)]
>>> list(nx.bfs_edges(G, source=0, depth_limit=1))
[(0, 1)]
"""
# FIXME: reverse not support.
depth_limit = -1 if depth_limit is None else depth_limit
ctx = AppAssets(algo="bfs_generic", context="tensor")(
G, source, depth_limit, format="edges"
)
return ctx.to_numpy("r", axis=0).tolist()
@project_to_simple
@patch_docstring(nxa.bfs_predecessors)
def bfs_predecessors(G, source, depth_limit=None):
return AppAssets(algo="bfs_generic", context="tensor")(
G, source, depth_limit, format="predecessors"
)
@project_to_simple
@patch_docstring(nxa.bfs_successors)
def bfs_successors(G, source, depth_limit=None):
return AppAssets(algo="bfs_generic", context="tensor")(
G, source, depth_limit, format="successors"
)
@project_to_simple
def all_pairs_shortest_path_length(G, weight=None):
"""Compute shortest path lengths between all nodes in a graph.
Parameters
----------
G : networkx graph
weight : string (default=None)
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 is None, every edge is assume to be one.
Returns
-------
:class:`DynamicVertexDataContext`: A context with each vertex assigned with the shortest distance.
One can use the context to access node's distance result or iterate by nodes.
Examples
--------
>>> G = nx.path_graph(5)
>>> length = dict(nx.all_pairs_dijkstra_path_length(G))
>>> for node in [0, 1, 2, 3, 4]:
... print(f"1 - {node}: {length[1][node]}")
1 - 0: 1
1 - 1: 0
1 - 2: 1
1 - 3: 2
1 - 4: 3
>>> length[3][2]
1
>>> length[2][2]
0
Notes
-----
Edge weight attributes must be numerical.
Distances are calculated as sums of weighted edges traversed.
"""
return AppAssets(algo="all_pairs_shortest_path_length", context="vertex_data")(G)
@patch_docstring(nxa.closeness_centrality)
def closeness_centrality(G, u=None, distance=None, wf_improved=True):
@context_to_dict
@project_to_simple
def _closeness_centrality(G, weight=None, wf_improved=True):
return AppAssets(algo="closeness_centrality", context="vertex_data")(
G, wf_improved
)
if u is not None:
# forward
return nxa.closeness_centrality(G, u, distance, wf_improved)
return _closeness_centrality(G, weight=distance, wf_improved=wf_improved)
@patch_docstring(nxa.bfs_tree)
def bfs_tree(G, source, reverse=False, depth_limit=None):
"""Returns an oriented tree constructed from of a breadth-first-search
starting at source.
Parameters
----------
G : networkx graph
source : node
Specify starting node for breadth-first search
depth_limit : int, optional(default=len(G))
Specify the maximum search depth
Returns
-------
T: networkx DiGraph
An oriented tree
Notes
-----
Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
by D. Eppstein, July 2004. The modifications
to allow depth limits based on the Wikipedia article
"`Depth-limited-search`_".
.. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
"""
T = nx.DiGraph()
T.add_node(source)
edges_gen = bfs_edges(G, source, depth_limit=depth_limit)
T.add_edges_from(edges_gen)
return T
@project_to_simple
def k_core(G, k=None, core_number=None):
"""Returns the k-core of G.
A k-core is a maximal subgraph that contains nodes of degree k or more.
Parameters
----------
G : networkx graph
A graph or directed graph
k : int, optional
The order of the core. If not specified return the main core.
Returns
-------
:class:`VertexDataContext`: A context with each vertex assigned with a boolean:
1 if the vertex satisfies k-core, otherwise 0.
References
----------
.. [1] An O(m) Algorithm for Cores Decomposition of Networks
Vladimir Batagelj and Matjaz Zaversnik, 2003.
https://arxiv.org/abs/cs.DS/0310049
"""
# FIXME: core number not support.
return graphscope.k_core(G, k)
@patch_docstring(nxa.clustering)
def clustering(G, nodes=None, weight=None):
@context_to_dict
@project_to_simple
def _clustering(G):
return graphscope.clustering(G)
if weight or not isinstance(G, (nx.Graph, nx.DiGraph, graphscope.Graph)):
# forward networkx.clustering
return nxa.clustering(G, nodes, weight)
clusterc = _clustering(G)
if nodes is not None:
if not isinstance(nodes, list) and nodes in clusterc:
return clusterc[nodes]
else:
return {n: clusterc[n] for n in nodes}
return clusterc
@not_implemented_for("directed")
@patch_docstring(nxa.triangles)
def triangles(G, nodes=None):
@context_to_dict
@project_to_simple
def _triangles(G):
return graphscope.triangles(G)
if not isinstance(G, (nx.Graph, nx.DiGraph, graphscope.Graph)):
# forward networkx.triangles
return nxa.triangles(G, nodes)
tricnt = _triangles(G)
if nodes is not None:
if not isinstance(nodes, list) and nodes in tricnt:
return tricnt[nodes]
else:
return {n: tricnt[n] for n in nodes}
return tricnt
@project_to_simple
@patch_docstring(nxa.transitivity)
def transitivity(G):
ctx = AppAssets(algo="transitivity", context="tensor")(G)
return ctx.to_numpy("r")[0]
@patch_docstring(nxa.average_clustering)
def average_clustering(G, nodes=None, weight=None, count_zeros=True):
@project_to_simple
def _average_clustering(G):
ctx = AppAssets(algo="avg_clustering", context="tensor")(
G, degree_threshold=1000000000
)
return ctx.to_numpy("r")[0]
if weight is not None:
# forward to networkx.average_clustering
return nxa.average_clustering(G, nodes, weight, count_zeros)
if nodes or not count_zeros or not G.is_directed():
c = clustering(G, nodes=nodes).values()
if not count_zeros:
c = [v for v in c if abs(v) > 0]
return sum(c) / len(c)
return _average_clustering(G)
@context_to_dict
@project_to_simple
def weakly_connected_components(G):
"""Generate weakly connected components of G.
Parameters
----------
G : networkx graph
A directed graph
Returns
-------
comp :class:`VertexDataContext`: A context with each vertex assigned with a boolean:
1 if the vertex satisfies k-core, otherwise 0.
"""
return AppAssets(algo="wcc_projected", context="vertex_data")(G)
@project_to_simple
def degree_assortativity_coefficient(G, x="out", y="in", weight=None):
"""Compute degree assortativity of graph.
Assortativity measures the similarity of connections
in the graph with respect to the node degree.
Parameters
----------
G : NetworkX graph
x: string ('in','out')
The degree type for source node (directed graphs only).
y: string ('in','out')
The degree type for target node (directed graphs only).
weighted: bool (True, False)
weighted graph or unweighted graph
Returns
-------
r : float
Assortativity of graph by degree.
Examples
--------
>>> G = nx.path_graph(4)
>>> r = nx.builtin.degree_assortativity_coefficient(G)
>>> print(f"{r:3.1f}")
-0.5
See Also
--------
attribute_assortativity_coefficient
Notes
-----
This computes Eq. (21) in Ref. [1]_ , where e is the joint
probability distribution (mixing matrix) of the degrees. If G is
directed than the matrix e is the joint probability of the
user-specified degree type for the source and target.
References
----------
.. [1] M. E. J. Newman, Mixing patterns in networks,
Physical Review E, 67 026126, 2003
.. [2] Foster, J.G., Foster, D.V., Grassberger, P. & Paczuski, M.
Edge direction and the structure of networks, PNAS 107, 10815-20 (2010).
"""
return graphscope.degree_assortativity_coefficient(G, x, y, weight)
@patch_docstring(nxa.node_boundary)
def node_boundary(G, nbunch1, nbunch2=None):
@project_to_simple
def _node_boundary(G, nbunch1, nbunch2=None):
n1json = json.dumps(list(nbunch1))
if nbunch2 is not None:
n2json = json.dumps(list(nbunch2))
else:
n2json = ""
ctx = AppAssets(algo="node_boundary", context="tensor")(G, n1json, n2json)
return set(ctx.to_numpy("r", axis=0).tolist())
if G.is_multigraph():
# forward to the NetworkX node_boundary
return nxa.node_boundary(G, nbunch1, nbunch2)
return _node_boundary(G, nbunch1, nbunch2)
@patch_docstring(nxa.edge_boundary)
def edge_boundary(G, nbunch1, nbunch2=None, data=False, keys=False, default=None):
@project_to_simple
def _boundary(G, nbunch1, nbunch2=None):
n1json = json.dumps(list(nbunch1))
if nbunch2:
n2json = json.dumps(list(nbunch2))
else:
n2json = ""
ctx = AppAssets(algo="edge_boundary", context="tensor")(G, n1json, n2json)
ret = ctx.to_numpy("r", axis=0).tolist()
for e in ret:
yield (e[0], e[1])
if G.is_multigraph():
# forward the NetworkX edge boundary
return nxa.edge_boundary(G, nbunch1, nbunch2, data, keys, default)
return _boundary(G, nbunch1, nbunch2)
@project_to_simple
def average_degree_connectivity(G, source="in+out", target="in+out", weight=None):
"""Compute the average degree connectivity of graph.
The average degree connectivity is the average nearest neighbor degree of
nodes with degree k. For weighted graphs, an analogous measure can
be computed using the weighted average neighbors degree defined in
[1]_, for a node `i`, as
.. math::
k_{nn,i}^{w} = \frac{1}{s_i} \sum_{j \in N(i)} w_{ij} k_j
where `s_i` is the weighted degree of node `i`,
`w_{ij}` is the weight of the edge that links `i` and `j`,
and `N(i)` are the neighbors of node `i`.
Parameters
----------
G : NetworkX graph
source : "in"|"out"|"in+out" (default:"in+out")
Directed graphs only. Use "in"- or "out"-degree for source node.
target : "in"|"out"|"in+out" (default:"in+out"
Directed graphs only. Use "in"- or "out"-degree for target node.
weight : string or None, optional (default=None)
The edge attribute that holds the numerical value used as a weight.
If None, then each edge has weight 1.
Returns
-------
d : dict
A dictionary keyed by degree k with the value of average connectivity.
Raises
------
ValueError
If either `source` or `target` are not one of 'in',
'out', or 'in+out'.
Examples
--------
>>> G = nx.Graph()
>>> G.add_edge(1, 2, weight=3)
>>> G.add_edges_from([(0, 1), (2, 3)], weight=1)
>>> nx.builtin.average_degree_connectivity(G)
{1: 2.0, 2: 1.5}
>>> nx.builtin.average_degree_connectivity(G, weight="weight")
{1: 2.0, 2: 1.75}
References
----------
.. [1] A. Barrat, M. Barthélemy, R. Pastor-Satorras, and A. Vespignani,
"The architecture of complex weighted networks".
PNAS 101 (11): 3747–3752 (2004).
"""
return graphscope.average_degree_connectivity(G, source, target, weight)
@project_to_simple
def attribute_assortativity_coefficient(G, attribute):
"""Compute assortativity for node attributes.
Assortativity measures the similarity of connections
in the graph with respect to the given attribute.
Parameters
----------
G : NetworkX graph
attribute : string
Node attribute key
Returns
-------
r: float
Assortativity of graph for given attribute
Examples
--------
>>> G = nx.Graph()
>>> G.add_nodes_from([0, 1], color="red")
>>> G.add_nodes_from([2, 3], color="blue")
>>> G.add_edges_from([(0, 1), (2, 3)])
>>> print(nx.builtin.attribute_assortativity_coefficient(G, "color"))
1.0
Notes
-----
This computes Eq. (2) in Ref. [1]_ , (trace(M)-sum(M^2))/(1-sum(M^2)),
where M is the joint probability distribution (mixing matrix)
of the specified attribute.
References
----------
.. [1] M. E. J. Newman, Mixing patterns in networks,
Physical Review E, 67 026126, 2003
"""
return graphscope.attribute_assortativity_coefficient(G, attribute)
@project_to_simple
def numeric_assortativity_coefficient(G, attribute):
"""Compute assortativity for numerical node attributes.
Assortativity measures the similarity of connections
in the graph with respect to the given numeric attribute.
Parameters
----------
G : NetworkX graph
attribute : string
Node attribute key.
Returns
-------
r: float
Assortativity of graph for given attribute
Examples
--------
>>> G = nx.Graph()
>>> G.add_nodes_from([0, 1], size=2)
>>> G.add_nodes_from([2, 3], size=3)
>>> G.add_edges_from([(0, 1), (2, 3)])
>>> print(nx.builtin.numeric_assortativity_coefficient(G, "size"))
1.0
Notes
-----
This computes Eq. (21) in Ref. [1]_ , for the mixing matrix
of the specified attribute.
References
----------
.. [1] M. E. J. Newman, Mixing patterns in networks
Physical Review E, 67 026126, 2003
"""
return graphscope.numeric_assortativity_coefficient(G, attribute)
@patch_docstring(nxa.is_simple_path)
def is_simple_path(G, nodes):
@project_to_simple
def _is_simple_path(G, nodes):
return graphscope.is_simple_path(G, nodes)
if G.is_multigraph():
# forward the networkx.is_simple_graph
return nxa.is_simple_path(G, nodes)
return _is_simple_path(G, nodes)
def get_all_simple_paths(G, source, target_nodes, cutoff):
@project_to_simple
def _all_simple_paths(G, source, target_nodes, cutoff):
targets_json = json.dumps(target_nodes)
return AppAssets(algo="all_simple_paths", context="tensor")(
G, source, targets_json, cutoff
)
if not isinstance(target_nodes, list):
target_nodes = [target_nodes]
if source not in G or len(target_nodes) != len(list(G.nbunch_iter(target_nodes))):
raise ValueError("nx.NodeNotFound")
if cutoff is None:
cutoff = len(G) - 1
if cutoff < 1 or source in target_nodes:
return []
ctx = _all_simple_paths(G, source, list(set(target_nodes)), cutoff)
paths = ctx.to_numpy("r", axis=0).tolist()
if len(paths) == 1:
if not isinstance(paths[0], list):
return []
return paths
def all_simple_paths(G, source, target_nodes, cutoff=None):
"""Generate all simple paths in the graph G from source to target.
A simple path is a path with no repeated nodes.
Parameters
----------
G : NetworkX graph
source : node
Starting node for path
target : nodes
Single node or iterable of nodes at which to end path
cutoff : integer, optional
Depth to stop the search. Only paths of length <= cutoff are returned.
Returns
-------
paths: list
A list that produces lists of simple paths. If there are no paths
between the source and target within the given cutoff the list
is empty.
Examples
--------
>>> G = nx.complete_graph(4)
>>> print(nx.builtin.all_simple_paths(G, 0, 3))
...
[0, 1, 2, 3]
[0, 1, 3]
[0, 2, 1, 3]
[0, 2, 3]
[0, 3]
"""
paths = get_all_simple_paths(G, source, target_nodes, cutoff)
# delete path tail padding
for path in paths:
for i in range(len(path) - 1, -1, -1):
if path[i] == -1:
path.pop(i)
else:
break
return paths
def all_simple_edge_paths(G, source, target_nodes, cutoff=None):
"""Generate lists of edges for all simple paths in G from source to target.
A simple path is a path with no repeated nodes.
Parameters
----------
G : NetworkX graph
source : node
Starting node for path
target : nodes
Single node or iterable of nodes at which to end path
cutoff : integer, optional
Depth to stop the search. Only paths of length <= cutoff are returned.
Returns
-------
paths: list
A list that produces lists of simple edge paths. If there are no paths
between the source and target within the given cutoff the list
is empty.
Examples
--------
Print the simple path edges of a Graph::
>>> g = nx.Graph([(1, 2), (2, 4), (1, 3), (3, 4)])
>>> print(nx.builtin.all_simple_paths(G, 1, 4))
[(1, 2), (2, 4)]
[(1, 3), (3, 4)]
"""
paths = get_all_simple_paths(G, source, target_nodes, cutoff)
for path in paths:
a = ""
b = ""
for i in range(len(path) - 1, -1, -1):
if path[i] == -1:
a = path.pop(i)
else:
b = path.pop(i)
if a != -1 and a != "":
path.insert(i, (b, a))
a = b
return paths
def betweenness_centrality(
G, k=None, normalized=True, weight=None, endpoints=False, seed=None
):
r"""Compute the shortest-path betweenness centrality for nodes.
Betweenness centrality of a node $v$ is the sum of the
fraction of all-pairs shortest paths that pass through $v$
.. math::
c_B(v) =\sum_{s,t \in V} \frac{\sigma(s, t|v)}{\sigma(s, t)}
where $V$ is the set of nodes, $\sigma(s, t)$ is the number of
shortest $(s, t)$-paths, and $\sigma(s, t|v)$ is the number of
those paths passing through some node $v$ other than $s, t$.
If $s = t$, $\sigma(s, t) = 1$, and if $v \in {s, t}$,
$\sigma(s, t|v) = 0$ [2]_.
Parameters
----------
G : graph
A NetworkX graph.
normalized : bool, optional
If True the betweenness values are normalized by `2/((n-1)(n-2))`
for graphs, and `1/((n-1)(n-2))` for directed graphs where `n`
is the number of nodes in G.
weight : None or string, optional (default=None)
If None, all edge weights are considered equal.
Otherwise holds the name of the edge attribute used as weight.
Weights are used to calculate weighted shortest paths, so they are
interpreted as distances.
endpoints : bool, optional
If True include the endpoints in the shortest path counts.
Returns
-------
nodes : dictionary
Dictionary of nodes with betweenness centrality as the value.
See Also
--------
edge_betweenness_centrality
load_centrality
Notes
-----
The algorithm is from Ulrik Brandes [1]_.
See [4]_ for the original first published version and [2]_ for details on
algorithms for variations and related metrics.
For approximate betweenness calculations set k=#samples to use
k nodes ("pivots") to estimate the betweenness values. For an estimate
of the number of pivots needed see [3]_.
For weighted graphs the edge weights must be greater than zero.
Zero edge weights can produce an infinite number of equal length
paths between pairs of nodes.
The total number of paths between source and target is counted
differently for directed and undirected graphs. Directed paths
are easy to count. Undirected paths are tricky: should a path
from "u" to "v" count as 1 undirected path or as 2 directed paths?
For betweenness_centrality we report the number of undirected
paths when G is undirected.
For betweenness_centrality_subset the reporting is different.
If the source and target subsets are the same, then we want
to count undirected paths. But if the source and target subsets
differ -- for example, if sources is {0} and targets is {1},
then we are only counting the paths in one direction. They are
undirected paths but we are counting them in a directed way.
To count them as undirected paths, each should count as half a path.
References
----------
.. [1] Ulrik Brandes:
A Faster Algorithm for Betweenness Centrality.
Journal of Mathematical Sociology 25(2):163-177, 2001.
https://doi.org/10.1080/0022250X.2001.9990249
.. [2] Ulrik Brandes:
On Variants of Shortest-Path Betweenness
Centrality and their Generic Computation.
Social Networks 30(2):136-145, 2008.
https://doi.org/10.1016/j.socnet.2007.11.001
.. [3] Ulrik Brandes and Christian Pich:
Centrality Estimation in Large Networks.
International Journal of Bifurcation and Chaos 17(7):2303-2318, 2007.
https://dx.doi.org/10.1142/S0218127407018403
.. [4] Linton C. Freeman:
A set of measures of centrality based on betweenness.
Sociometry 40: 35–41, 1977
https://doi.org/10.2307/3033543
"""
@context_to_dict
@project_to_simple
def _betweenness_centrality(
G, k=None, normalized=True, weight=None, endpoints=False, seed=None
):
algorithm = "betweenness_centrality"
if weight is not None:
algorithm = "betweenness_centrality_generic"
return AppAssets(algo=algorithm, context="vertex_data")(
G, normalized=normalized, endpoints=endpoints
)
if not isinstance(G, nx.Graph) or seed is not None:
return nxa.betweenness_centrality(G, k, normalized, weight, endpoints, seed)
return _betweenness_centrality(
G, k=k, normalized=normalized, weight=weight, endpoints=endpoints, seed=seed
)
@project_to_simple
@not_implemented_for("multigraph")
def voterank(G, num_of_nodes=0):
"""Select a list of influential nodes in a graph using VoteRank algorithm
VoteRank [1]_ computes a ranking of the nodes in a graph G based on a
voting scheme. With VoteRank, all nodes vote for each of its in-neighbours
and the node with the highest votes is elected iteratively. The voting
ability of out-neighbors of elected nodes is decreased in subsequent turns.
Note: We treat each edge independently in case of multigraphs.
Parameters
----------
G : graph
A networkx directed graph.
number_of_nodes : integer, optional
Number of ranked nodes to extract (default all nodes).
Returns
-------
voterank : list
Ordered list of computed seeds.
Only nodes with positive number of votes are returned.
Examples
--------
>>> G = nx.DiGraph(nx.path_graph(4))
>>> pr = nx.voterank(G, num_of_nodes=2)
References
----------
.. [1] Zhang, J.-X. et al. (2016).
Identifying a set of influential spreaders in complex networks.
Sci. Rep. 6, 27823; doi: 10.1038/srep27823.
"""
ctx = graphscope.voterank(G, num_of_nodes)
r = ctx.to_dataframe({"id": "v.id", "result": "r"})
r = r[r["result"] != 0].sort_values(by=["result"])
return r["id"].tolist()