ZhengyangZhang commited on
Commit
085d76f
·
verified ·
1 Parent(s): abc9335

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. lib/python3.12/site-packages/networkx/algorithms/__init__.py +134 -0
  2. lib/python3.12/site-packages/networkx/algorithms/assortativity/__init__.py +5 -0
  3. lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/__init__.cpython-312.pyc +0 -0
  4. lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/connectivity.cpython-312.pyc +0 -0
  5. lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/correlation.cpython-312.pyc +0 -0
  6. lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/mixing.cpython-312.pyc +0 -0
  7. lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/neighbor_degree.cpython-312.pyc +0 -0
  8. lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/pairs.cpython-312.pyc +0 -0
  9. lib/python3.12/site-packages/networkx/algorithms/assortativity/connectivity.py +122 -0
  10. lib/python3.12/site-packages/networkx/algorithms/assortativity/correlation.py +302 -0
  11. lib/python3.12/site-packages/networkx/algorithms/assortativity/mixing.py +255 -0
  12. lib/python3.12/site-packages/networkx/algorithms/assortativity/neighbor_degree.py +160 -0
  13. lib/python3.12/site-packages/networkx/algorithms/assortativity/pairs.py +127 -0
  14. lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__init__.py +0 -0
  15. lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/__init__.cpython-312.pyc +0 -0
  16. lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/base_test.cpython-312.pyc +0 -0
  17. lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_connectivity.cpython-312.pyc +0 -0
  18. lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_correlation.cpython-312.pyc +0 -0
  19. lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_mixing.cpython-312.pyc +0 -0
  20. lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_neighbor_degree.cpython-312.pyc +0 -0
  21. lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_pairs.cpython-312.pyc +0 -0
  22. lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/base_test.py +81 -0
  23. lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_connectivity.py +143 -0
  24. lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_correlation.py +122 -0
  25. lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_mixing.py +174 -0
  26. lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_neighbor_degree.py +107 -0
  27. lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_pairs.py +87 -0
  28. lib/python3.12/site-packages/networkx/algorithms/asteroidal.py +164 -0
  29. lib/python3.12/site-packages/networkx/algorithms/boundary.py +168 -0
  30. lib/python3.12/site-packages/networkx/algorithms/bridges.py +205 -0
  31. lib/python3.12/site-packages/networkx/algorithms/broadcasting.py +164 -0
  32. lib/python3.12/site-packages/networkx/algorithms/chains.py +172 -0
  33. lib/python3.12/site-packages/networkx/algorithms/chordal.py +443 -0
  34. lib/python3.12/site-packages/networkx/algorithms/clique.py +818 -0
  35. lib/python3.12/site-packages/networkx/algorithms/cluster.py +732 -0
  36. lib/python3.12/site-packages/networkx/algorithms/communicability_alg.py +163 -0
  37. lib/python3.12/site-packages/networkx/algorithms/connectivity/__init__.py +11 -0
  38. lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/__init__.cpython-312.pyc +0 -0
  39. lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/connectivity.cpython-312.pyc +0 -0
  40. lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/cuts.cpython-312.pyc +0 -0
  41. lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/disjoint_paths.cpython-312.pyc +0 -0
  42. lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/edge_augmentation.cpython-312.pyc +0 -0
  43. lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/edge_kcomponents.cpython-312.pyc +0 -0
  44. lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/kcomponents.cpython-312.pyc +0 -0
  45. lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/kcutsets.cpython-312.pyc +0 -0
  46. lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/stoerwagner.cpython-312.pyc +0 -0
  47. lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/utils.cpython-312.pyc +0 -0
  48. lib/python3.12/site-packages/networkx/algorithms/connectivity/connectivity.py +811 -0
  49. lib/python3.12/site-packages/networkx/algorithms/connectivity/cuts.py +616 -0
  50. lib/python3.12/site-packages/networkx/algorithms/connectivity/disjoint_paths.py +408 -0
lib/python3.12/site-packages/networkx/algorithms/__init__.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from networkx.algorithms.assortativity import *
2
+ from networkx.algorithms.asteroidal import *
3
+ from networkx.algorithms.boundary import *
4
+ from networkx.algorithms.broadcasting import *
5
+ from networkx.algorithms.bridges import *
6
+ from networkx.algorithms.chains import *
7
+ from networkx.algorithms.centrality import *
8
+ from networkx.algorithms.chordal import *
9
+ from networkx.algorithms.cluster import *
10
+ from networkx.algorithms.clique import *
11
+ from networkx.algorithms.communicability_alg import *
12
+ from networkx.algorithms.components import *
13
+ from networkx.algorithms.coloring import *
14
+ from networkx.algorithms.core import *
15
+ from networkx.algorithms.covering import *
16
+ from networkx.algorithms.cycles import *
17
+ from networkx.algorithms.cuts import *
18
+ from networkx.algorithms.d_separation import *
19
+ from networkx.algorithms.dag import *
20
+ from networkx.algorithms.distance_measures import *
21
+ from networkx.algorithms.distance_regular import *
22
+ from networkx.algorithms.dominance import *
23
+ from networkx.algorithms.dominating import *
24
+ from networkx.algorithms.efficiency_measures import *
25
+ from networkx.algorithms.euler import *
26
+ from networkx.algorithms.graphical import *
27
+ from networkx.algorithms.hierarchy import *
28
+ from networkx.algorithms.hybrid import *
29
+ from networkx.algorithms.link_analysis import *
30
+ from networkx.algorithms.link_prediction import *
31
+ from networkx.algorithms.lowest_common_ancestors import *
32
+ from networkx.algorithms.isolate import *
33
+ from networkx.algorithms.matching import *
34
+ from networkx.algorithms.minors import *
35
+ from networkx.algorithms.mis import *
36
+ from networkx.algorithms.moral import *
37
+ from networkx.algorithms.non_randomness import *
38
+ from networkx.algorithms.operators import *
39
+ from networkx.algorithms.planarity import *
40
+ from networkx.algorithms.planar_drawing import *
41
+ from networkx.algorithms.polynomials import *
42
+ from networkx.algorithms.perfect_graph import *
43
+ from networkx.algorithms.reciprocity import *
44
+ from networkx.algorithms.regular import *
45
+ from networkx.algorithms.richclub import *
46
+ from networkx.algorithms.shortest_paths import *
47
+ from networkx.algorithms.similarity import *
48
+ from networkx.algorithms.graph_hashing import *
49
+ from networkx.algorithms.simple_paths import *
50
+ from networkx.algorithms.smallworld import *
51
+ from networkx.algorithms.smetric import *
52
+ from networkx.algorithms.structuralholes import *
53
+ from networkx.algorithms.sparsifiers import *
54
+ from networkx.algorithms.summarization import *
55
+ from networkx.algorithms.swap import *
56
+ from networkx.algorithms.time_dependent import *
57
+ from networkx.algorithms.traversal import *
58
+ from networkx.algorithms.triads import *
59
+ from networkx.algorithms.vitality import *
60
+ from networkx.algorithms.voronoi import *
61
+ from networkx.algorithms.walks import *
62
+ from networkx.algorithms.wiener import *
63
+
64
+ # Make certain subpackages available to the user as direct imports from
65
+ # the `networkx` namespace.
66
+ from networkx.algorithms import approximation
67
+ from networkx.algorithms import assortativity
68
+ from networkx.algorithms import bipartite
69
+ from networkx.algorithms import node_classification
70
+ from networkx.algorithms import centrality
71
+ from networkx.algorithms import chordal
72
+ from networkx.algorithms import cluster
73
+ from networkx.algorithms import clique
74
+ from networkx.algorithms import components
75
+ from networkx.algorithms import connectivity
76
+ from networkx.algorithms import community
77
+ from networkx.algorithms import coloring
78
+ from networkx.algorithms import flow
79
+ from networkx.algorithms import isomorphism
80
+ from networkx.algorithms import link_analysis
81
+ from networkx.algorithms import lowest_common_ancestors
82
+ from networkx.algorithms import operators
83
+ from networkx.algorithms import shortest_paths
84
+ from networkx.algorithms import tournament
85
+ from networkx.algorithms import traversal
86
+ from networkx.algorithms import tree
87
+
88
+ # Make certain functions from some of the previous subpackages available
89
+ # to the user as direct imports from the `networkx` namespace.
90
+ from networkx.algorithms.bipartite import complete_bipartite_graph
91
+ from networkx.algorithms.bipartite import is_bipartite
92
+ from networkx.algorithms.bipartite import projected_graph
93
+ from networkx.algorithms.connectivity import all_pairs_node_connectivity
94
+ from networkx.algorithms.connectivity import all_node_cuts
95
+ from networkx.algorithms.connectivity import average_node_connectivity
96
+ from networkx.algorithms.connectivity import edge_connectivity
97
+ from networkx.algorithms.connectivity import edge_disjoint_paths
98
+ from networkx.algorithms.connectivity import k_components
99
+ from networkx.algorithms.connectivity import k_edge_components
100
+ from networkx.algorithms.connectivity import k_edge_subgraphs
101
+ from networkx.algorithms.connectivity import k_edge_augmentation
102
+ from networkx.algorithms.connectivity import is_k_edge_connected
103
+ from networkx.algorithms.connectivity import minimum_edge_cut
104
+ from networkx.algorithms.connectivity import minimum_node_cut
105
+ from networkx.algorithms.connectivity import node_connectivity
106
+ from networkx.algorithms.connectivity import node_disjoint_paths
107
+ from networkx.algorithms.connectivity import stoer_wagner
108
+ from networkx.algorithms.flow import capacity_scaling
109
+ from networkx.algorithms.flow import cost_of_flow
110
+ from networkx.algorithms.flow import gomory_hu_tree
111
+ from networkx.algorithms.flow import max_flow_min_cost
112
+ from networkx.algorithms.flow import maximum_flow
113
+ from networkx.algorithms.flow import maximum_flow_value
114
+ from networkx.algorithms.flow import min_cost_flow
115
+ from networkx.algorithms.flow import min_cost_flow_cost
116
+ from networkx.algorithms.flow import minimum_cut
117
+ from networkx.algorithms.flow import minimum_cut_value
118
+ from networkx.algorithms.flow import network_simplex
119
+ from networkx.algorithms.isomorphism import could_be_isomorphic
120
+ from networkx.algorithms.isomorphism import fast_could_be_isomorphic
121
+ from networkx.algorithms.isomorphism import faster_could_be_isomorphic
122
+ from networkx.algorithms.isomorphism import is_isomorphic
123
+ from networkx.algorithms.isomorphism.vf2pp import *
124
+ from networkx.algorithms.tree.branchings import maximum_branching
125
+ from networkx.algorithms.tree.branchings import maximum_spanning_arborescence
126
+ from networkx.algorithms.tree.branchings import minimum_branching
127
+ from networkx.algorithms.tree.branchings import minimum_spanning_arborescence
128
+ from networkx.algorithms.tree.branchings import ArborescenceIterator
129
+ from networkx.algorithms.tree.coding import *
130
+ from networkx.algorithms.tree.decomposition import *
131
+ from networkx.algorithms.tree.mst import *
132
+ from networkx.algorithms.tree.operations import *
133
+ from networkx.algorithms.tree.recognition import *
134
+ from networkx.algorithms.tournament import is_tournament
lib/python3.12/site-packages/networkx/algorithms/assortativity/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from networkx.algorithms.assortativity.connectivity import *
2
+ from networkx.algorithms.assortativity.correlation import *
3
+ from networkx.algorithms.assortativity.mixing import *
4
+ from networkx.algorithms.assortativity.neighbor_degree import *
5
+ from networkx.algorithms.assortativity.pairs import *
lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (513 Bytes). View file
 
lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/connectivity.cpython-312.pyc ADDED
Binary file (5.22 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/correlation.cpython-312.pyc ADDED
Binary file (10.9 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/mixing.cpython-312.pyc ADDED
Binary file (8.42 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/neighbor_degree.cpython-312.pyc ADDED
Binary file (6.16 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/pairs.cpython-312.pyc ADDED
Binary file (4.81 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/assortativity/connectivity.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections import defaultdict
2
+
3
+ import networkx as nx
4
+
5
+ __all__ = ["average_degree_connectivity"]
6
+
7
+
8
+ @nx._dispatchable(edge_attrs="weight")
9
+ def average_degree_connectivity(
10
+ G, source="in+out", target="in+out", nodes=None, weight=None
11
+ ):
12
+ r"""Compute the average degree connectivity of graph.
13
+
14
+ The average degree connectivity is the average nearest neighbor degree of
15
+ nodes with degree k. For weighted graphs, an analogous measure can
16
+ be computed using the weighted average neighbors degree defined in
17
+ [1]_, for a node `i`, as
18
+
19
+ .. math::
20
+
21
+ k_{nn,i}^{w} = \frac{1}{s_i} \sum_{j \in N(i)} w_{ij} k_j
22
+
23
+ where `s_i` is the weighted degree of node `i`,
24
+ `w_{ij}` is the weight of the edge that links `i` and `j`,
25
+ and `N(i)` are the neighbors of node `i`.
26
+
27
+ Parameters
28
+ ----------
29
+ G : NetworkX graph
30
+
31
+ source : "in"|"out"|"in+out" (default:"in+out")
32
+ Directed graphs only. Use "in"- or "out"-degree for source node.
33
+
34
+ target : "in"|"out"|"in+out" (default:"in+out"
35
+ Directed graphs only. Use "in"- or "out"-degree for target node.
36
+
37
+ nodes : list or iterable (optional)
38
+ Compute neighbor connectivity for these nodes. The default is all
39
+ nodes.
40
+
41
+ weight : string or None, optional (default=None)
42
+ The edge attribute that holds the numerical value used as a weight.
43
+ If None, then each edge has weight 1.
44
+
45
+ Returns
46
+ -------
47
+ d : dict
48
+ A dictionary keyed by degree k with the value of average connectivity.
49
+
50
+ Raises
51
+ ------
52
+ NetworkXError
53
+ If either `source` or `target` are not one of 'in',
54
+ 'out', or 'in+out'.
55
+ If either `source` or `target` is passed for an undirected graph.
56
+
57
+ Examples
58
+ --------
59
+ >>> G = nx.path_graph(4)
60
+ >>> G.edges[1, 2]["weight"] = 3
61
+ >>> nx.average_degree_connectivity(G)
62
+ {1: 2.0, 2: 1.5}
63
+ >>> nx.average_degree_connectivity(G, weight="weight")
64
+ {1: 2.0, 2: 1.75}
65
+
66
+ See Also
67
+ --------
68
+ average_neighbor_degree
69
+
70
+ References
71
+ ----------
72
+ .. [1] A. Barrat, M. Barthélemy, R. Pastor-Satorras, and A. Vespignani,
73
+ "The architecture of complex weighted networks".
74
+ PNAS 101 (11): 3747–3752 (2004).
75
+ """
76
+ # First, determine the type of neighbors and the type of degree to use.
77
+ if G.is_directed():
78
+ if source not in ("in", "out", "in+out"):
79
+ raise nx.NetworkXError('source must be one of "in", "out", or "in+out"')
80
+ if target not in ("in", "out", "in+out"):
81
+ raise nx.NetworkXError('target must be one of "in", "out", or "in+out"')
82
+ direction = {"out": G.out_degree, "in": G.in_degree, "in+out": G.degree}
83
+ neighbor_funcs = {
84
+ "out": G.successors,
85
+ "in": G.predecessors,
86
+ "in+out": G.neighbors,
87
+ }
88
+ source_degree = direction[source]
89
+ target_degree = direction[target]
90
+ neighbors = neighbor_funcs[source]
91
+ # `reverse` indicates whether to look at the in-edge when
92
+ # computing the weight of an edge.
93
+ reverse = source == "in"
94
+ else:
95
+ if source != "in+out" or target != "in+out":
96
+ raise nx.NetworkXError(
97
+ f"source and target arguments are only supported for directed graphs"
98
+ )
99
+ source_degree = G.degree
100
+ target_degree = G.degree
101
+ neighbors = G.neighbors
102
+ reverse = False
103
+ dsum = defaultdict(int)
104
+ dnorm = defaultdict(int)
105
+ # Check if `source_nodes` is actually a single node in the graph.
106
+ source_nodes = source_degree(nodes)
107
+ if nodes in G:
108
+ source_nodes = [(nodes, source_degree(nodes))]
109
+ for n, k in source_nodes:
110
+ nbrdeg = target_degree(neighbors(n))
111
+ if weight is None:
112
+ s = sum(d for n, d in nbrdeg)
113
+ else: # weight nbr degree by weight of (n,nbr) edge
114
+ if reverse:
115
+ s = sum(G[nbr][n].get(weight, 1) * d for nbr, d in nbrdeg)
116
+ else:
117
+ s = sum(G[n][nbr].get(weight, 1) * d for nbr, d in nbrdeg)
118
+ dnorm[k] += source_degree(n, weight=weight)
119
+ dsum[k] += s
120
+
121
+ # normalize
122
+ return {k: avg if dnorm[k] == 0 else avg / dnorm[k] for k, avg in dsum.items()}
lib/python3.12/site-packages/networkx/algorithms/assortativity/correlation.py ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Node assortativity coefficients and correlation measures."""
2
+
3
+ import networkx as nx
4
+ from networkx.algorithms.assortativity.mixing import (
5
+ attribute_mixing_matrix,
6
+ degree_mixing_matrix,
7
+ )
8
+ from networkx.algorithms.assortativity.pairs import node_degree_xy
9
+
10
+ __all__ = [
11
+ "degree_pearson_correlation_coefficient",
12
+ "degree_assortativity_coefficient",
13
+ "attribute_assortativity_coefficient",
14
+ "numeric_assortativity_coefficient",
15
+ ]
16
+
17
+
18
+ @nx._dispatchable(edge_attrs="weight")
19
+ def degree_assortativity_coefficient(G, x="out", y="in", weight=None, nodes=None):
20
+ """Compute degree assortativity of graph.
21
+
22
+ Assortativity measures the similarity of connections
23
+ in the graph with respect to the node degree.
24
+
25
+ Parameters
26
+ ----------
27
+ G : NetworkX graph
28
+
29
+ x: string ('in','out')
30
+ The degree type for source node (directed graphs only).
31
+
32
+ y: string ('in','out')
33
+ The degree type for target node (directed graphs only).
34
+
35
+ weight: string or None, optional (default=None)
36
+ The edge attribute that holds the numerical value used
37
+ as a weight. If None, then each edge has weight 1.
38
+ The degree is the sum of the edge weights adjacent to the node.
39
+
40
+ nodes: list or iterable (optional)
41
+ Compute degree assortativity only for nodes in container.
42
+ The default is all nodes.
43
+
44
+ Returns
45
+ -------
46
+ r : float
47
+ Assortativity of graph by degree.
48
+
49
+ Examples
50
+ --------
51
+ >>> G = nx.path_graph(4)
52
+ >>> r = nx.degree_assortativity_coefficient(G)
53
+ >>> print(f"{r:3.1f}")
54
+ -0.5
55
+
56
+ See Also
57
+ --------
58
+ attribute_assortativity_coefficient
59
+ numeric_assortativity_coefficient
60
+ degree_mixing_dict
61
+ degree_mixing_matrix
62
+
63
+ Notes
64
+ -----
65
+ This computes Eq. (21) in Ref. [1]_ , where e is the joint
66
+ probability distribution (mixing matrix) of the degrees. If G is
67
+ directed than the matrix e is the joint probability of the
68
+ user-specified degree type for the source and target.
69
+
70
+ References
71
+ ----------
72
+ .. [1] M. E. J. Newman, Mixing patterns in networks,
73
+ Physical Review E, 67 026126, 2003
74
+ .. [2] Foster, J.G., Foster, D.V., Grassberger, P. & Paczuski, M.
75
+ Edge direction and the structure of networks, PNAS 107, 10815-20 (2010).
76
+ """
77
+ if nodes is None:
78
+ nodes = G.nodes
79
+
80
+ degrees = None
81
+
82
+ if G.is_directed():
83
+ indeg = (
84
+ {d for _, d in G.in_degree(nodes, weight=weight)}
85
+ if "in" in (x, y)
86
+ else set()
87
+ )
88
+ outdeg = (
89
+ {d for _, d in G.out_degree(nodes, weight=weight)}
90
+ if "out" in (x, y)
91
+ else set()
92
+ )
93
+ degrees = set.union(indeg, outdeg)
94
+ else:
95
+ degrees = {d for _, d in G.degree(nodes, weight=weight)}
96
+
97
+ mapping = {d: i for i, d in enumerate(degrees)}
98
+ M = degree_mixing_matrix(G, x=x, y=y, nodes=nodes, weight=weight, mapping=mapping)
99
+
100
+ return _numeric_ac(M, mapping=mapping)
101
+
102
+
103
+ @nx._dispatchable(edge_attrs="weight")
104
+ def degree_pearson_correlation_coefficient(G, x="out", y="in", weight=None, nodes=None):
105
+ """Compute degree assortativity of graph.
106
+
107
+ Assortativity measures the similarity of connections
108
+ in the graph with respect to the node degree.
109
+
110
+ This is the same as degree_assortativity_coefficient but uses the
111
+ potentially faster scipy.stats.pearsonr function.
112
+
113
+ Parameters
114
+ ----------
115
+ G : NetworkX graph
116
+
117
+ x: string ('in','out')
118
+ The degree type for source node (directed graphs only).
119
+
120
+ y: string ('in','out')
121
+ The degree type for target node (directed graphs only).
122
+
123
+ weight: string or None, optional (default=None)
124
+ The edge attribute that holds the numerical value used
125
+ as a weight. If None, then each edge has weight 1.
126
+ The degree is the sum of the edge weights adjacent to the node.
127
+
128
+ nodes: list or iterable (optional)
129
+ Compute pearson correlation of degrees only for specified nodes.
130
+ The default is all nodes.
131
+
132
+ Returns
133
+ -------
134
+ r : float
135
+ Assortativity of graph by degree.
136
+
137
+ Examples
138
+ --------
139
+ >>> G = nx.path_graph(4)
140
+ >>> r = nx.degree_pearson_correlation_coefficient(G)
141
+ >>> print(f"{r:3.1f}")
142
+ -0.5
143
+
144
+ Notes
145
+ -----
146
+ This calls scipy.stats.pearsonr.
147
+
148
+ References
149
+ ----------
150
+ .. [1] M. E. J. Newman, Mixing patterns in networks
151
+ Physical Review E, 67 026126, 2003
152
+ .. [2] Foster, J.G., Foster, D.V., Grassberger, P. & Paczuski, M.
153
+ Edge direction and the structure of networks, PNAS 107, 10815-20 (2010).
154
+ """
155
+ import scipy as sp
156
+
157
+ xy = node_degree_xy(G, x=x, y=y, nodes=nodes, weight=weight)
158
+ x, y = zip(*xy)
159
+ return float(sp.stats.pearsonr(x, y)[0])
160
+
161
+
162
+ @nx._dispatchable(node_attrs="attribute")
163
+ def attribute_assortativity_coefficient(G, attribute, nodes=None):
164
+ """Compute assortativity for node attributes.
165
+
166
+ Assortativity measures the similarity of connections
167
+ in the graph with respect to the given attribute.
168
+
169
+ Parameters
170
+ ----------
171
+ G : NetworkX graph
172
+
173
+ attribute : string
174
+ Node attribute key
175
+
176
+ nodes: list or iterable (optional)
177
+ Compute attribute assortativity for nodes in container.
178
+ The default is all nodes.
179
+
180
+ Returns
181
+ -------
182
+ r: float
183
+ Assortativity of graph for given attribute
184
+
185
+ Examples
186
+ --------
187
+ >>> G = nx.Graph()
188
+ >>> G.add_nodes_from([0, 1], color="red")
189
+ >>> G.add_nodes_from([2, 3], color="blue")
190
+ >>> G.add_edges_from([(0, 1), (2, 3)])
191
+ >>> print(nx.attribute_assortativity_coefficient(G, "color"))
192
+ 1.0
193
+
194
+ Notes
195
+ -----
196
+ This computes Eq. (2) in Ref. [1]_ , (trace(M)-sum(M^2))/(1-sum(M^2)),
197
+ where M is the joint probability distribution (mixing matrix)
198
+ of the specified attribute.
199
+
200
+ References
201
+ ----------
202
+ .. [1] M. E. J. Newman, Mixing patterns in networks,
203
+ Physical Review E, 67 026126, 2003
204
+ """
205
+ M = attribute_mixing_matrix(G, attribute, nodes)
206
+ return attribute_ac(M)
207
+
208
+
209
+ @nx._dispatchable(node_attrs="attribute")
210
+ def numeric_assortativity_coefficient(G, attribute, nodes=None):
211
+ """Compute assortativity for numerical node attributes.
212
+
213
+ Assortativity measures the similarity of connections
214
+ in the graph with respect to the given numeric attribute.
215
+
216
+ Parameters
217
+ ----------
218
+ G : NetworkX graph
219
+
220
+ attribute : string
221
+ Node attribute key.
222
+
223
+ nodes: list or iterable (optional)
224
+ Compute numeric assortativity only for attributes of nodes in
225
+ container. The default is all nodes.
226
+
227
+ Returns
228
+ -------
229
+ r: float
230
+ Assortativity of graph for given attribute
231
+
232
+ Examples
233
+ --------
234
+ >>> G = nx.Graph()
235
+ >>> G.add_nodes_from([0, 1], size=2)
236
+ >>> G.add_nodes_from([2, 3], size=3)
237
+ >>> G.add_edges_from([(0, 1), (2, 3)])
238
+ >>> print(nx.numeric_assortativity_coefficient(G, "size"))
239
+ 1.0
240
+
241
+ Notes
242
+ -----
243
+ This computes Eq. (21) in Ref. [1]_ , which is the Pearson correlation
244
+ coefficient of the specified (scalar valued) attribute across edges.
245
+
246
+ References
247
+ ----------
248
+ .. [1] M. E. J. Newman, Mixing patterns in networks
249
+ Physical Review E, 67 026126, 2003
250
+ """
251
+ if nodes is None:
252
+ nodes = G.nodes
253
+ vals = {G.nodes[n][attribute] for n in nodes}
254
+ mapping = {d: i for i, d in enumerate(vals)}
255
+ M = attribute_mixing_matrix(G, attribute, nodes, mapping)
256
+ return _numeric_ac(M, mapping)
257
+
258
+
259
+ def attribute_ac(M):
260
+ """Compute assortativity for attribute matrix M.
261
+
262
+ Parameters
263
+ ----------
264
+ M : numpy.ndarray
265
+ 2D ndarray representing the attribute mixing matrix.
266
+
267
+ Notes
268
+ -----
269
+ This computes Eq. (2) in Ref. [1]_ , (trace(e)-sum(e^2))/(1-sum(e^2)),
270
+ where e is the joint probability distribution (mixing matrix)
271
+ of the specified attribute.
272
+
273
+ References
274
+ ----------
275
+ .. [1] M. E. J. Newman, Mixing patterns in networks,
276
+ Physical Review E, 67 026126, 2003
277
+ """
278
+ if M.sum() != 1.0:
279
+ M = M / M.sum()
280
+ s = (M @ M).sum()
281
+ t = M.trace()
282
+ r = (t - s) / (1 - s)
283
+ return float(r)
284
+
285
+
286
+ def _numeric_ac(M, mapping):
287
+ # M is a 2D numpy array
288
+ # numeric assortativity coefficient, pearsonr
289
+ import numpy as np
290
+
291
+ if M.sum() != 1.0:
292
+ M = M / M.sum()
293
+ x = np.array(list(mapping.keys()))
294
+ y = x # x and y have the same support
295
+ idx = list(mapping.values())
296
+ a = M.sum(axis=0)
297
+ b = M.sum(axis=1)
298
+ vara = (a[idx] * x**2).sum() - ((a[idx] * x).sum()) ** 2
299
+ varb = (b[idx] * y**2).sum() - ((b[idx] * y).sum()) ** 2
300
+ xy = np.outer(x, y)
301
+ ab = np.outer(a[idx], b[idx])
302
+ return float((xy * (M - ab)).sum() / np.sqrt(vara * varb))
lib/python3.12/site-packages/networkx/algorithms/assortativity/mixing.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Mixing matrices for node attributes and degree.
3
+ """
4
+
5
+ import networkx as nx
6
+ from networkx.algorithms.assortativity.pairs import node_attribute_xy, node_degree_xy
7
+ from networkx.utils import dict_to_numpy_array
8
+
9
+ __all__ = [
10
+ "attribute_mixing_matrix",
11
+ "attribute_mixing_dict",
12
+ "degree_mixing_matrix",
13
+ "degree_mixing_dict",
14
+ "mixing_dict",
15
+ ]
16
+
17
+
18
+ @nx._dispatchable(node_attrs="attribute")
19
+ def attribute_mixing_dict(G, attribute, nodes=None, normalized=False):
20
+ """Returns dictionary representation of mixing matrix for attribute.
21
+
22
+ Parameters
23
+ ----------
24
+ G : graph
25
+ NetworkX graph object.
26
+
27
+ attribute : string
28
+ Node attribute key.
29
+
30
+ nodes: list or iterable (optional)
31
+ Unse nodes in container to build the dict. The default is all nodes.
32
+
33
+ normalized : bool (default=False)
34
+ Return counts if False or probabilities if True.
35
+
36
+ Examples
37
+ --------
38
+ >>> G = nx.Graph()
39
+ >>> G.add_nodes_from([0, 1], color="red")
40
+ >>> G.add_nodes_from([2, 3], color="blue")
41
+ >>> G.add_edge(1, 3)
42
+ >>> d = nx.attribute_mixing_dict(G, "color")
43
+ >>> print(d["red"]["blue"])
44
+ 1
45
+ >>> print(d["blue"]["red"]) # d symmetric for undirected graphs
46
+ 1
47
+
48
+ Returns
49
+ -------
50
+ d : dictionary
51
+ Counts or joint probability of occurrence of attribute pairs.
52
+ """
53
+ xy_iter = node_attribute_xy(G, attribute, nodes)
54
+ return mixing_dict(xy_iter, normalized=normalized)
55
+
56
+
57
+ @nx._dispatchable(node_attrs="attribute")
58
+ def attribute_mixing_matrix(G, attribute, nodes=None, mapping=None, normalized=True):
59
+ """Returns mixing matrix for attribute.
60
+
61
+ Parameters
62
+ ----------
63
+ G : graph
64
+ NetworkX graph object.
65
+
66
+ attribute : string
67
+ Node attribute key.
68
+
69
+ nodes: list or iterable (optional)
70
+ Use only nodes in container to build the matrix. The default is
71
+ all nodes.
72
+
73
+ mapping : dictionary, optional
74
+ Mapping from node attribute to integer index in matrix.
75
+ If not specified, an arbitrary ordering will be used.
76
+
77
+ normalized : bool (default=True)
78
+ Return counts if False or probabilities if True.
79
+
80
+ Returns
81
+ -------
82
+ m: numpy array
83
+ Counts or joint probability of occurrence of attribute pairs.
84
+
85
+ Notes
86
+ -----
87
+ If each node has a unique attribute value, the unnormalized mixing matrix
88
+ will be equal to the adjacency matrix. To get a denser mixing matrix,
89
+ the rounding can be performed to form groups of nodes with equal values.
90
+ For example, the exact height of persons in cm (180.79155222, 163.9080892,
91
+ 163.30095355, 167.99016217, 168.21590163, ...) can be rounded to (180, 163,
92
+ 163, 168, 168, ...).
93
+
94
+ Definitions of attribute mixing matrix vary on whether the matrix
95
+ should include rows for attribute values that don't arise. Here we
96
+ do not include such empty-rows. But you can force them to appear
97
+ by inputting a `mapping` that includes those values.
98
+
99
+ Examples
100
+ --------
101
+ >>> G = nx.path_graph(3)
102
+ >>> gender = {0: "male", 1: "female", 2: "female"}
103
+ >>> nx.set_node_attributes(G, gender, "gender")
104
+ >>> mapping = {"male": 0, "female": 1}
105
+ >>> mix_mat = nx.attribute_mixing_matrix(G, "gender", mapping=mapping)
106
+ >>> mix_mat
107
+ array([[0. , 0.25],
108
+ [0.25, 0.5 ]])
109
+ """
110
+ d = attribute_mixing_dict(G, attribute, nodes)
111
+ a = dict_to_numpy_array(d, mapping=mapping)
112
+ if normalized:
113
+ a = a / a.sum()
114
+ return a
115
+
116
+
117
+ @nx._dispatchable(edge_attrs="weight")
118
+ def degree_mixing_dict(G, x="out", y="in", weight=None, nodes=None, normalized=False):
119
+ """Returns dictionary representation of mixing matrix for degree.
120
+
121
+ Parameters
122
+ ----------
123
+ G : graph
124
+ NetworkX graph object.
125
+
126
+ x: string ('in','out')
127
+ The degree type for source node (directed graphs only).
128
+
129
+ y: string ('in','out')
130
+ The degree type for target node (directed graphs only).
131
+
132
+ weight: string or None, optional (default=None)
133
+ The edge attribute that holds the numerical value used
134
+ as a weight. If None, then each edge has weight 1.
135
+ The degree is the sum of the edge weights adjacent to the node.
136
+
137
+ normalized : bool (default=False)
138
+ Return counts if False or probabilities if True.
139
+
140
+ Returns
141
+ -------
142
+ d: dictionary
143
+ Counts or joint probability of occurrence of degree pairs.
144
+ """
145
+ xy_iter = node_degree_xy(G, x=x, y=y, nodes=nodes, weight=weight)
146
+ return mixing_dict(xy_iter, normalized=normalized)
147
+
148
+
149
+ @nx._dispatchable(edge_attrs="weight")
150
+ def degree_mixing_matrix(
151
+ G, x="out", y="in", weight=None, nodes=None, normalized=True, mapping=None
152
+ ):
153
+ """Returns mixing matrix for attribute.
154
+
155
+ Parameters
156
+ ----------
157
+ G : graph
158
+ NetworkX graph object.
159
+
160
+ x: string ('in','out')
161
+ The degree type for source node (directed graphs only).
162
+
163
+ y: string ('in','out')
164
+ The degree type for target node (directed graphs only).
165
+
166
+ nodes: list or iterable (optional)
167
+ Build the matrix using only nodes in container.
168
+ The default is all nodes.
169
+
170
+ weight: string or None, optional (default=None)
171
+ The edge attribute that holds the numerical value used
172
+ as a weight. If None, then each edge has weight 1.
173
+ The degree is the sum of the edge weights adjacent to the node.
174
+
175
+ normalized : bool (default=True)
176
+ Return counts if False or probabilities if True.
177
+
178
+ mapping : dictionary, optional
179
+ Mapping from node degree to integer index in matrix.
180
+ If not specified, an arbitrary ordering will be used.
181
+
182
+ Returns
183
+ -------
184
+ m: numpy array
185
+ Counts, or joint probability, of occurrence of node degree.
186
+
187
+ Notes
188
+ -----
189
+ Definitions of degree mixing matrix vary on whether the matrix
190
+ should include rows for degree values that don't arise. Here we
191
+ do not include such empty-rows. But you can force them to appear
192
+ by inputting a `mapping` that includes those values. See examples.
193
+
194
+ Examples
195
+ --------
196
+ >>> G = nx.star_graph(3)
197
+ >>> mix_mat = nx.degree_mixing_matrix(G)
198
+ >>> mix_mat
199
+ array([[0. , 0.5],
200
+ [0.5, 0. ]])
201
+
202
+ If you want every possible degree to appear as a row, even if no nodes
203
+ have that degree, use `mapping` as follows,
204
+
205
+ >>> max_degree = max(deg for n, deg in G.degree)
206
+ >>> mapping = {x: x for x in range(max_degree + 1)} # identity mapping
207
+ >>> mix_mat = nx.degree_mixing_matrix(G, mapping=mapping)
208
+ >>> mix_mat
209
+ array([[0. , 0. , 0. , 0. ],
210
+ [0. , 0. , 0. , 0.5],
211
+ [0. , 0. , 0. , 0. ],
212
+ [0. , 0.5, 0. , 0. ]])
213
+ """
214
+ d = degree_mixing_dict(G, x=x, y=y, nodes=nodes, weight=weight)
215
+ a = dict_to_numpy_array(d, mapping=mapping)
216
+ if normalized:
217
+ a = a / a.sum()
218
+ return a
219
+
220
+
221
+ def mixing_dict(xy, normalized=False):
222
+ """Returns a dictionary representation of mixing matrix.
223
+
224
+ Parameters
225
+ ----------
226
+ xy : list or container of two-tuples
227
+ Pairs of (x,y) items.
228
+
229
+ attribute : string
230
+ Node attribute key
231
+
232
+ normalized : bool (default=False)
233
+ Return counts if False or probabilities if True.
234
+
235
+ Returns
236
+ -------
237
+ d: dictionary
238
+ Counts or Joint probability of occurrence of values in xy.
239
+ """
240
+ d = {}
241
+ psum = 0.0
242
+ for x, y in xy:
243
+ if x not in d:
244
+ d[x] = {}
245
+ if y not in d:
246
+ d[y] = {}
247
+ v = d[x].get(y, 0)
248
+ d[x][y] = v + 1
249
+ psum += 1
250
+
251
+ if normalized:
252
+ for _, jdict in d.items():
253
+ for j in jdict:
254
+ jdict[j] /= psum
255
+ return d
lib/python3.12/site-packages/networkx/algorithms/assortativity/neighbor_degree.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import networkx as nx
2
+
3
+ __all__ = ["average_neighbor_degree"]
4
+
5
+
6
+ @nx._dispatchable(edge_attrs="weight")
7
+ def average_neighbor_degree(G, source="out", target="out", nodes=None, weight=None):
8
+ r"""Returns the average degree of the neighborhood of each node.
9
+
10
+ In an undirected graph, the neighborhood `N(i)` of node `i` contains the
11
+ nodes that are connected to `i` by an edge.
12
+
13
+ For directed graphs, `N(i)` is defined according to the parameter `source`:
14
+
15
+ - if source is 'in', then `N(i)` consists of predecessors of node `i`.
16
+ - if source is 'out', then `N(i)` consists of successors of node `i`.
17
+ - if source is 'in+out', then `N(i)` is both predecessors and successors.
18
+
19
+ The average neighborhood degree of a node `i` is
20
+
21
+ .. math::
22
+
23
+ k_{nn,i} = \frac{1}{|N(i)|} \sum_{j \in N(i)} k_j
24
+
25
+ where `N(i)` are the neighbors of node `i` and `k_j` is
26
+ the degree of node `j` which belongs to `N(i)`. For weighted
27
+ graphs, an analogous measure can be defined [1]_,
28
+
29
+ .. math::
30
+
31
+ k_{nn,i}^{w} = \frac{1}{s_i} \sum_{j \in N(i)} w_{ij} k_j
32
+
33
+ where `s_i` is the weighted degree of node `i`, `w_{ij}`
34
+ is the weight of the edge that links `i` and `j` and
35
+ `N(i)` are the neighbors of node `i`.
36
+
37
+
38
+ Parameters
39
+ ----------
40
+ G : NetworkX graph
41
+
42
+ source : string ("in"|"out"|"in+out"), optional (default="out")
43
+ Directed graphs only.
44
+ Use "in"- or "out"-neighbors of source node.
45
+
46
+ target : string ("in"|"out"|"in+out"), optional (default="out")
47
+ Directed graphs only.
48
+ Use "in"- or "out"-degree for target node.
49
+
50
+ nodes : list or iterable, optional (default=G.nodes)
51
+ Compute neighbor degree only for specified nodes.
52
+
53
+ weight : string or None, optional (default=None)
54
+ The edge attribute that holds the numerical value used as a weight.
55
+ If None, then each edge has weight 1.
56
+
57
+ Returns
58
+ -------
59
+ d: dict
60
+ A dictionary keyed by node to the average degree of its neighbors.
61
+
62
+ Raises
63
+ ------
64
+ NetworkXError
65
+ If either `source` or `target` are not one of 'in', 'out', or 'in+out'.
66
+ If either `source` or `target` is passed for an undirected graph.
67
+
68
+ Examples
69
+ --------
70
+ >>> G = nx.path_graph(4)
71
+ >>> G.edges[0, 1]["weight"] = 5
72
+ >>> G.edges[2, 3]["weight"] = 3
73
+
74
+ >>> nx.average_neighbor_degree(G)
75
+ {0: 2.0, 1: 1.5, 2: 1.5, 3: 2.0}
76
+ >>> nx.average_neighbor_degree(G, weight="weight")
77
+ {0: 2.0, 1: 1.1666666666666667, 2: 1.25, 3: 2.0}
78
+
79
+ >>> G = nx.DiGraph()
80
+ >>> nx.add_path(G, [0, 1, 2, 3])
81
+ >>> nx.average_neighbor_degree(G, source="in", target="in")
82
+ {0: 0.0, 1: 0.0, 2: 1.0, 3: 1.0}
83
+
84
+ >>> nx.average_neighbor_degree(G, source="out", target="out")
85
+ {0: 1.0, 1: 1.0, 2: 0.0, 3: 0.0}
86
+
87
+ See Also
88
+ --------
89
+ average_degree_connectivity
90
+
91
+ References
92
+ ----------
93
+ .. [1] A. Barrat, M. Barthélemy, R. Pastor-Satorras, and A. Vespignani,
94
+ "The architecture of complex weighted networks".
95
+ PNAS 101 (11): 3747–3752 (2004).
96
+ """
97
+ if G.is_directed():
98
+ if source == "in":
99
+ source_degree = G.in_degree
100
+ elif source == "out":
101
+ source_degree = G.out_degree
102
+ elif source == "in+out":
103
+ source_degree = G.degree
104
+ else:
105
+ raise nx.NetworkXError(
106
+ f"source argument {source} must be 'in', 'out' or 'in+out'"
107
+ )
108
+
109
+ if target == "in":
110
+ target_degree = G.in_degree
111
+ elif target == "out":
112
+ target_degree = G.out_degree
113
+ elif target == "in+out":
114
+ target_degree = G.degree
115
+ else:
116
+ raise nx.NetworkXError(
117
+ f"target argument {target} must be 'in', 'out' or 'in+out'"
118
+ )
119
+ else:
120
+ if source != "out" or target != "out":
121
+ raise nx.NetworkXError(
122
+ f"source and target arguments are only supported for directed graphs"
123
+ )
124
+ source_degree = target_degree = G.degree
125
+
126
+ # precompute target degrees -- should *not* be weighted degree
127
+ t_deg = dict(target_degree())
128
+
129
+ # Set up both predecessor and successor neighbor dicts leaving empty if not needed
130
+ G_P = G_S = {n: {} for n in G}
131
+ if G.is_directed():
132
+ # "in" or "in+out" cases: G_P contains predecessors
133
+ if "in" in source:
134
+ G_P = G.pred
135
+ # "out" or "in+out" cases: G_S contains successors
136
+ if "out" in source:
137
+ G_S = G.succ
138
+ else:
139
+ # undirected leave G_P empty but G_S is the adjacency
140
+ G_S = G.adj
141
+
142
+ # Main loop: Compute average degree of neighbors
143
+ avg = {}
144
+ for n, deg in source_degree(nodes, weight=weight):
145
+ # handle degree zero average
146
+ if deg == 0:
147
+ avg[n] = 0.0
148
+ continue
149
+
150
+ # we sum over both G_P and G_S, but one of the two is usually empty.
151
+ if weight is None:
152
+ avg[n] = (
153
+ sum(t_deg[nbr] for nbr in G_S[n]) + sum(t_deg[nbr] for nbr in G_P[n])
154
+ ) / deg
155
+ else:
156
+ avg[n] = (
157
+ sum(dd.get(weight, 1) * t_deg[nbr] for nbr, dd in G_S[n].items())
158
+ + sum(dd.get(weight, 1) * t_deg[nbr] for nbr, dd in G_P[n].items())
159
+ ) / deg
160
+ return avg
lib/python3.12/site-packages/networkx/algorithms/assortativity/pairs.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Generators of x-y pairs of node data."""
2
+
3
+ import networkx as nx
4
+
5
+ __all__ = ["node_attribute_xy", "node_degree_xy"]
6
+
7
+
8
+ @nx._dispatchable(node_attrs="attribute")
9
+ def node_attribute_xy(G, attribute, nodes=None):
10
+ """Yields 2-tuples of node attribute values for all edges in `G`.
11
+
12
+ This generator yields, for each edge in `G` incident to a node in `nodes`,
13
+ a 2-tuple of form ``(attribute value, attribute value)`` for the parameter
14
+ specified node-attribute.
15
+
16
+ Parameters
17
+ ----------
18
+ G: NetworkX graph
19
+
20
+ attribute: key
21
+ The node attribute key.
22
+
23
+ nodes: list or iterable (optional)
24
+ Use only edges that are incident to specified nodes.
25
+ The default is all nodes.
26
+
27
+ Yields
28
+ ------
29
+ (x, y): 2-tuple
30
+ Generates 2-tuple of (attribute, attribute) values.
31
+
32
+ Examples
33
+ --------
34
+ >>> G = nx.DiGraph()
35
+ >>> G.add_node(1, color="red")
36
+ >>> G.add_node(2, color="blue")
37
+ >>> G.add_node(3, color="green")
38
+ >>> G.add_edge(1, 2)
39
+ >>> list(nx.node_attribute_xy(G, "color"))
40
+ [('red', 'blue')]
41
+
42
+ Notes
43
+ -----
44
+ For undirected graphs, each edge is produced twice, once for each edge
45
+ representation (u, v) and (v, u), with the exception of self-loop edges
46
+ which only appear once.
47
+ """
48
+ if nodes is None:
49
+ nodes = set(G)
50
+ else:
51
+ nodes = set(nodes)
52
+ Gnodes = G.nodes
53
+ for u, nbrsdict in G.adjacency():
54
+ if u not in nodes:
55
+ continue
56
+ uattr = Gnodes[u].get(attribute, None)
57
+ if G.is_multigraph():
58
+ for v, keys in nbrsdict.items():
59
+ vattr = Gnodes[v].get(attribute, None)
60
+ for _ in keys:
61
+ yield (uattr, vattr)
62
+ else:
63
+ for v in nbrsdict:
64
+ vattr = Gnodes[v].get(attribute, None)
65
+ yield (uattr, vattr)
66
+
67
+
68
+ @nx._dispatchable(edge_attrs="weight")
69
+ def node_degree_xy(G, x="out", y="in", weight=None, nodes=None):
70
+ """Yields 2-tuples of ``(degree, degree)`` values for edges in `G`.
71
+
72
+ This generator yields, for each edge in `G` incident to a node in `nodes`,
73
+ a 2-tuple of form ``(degree, degree)``. The node degrees are weighted
74
+ when a `weight` attribute is specified.
75
+
76
+ Parameters
77
+ ----------
78
+ G: NetworkX graph
79
+
80
+ x: string ('in','out')
81
+ The degree type for source node (directed graphs only).
82
+
83
+ y: string ('in','out')
84
+ The degree type for target node (directed graphs only).
85
+
86
+ weight: string or None, optional (default=None)
87
+ The edge attribute that holds the numerical value used
88
+ as a weight. If None, then each edge has weight 1.
89
+ The degree is the sum of the edge weights adjacent to the node.
90
+
91
+ nodes: list or iterable (optional)
92
+ Use only edges that are adjacency to specified nodes.
93
+ The default is all nodes.
94
+
95
+ Yields
96
+ ------
97
+ (x, y): 2-tuple
98
+ Generates 2-tuple of (degree, degree) values.
99
+
100
+ Examples
101
+ --------
102
+ >>> G = nx.DiGraph()
103
+ >>> G.add_edge(1, 2)
104
+ >>> list(nx.node_degree_xy(G, x="out", y="in"))
105
+ [(1, 1)]
106
+ >>> list(nx.node_degree_xy(G, x="in", y="out"))
107
+ [(0, 0)]
108
+
109
+ Notes
110
+ -----
111
+ For undirected graphs, each edge is produced twice, once for each edge
112
+ representation (u, v) and (v, u), with the exception of self-loop edges
113
+ which only appear once.
114
+ """
115
+ nodes = set(G) if nodes is None else set(nodes)
116
+ if G.is_directed():
117
+ direction = {"out": G.out_degree, "in": G.in_degree}
118
+ xdeg = direction[x]
119
+ ydeg = direction[y]
120
+ else:
121
+ xdeg = ydeg = G.degree
122
+
123
+ for u, degu in xdeg(nodes, weight=weight):
124
+ # use G.edges to treat multigraphs correctly
125
+ neighbors = (nbr for _, nbr in G.edges(u) if nbr in nodes)
126
+ for _, degv in ydeg(neighbors, weight=weight):
127
+ yield degu, degv
lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__init__.py ADDED
File without changes
lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (218 Bytes). View file
 
lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/base_test.cpython-312.pyc ADDED
Binary file (5.4 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_connectivity.cpython-312.pyc ADDED
Binary file (7.95 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_correlation.cpython-312.pyc ADDED
Binary file (10.7 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_mixing.cpython-312.pyc ADDED
Binary file (12.5 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_neighbor_degree.cpython-312.pyc ADDED
Binary file (6.64 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_pairs.cpython-312.pyc ADDED
Binary file (5.36 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/base_test.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import networkx as nx
2
+
3
+
4
+ class BaseTestAttributeMixing:
5
+ @classmethod
6
+ def setup_class(cls):
7
+ G = nx.Graph()
8
+ G.add_nodes_from([0, 1], fish="one")
9
+ G.add_nodes_from([2, 3], fish="two")
10
+ G.add_nodes_from([4], fish="red")
11
+ G.add_nodes_from([5], fish="blue")
12
+ G.add_edges_from([(0, 1), (2, 3), (0, 4), (2, 5)])
13
+ cls.G = G
14
+
15
+ D = nx.DiGraph()
16
+ D.add_nodes_from([0, 1], fish="one")
17
+ D.add_nodes_from([2, 3], fish="two")
18
+ D.add_nodes_from([4], fish="red")
19
+ D.add_nodes_from([5], fish="blue")
20
+ D.add_edges_from([(0, 1), (2, 3), (0, 4), (2, 5)])
21
+ cls.D = D
22
+
23
+ M = nx.MultiGraph()
24
+ M.add_nodes_from([0, 1], fish="one")
25
+ M.add_nodes_from([2, 3], fish="two")
26
+ M.add_nodes_from([4], fish="red")
27
+ M.add_nodes_from([5], fish="blue")
28
+ M.add_edges_from([(0, 1), (0, 1), (2, 3)])
29
+ cls.M = M
30
+
31
+ S = nx.Graph()
32
+ S.add_nodes_from([0, 1], fish="one")
33
+ S.add_nodes_from([2, 3], fish="two")
34
+ S.add_nodes_from([4], fish="red")
35
+ S.add_nodes_from([5], fish="blue")
36
+ S.add_edge(0, 0)
37
+ S.add_edge(2, 2)
38
+ cls.S = S
39
+
40
+ N = nx.Graph()
41
+ N.add_nodes_from([0, 1], margin=-2)
42
+ N.add_nodes_from([2, 3], margin=-2)
43
+ N.add_nodes_from([4], margin=-3)
44
+ N.add_nodes_from([5], margin=-4)
45
+ N.add_edges_from([(0, 1), (2, 3), (0, 4), (2, 5)])
46
+ cls.N = N
47
+
48
+ F = nx.Graph()
49
+ F.add_edges_from([(0, 3), (1, 3), (2, 3)], weight=0.5)
50
+ F.add_edge(0, 2, weight=1)
51
+ nx.set_node_attributes(F, dict(F.degree(weight="weight")), "margin")
52
+ cls.F = F
53
+
54
+ K = nx.Graph()
55
+ K.add_nodes_from([1, 2], margin=-1)
56
+ K.add_nodes_from([3], margin=1)
57
+ K.add_nodes_from([4], margin=2)
58
+ K.add_edges_from([(3, 4), (1, 2), (1, 3)])
59
+ cls.K = K
60
+
61
+
62
+ class BaseTestDegreeMixing:
63
+ @classmethod
64
+ def setup_class(cls):
65
+ cls.P4 = nx.path_graph(4)
66
+ cls.D = nx.DiGraph()
67
+ cls.D.add_edges_from([(0, 2), (0, 3), (1, 3), (2, 3)])
68
+ cls.D2 = nx.DiGraph()
69
+ cls.D2.add_edges_from([(0, 3), (1, 0), (1, 2), (2, 4), (4, 1), (4, 3), (4, 2)])
70
+ cls.M = nx.MultiGraph()
71
+ nx.add_path(cls.M, range(4))
72
+ cls.M.add_edge(0, 1)
73
+ cls.S = nx.Graph()
74
+ cls.S.add_edges_from([(0, 0), (1, 1)])
75
+ cls.W = nx.Graph()
76
+ cls.W.add_edges_from([(0, 3), (1, 3), (2, 3)], weight=0.5)
77
+ cls.W.add_edge(0, 2, weight=1)
78
+ S1 = nx.star_graph(4)
79
+ S2 = nx.star_graph(4)
80
+ cls.DS = nx.disjoint_union(S1, S2)
81
+ cls.DS.add_edge(4, 5)
lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_connectivity.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from itertools import permutations
2
+
3
+ import pytest
4
+
5
+ import networkx as nx
6
+
7
+
8
+ class TestNeighborConnectivity:
9
+ def test_degree_p4(self):
10
+ G = nx.path_graph(4)
11
+ answer = {1: 2.0, 2: 1.5}
12
+ nd = nx.average_degree_connectivity(G)
13
+ assert nd == answer
14
+
15
+ D = G.to_directed()
16
+ answer = {2: 2.0, 4: 1.5}
17
+ nd = nx.average_degree_connectivity(D)
18
+ assert nd == answer
19
+
20
+ answer = {1: 2.0, 2: 1.5}
21
+ D = G.to_directed()
22
+ nd = nx.average_degree_connectivity(D, source="in", target="in")
23
+ assert nd == answer
24
+
25
+ D = G.to_directed()
26
+ nd = nx.average_degree_connectivity(D, source="in", target="in")
27
+ assert nd == answer
28
+
29
+ def test_degree_p4_weighted(self):
30
+ G = nx.path_graph(4)
31
+ G[1][2]["weight"] = 4
32
+ answer = {1: 2.0, 2: 1.8}
33
+ nd = nx.average_degree_connectivity(G, weight="weight")
34
+ assert nd == answer
35
+ answer = {1: 2.0, 2: 1.5}
36
+ nd = nx.average_degree_connectivity(G)
37
+ assert nd == answer
38
+
39
+ D = G.to_directed()
40
+ answer = {2: 2.0, 4: 1.8}
41
+ nd = nx.average_degree_connectivity(D, weight="weight")
42
+ assert nd == answer
43
+
44
+ answer = {1: 2.0, 2: 1.8}
45
+ D = G.to_directed()
46
+ nd = nx.average_degree_connectivity(
47
+ D, weight="weight", source="in", target="in"
48
+ )
49
+ assert nd == answer
50
+
51
+ D = G.to_directed()
52
+ nd = nx.average_degree_connectivity(
53
+ D, source="in", target="out", weight="weight"
54
+ )
55
+ assert nd == answer
56
+
57
+ def test_weight_keyword(self):
58
+ G = nx.path_graph(4)
59
+ G[1][2]["other"] = 4
60
+ answer = {1: 2.0, 2: 1.8}
61
+ nd = nx.average_degree_connectivity(G, weight="other")
62
+ assert nd == answer
63
+ answer = {1: 2.0, 2: 1.5}
64
+ nd = nx.average_degree_connectivity(G, weight=None)
65
+ assert nd == answer
66
+
67
+ D = G.to_directed()
68
+ answer = {2: 2.0, 4: 1.8}
69
+ nd = nx.average_degree_connectivity(D, weight="other")
70
+ assert nd == answer
71
+
72
+ answer = {1: 2.0, 2: 1.8}
73
+ D = G.to_directed()
74
+ nd = nx.average_degree_connectivity(D, weight="other", source="in", target="in")
75
+ assert nd == answer
76
+
77
+ D = G.to_directed()
78
+ nd = nx.average_degree_connectivity(D, weight="other", source="in", target="in")
79
+ assert nd == answer
80
+
81
+ def test_degree_barrat(self):
82
+ G = nx.star_graph(5)
83
+ G.add_edges_from([(5, 6), (5, 7), (5, 8), (5, 9)])
84
+ G[0][5]["weight"] = 5
85
+ nd = nx.average_degree_connectivity(G)[5]
86
+ assert nd == 1.8
87
+ nd = nx.average_degree_connectivity(G, weight="weight")[5]
88
+ assert nd == pytest.approx(3.222222, abs=1e-5)
89
+
90
+ def test_zero_deg(self):
91
+ G = nx.DiGraph()
92
+ G.add_edge(1, 2)
93
+ G.add_edge(1, 3)
94
+ G.add_edge(1, 4)
95
+ c = nx.average_degree_connectivity(G)
96
+ assert c == {1: 0, 3: 1}
97
+ c = nx.average_degree_connectivity(G, source="in", target="in")
98
+ assert c == {0: 0, 1: 0}
99
+ c = nx.average_degree_connectivity(G, source="in", target="out")
100
+ assert c == {0: 0, 1: 3}
101
+ c = nx.average_degree_connectivity(G, source="in", target="in+out")
102
+ assert c == {0: 0, 1: 3}
103
+ c = nx.average_degree_connectivity(G, source="out", target="out")
104
+ assert c == {0: 0, 3: 0}
105
+ c = nx.average_degree_connectivity(G, source="out", target="in")
106
+ assert c == {0: 0, 3: 1}
107
+ c = nx.average_degree_connectivity(G, source="out", target="in+out")
108
+ assert c == {0: 0, 3: 1}
109
+
110
+ def test_in_out_weight(self):
111
+ G = nx.DiGraph()
112
+ G.add_edge(1, 2, weight=1)
113
+ G.add_edge(1, 3, weight=1)
114
+ G.add_edge(3, 1, weight=1)
115
+ for s, t in permutations(["in", "out", "in+out"], 2):
116
+ c = nx.average_degree_connectivity(G, source=s, target=t)
117
+ cw = nx.average_degree_connectivity(G, source=s, target=t, weight="weight")
118
+ assert c == cw
119
+
120
+ def test_invalid_source(self):
121
+ with pytest.raises(nx.NetworkXError):
122
+ G = nx.DiGraph()
123
+ nx.average_degree_connectivity(G, source="bogus")
124
+
125
+ def test_invalid_target(self):
126
+ with pytest.raises(nx.NetworkXError):
127
+ G = nx.DiGraph()
128
+ nx.average_degree_connectivity(G, target="bogus")
129
+
130
+ def test_invalid_undirected_graph(self):
131
+ G = nx.Graph()
132
+ with pytest.raises(nx.NetworkXError):
133
+ nx.average_degree_connectivity(G, target="bogus")
134
+ with pytest.raises(nx.NetworkXError):
135
+ nx.average_degree_connectivity(G, source="bogus")
136
+
137
+ def test_single_node(self):
138
+ # TODO Is this really the intended behavior for providing a
139
+ # single node as the argument `nodes`? Shouldn't the function
140
+ # just return the connectivity value itself?
141
+ G = nx.trivial_graph()
142
+ conn = nx.average_degree_connectivity(G, nodes=0)
143
+ assert conn == {0: 0}
lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_correlation.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import networkx as nx
4
+ from networkx.algorithms.assortativity.correlation import attribute_ac
5
+
6
+ from .base_test import BaseTestAttributeMixing, BaseTestDegreeMixing
7
+
8
+ np = pytest.importorskip("numpy")
9
+ pytest.importorskip("scipy")
10
+
11
+
12
+ class TestDegreeMixingCorrelation(BaseTestDegreeMixing):
13
+ def test_degree_assortativity_undirected(self):
14
+ r = nx.degree_assortativity_coefficient(self.P4)
15
+ np.testing.assert_almost_equal(r, -1.0 / 2, decimal=4)
16
+
17
+ def test_degree_assortativity_node_kwargs(self):
18
+ G = nx.Graph()
19
+ edges = [(0, 1), (0, 3), (1, 2), (1, 3), (1, 4), (5, 9), (9, 0)]
20
+ G.add_edges_from(edges)
21
+ r = nx.degree_assortativity_coefficient(G, nodes=[1, 2, 4])
22
+ np.testing.assert_almost_equal(r, -1.0, decimal=4)
23
+
24
+ def test_degree_assortativity_directed(self):
25
+ r = nx.degree_assortativity_coefficient(self.D)
26
+ np.testing.assert_almost_equal(r, -0.57735, decimal=4)
27
+
28
+ def test_degree_assortativity_directed2(self):
29
+ """Test degree assortativity for a directed graph where the set of
30
+ in/out degree does not equal the total degree."""
31
+ r = nx.degree_assortativity_coefficient(self.D2)
32
+ np.testing.assert_almost_equal(r, 0.14852, decimal=4)
33
+
34
+ def test_degree_assortativity_multigraph(self):
35
+ r = nx.degree_assortativity_coefficient(self.M)
36
+ np.testing.assert_almost_equal(r, -1.0 / 7.0, decimal=4)
37
+
38
+ def test_degree_pearson_assortativity_undirected(self):
39
+ r = nx.degree_pearson_correlation_coefficient(self.P4)
40
+ np.testing.assert_almost_equal(r, -1.0 / 2, decimal=4)
41
+
42
+ def test_degree_pearson_assortativity_directed(self):
43
+ r = nx.degree_pearson_correlation_coefficient(self.D)
44
+ np.testing.assert_almost_equal(r, -0.57735, decimal=4)
45
+
46
+ def test_degree_pearson_assortativity_directed2(self):
47
+ """Test degree assortativity with Pearson for a directed graph where
48
+ the set of in/out degree does not equal the total degree."""
49
+ r = nx.degree_pearson_correlation_coefficient(self.D2)
50
+ np.testing.assert_almost_equal(r, 0.14852, decimal=4)
51
+
52
+ def test_degree_pearson_assortativity_multigraph(self):
53
+ r = nx.degree_pearson_correlation_coefficient(self.M)
54
+ np.testing.assert_almost_equal(r, -1.0 / 7.0, decimal=4)
55
+
56
+ def test_degree_assortativity_weighted(self):
57
+ r = nx.degree_assortativity_coefficient(self.W, weight="weight")
58
+ np.testing.assert_almost_equal(r, -0.1429, decimal=4)
59
+
60
+ def test_degree_assortativity_double_star(self):
61
+ r = nx.degree_assortativity_coefficient(self.DS)
62
+ np.testing.assert_almost_equal(r, -0.9339, decimal=4)
63
+
64
+
65
+ class TestAttributeMixingCorrelation(BaseTestAttributeMixing):
66
+ def test_attribute_assortativity_undirected(self):
67
+ r = nx.attribute_assortativity_coefficient(self.G, "fish")
68
+ assert r == 6.0 / 22.0
69
+
70
+ def test_attribute_assortativity_directed(self):
71
+ r = nx.attribute_assortativity_coefficient(self.D, "fish")
72
+ assert r == 1.0 / 3.0
73
+
74
+ def test_attribute_assortativity_multigraph(self):
75
+ r = nx.attribute_assortativity_coefficient(self.M, "fish")
76
+ assert r == 1.0
77
+
78
+ def test_attribute_assortativity_coefficient(self):
79
+ # from "Mixing patterns in networks"
80
+ # fmt: off
81
+ a = np.array([[0.258, 0.016, 0.035, 0.013],
82
+ [0.012, 0.157, 0.058, 0.019],
83
+ [0.013, 0.023, 0.306, 0.035],
84
+ [0.005, 0.007, 0.024, 0.016]])
85
+ # fmt: on
86
+ r = attribute_ac(a)
87
+ np.testing.assert_almost_equal(r, 0.623, decimal=3)
88
+
89
+ def test_attribute_assortativity_coefficient2(self):
90
+ # fmt: off
91
+ a = np.array([[0.18, 0.02, 0.01, 0.03],
92
+ [0.02, 0.20, 0.03, 0.02],
93
+ [0.01, 0.03, 0.16, 0.01],
94
+ [0.03, 0.02, 0.01, 0.22]])
95
+ # fmt: on
96
+ r = attribute_ac(a)
97
+ np.testing.assert_almost_equal(r, 0.68, decimal=2)
98
+
99
+ def test_attribute_assortativity(self):
100
+ a = np.array([[50, 50, 0], [50, 50, 0], [0, 0, 2]])
101
+ r = attribute_ac(a)
102
+ np.testing.assert_almost_equal(r, 0.029, decimal=3)
103
+
104
+ def test_attribute_assortativity_negative(self):
105
+ r = nx.numeric_assortativity_coefficient(self.N, "margin")
106
+ np.testing.assert_almost_equal(r, -0.2903, decimal=4)
107
+
108
+ def test_assortativity_node_kwargs(self):
109
+ G = nx.Graph()
110
+ G.add_nodes_from([0, 1], size=2)
111
+ G.add_nodes_from([2, 3], size=3)
112
+ G.add_edges_from([(0, 1), (2, 3)])
113
+ r = nx.numeric_assortativity_coefficient(G, "size", nodes=[0, 3])
114
+ np.testing.assert_almost_equal(r, 1.0, decimal=4)
115
+
116
+ def test_attribute_assortativity_float(self):
117
+ r = nx.numeric_assortativity_coefficient(self.F, "margin")
118
+ np.testing.assert_almost_equal(r, -0.1429, decimal=4)
119
+
120
+ def test_attribute_assortativity_mixed(self):
121
+ r = nx.numeric_assortativity_coefficient(self.K, "margin")
122
+ np.testing.assert_almost_equal(r, 0.4340, decimal=4)
lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_mixing.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import networkx as nx
4
+
5
+ from .base_test import BaseTestAttributeMixing, BaseTestDegreeMixing
6
+
7
+ np = pytest.importorskip("numpy")
8
+
9
+
10
+ class TestDegreeMixingDict(BaseTestDegreeMixing):
11
+ def test_degree_mixing_dict_undirected(self):
12
+ d = nx.degree_mixing_dict(self.P4)
13
+ d_result = {1: {2: 2}, 2: {1: 2, 2: 2}}
14
+ assert d == d_result
15
+
16
+ def test_degree_mixing_dict_undirected_normalized(self):
17
+ d = nx.degree_mixing_dict(self.P4, normalized=True)
18
+ d_result = {1: {2: 1.0 / 3}, 2: {1: 1.0 / 3, 2: 1.0 / 3}}
19
+ assert d == d_result
20
+
21
+ def test_degree_mixing_dict_directed(self):
22
+ d = nx.degree_mixing_dict(self.D)
23
+ d_result = {1: {3: 2}, 2: {1: 1, 3: 1}, 3: {}}
24
+ assert d == d_result
25
+
26
+ def test_degree_mixing_dict_multigraph(self):
27
+ d = nx.degree_mixing_dict(self.M)
28
+ d_result = {1: {2: 1}, 2: {1: 1, 3: 3}, 3: {2: 3}}
29
+ assert d == d_result
30
+
31
+ def test_degree_mixing_dict_weighted(self):
32
+ d = nx.degree_mixing_dict(self.W, weight="weight")
33
+ d_result = {0.5: {1.5: 1}, 1.5: {1.5: 6, 0.5: 1}}
34
+ assert d == d_result
35
+
36
+
37
+ class TestDegreeMixingMatrix(BaseTestDegreeMixing):
38
+ def test_degree_mixing_matrix_undirected(self):
39
+ # fmt: off
40
+ a_result = np.array([[0, 2],
41
+ [2, 2]]
42
+ )
43
+ # fmt: on
44
+ a = nx.degree_mixing_matrix(self.P4, normalized=False)
45
+ np.testing.assert_equal(a, a_result)
46
+ a = nx.degree_mixing_matrix(self.P4)
47
+ np.testing.assert_equal(a, a_result / a_result.sum())
48
+
49
+ def test_degree_mixing_matrix_directed(self):
50
+ # fmt: off
51
+ a_result = np.array([[0, 0, 2],
52
+ [1, 0, 1],
53
+ [0, 0, 0]]
54
+ )
55
+ # fmt: on
56
+ a = nx.degree_mixing_matrix(self.D, normalized=False)
57
+ np.testing.assert_equal(a, a_result)
58
+ a = nx.degree_mixing_matrix(self.D)
59
+ np.testing.assert_equal(a, a_result / a_result.sum())
60
+
61
+ def test_degree_mixing_matrix_multigraph(self):
62
+ # fmt: off
63
+ a_result = np.array([[0, 1, 0],
64
+ [1, 0, 3],
65
+ [0, 3, 0]]
66
+ )
67
+ # fmt: on
68
+ a = nx.degree_mixing_matrix(self.M, normalized=False)
69
+ np.testing.assert_equal(a, a_result)
70
+ a = nx.degree_mixing_matrix(self.M)
71
+ np.testing.assert_equal(a, a_result / a_result.sum())
72
+
73
+ def test_degree_mixing_matrix_selfloop(self):
74
+ # fmt: off
75
+ a_result = np.array([[2]])
76
+ # fmt: on
77
+ a = nx.degree_mixing_matrix(self.S, normalized=False)
78
+ np.testing.assert_equal(a, a_result)
79
+ a = nx.degree_mixing_matrix(self.S)
80
+ np.testing.assert_equal(a, a_result / a_result.sum())
81
+
82
+ def test_degree_mixing_matrix_weighted(self):
83
+ a_result = np.array([[0.0, 1.0], [1.0, 6.0]])
84
+ a = nx.degree_mixing_matrix(self.W, weight="weight", normalized=False)
85
+ np.testing.assert_equal(a, a_result)
86
+ a = nx.degree_mixing_matrix(self.W, weight="weight")
87
+ np.testing.assert_equal(a, a_result / float(a_result.sum()))
88
+
89
+ def test_degree_mixing_matrix_mapping(self):
90
+ a_result = np.array([[6.0, 1.0], [1.0, 0.0]])
91
+ mapping = {0.5: 1, 1.5: 0}
92
+ a = nx.degree_mixing_matrix(
93
+ self.W, weight="weight", normalized=False, mapping=mapping
94
+ )
95
+ np.testing.assert_equal(a, a_result)
96
+
97
+
98
+ class TestAttributeMixingDict(BaseTestAttributeMixing):
99
+ def test_attribute_mixing_dict_undirected(self):
100
+ d = nx.attribute_mixing_dict(self.G, "fish")
101
+ d_result = {
102
+ "one": {"one": 2, "red": 1},
103
+ "two": {"two": 2, "blue": 1},
104
+ "red": {"one": 1},
105
+ "blue": {"two": 1},
106
+ }
107
+ assert d == d_result
108
+
109
+ def test_attribute_mixing_dict_directed(self):
110
+ d = nx.attribute_mixing_dict(self.D, "fish")
111
+ d_result = {
112
+ "one": {"one": 1, "red": 1},
113
+ "two": {"two": 1, "blue": 1},
114
+ "red": {},
115
+ "blue": {},
116
+ }
117
+ assert d == d_result
118
+
119
+ def test_attribute_mixing_dict_multigraph(self):
120
+ d = nx.attribute_mixing_dict(self.M, "fish")
121
+ d_result = {"one": {"one": 4}, "two": {"two": 2}}
122
+ assert d == d_result
123
+
124
+
125
+ class TestAttributeMixingMatrix(BaseTestAttributeMixing):
126
+ def test_attribute_mixing_matrix_undirected(self):
127
+ mapping = {"one": 0, "two": 1, "red": 2, "blue": 3}
128
+ a_result = np.array([[2, 0, 1, 0], [0, 2, 0, 1], [1, 0, 0, 0], [0, 1, 0, 0]])
129
+ a = nx.attribute_mixing_matrix(
130
+ self.G, "fish", mapping=mapping, normalized=False
131
+ )
132
+ np.testing.assert_equal(a, a_result)
133
+ a = nx.attribute_mixing_matrix(self.G, "fish", mapping=mapping)
134
+ np.testing.assert_equal(a, a_result / a_result.sum())
135
+
136
+ def test_attribute_mixing_matrix_directed(self):
137
+ mapping = {"one": 0, "two": 1, "red": 2, "blue": 3}
138
+ a_result = np.array([[1, 0, 1, 0], [0, 1, 0, 1], [0, 0, 0, 0], [0, 0, 0, 0]])
139
+ a = nx.attribute_mixing_matrix(
140
+ self.D, "fish", mapping=mapping, normalized=False
141
+ )
142
+ np.testing.assert_equal(a, a_result)
143
+ a = nx.attribute_mixing_matrix(self.D, "fish", mapping=mapping)
144
+ np.testing.assert_equal(a, a_result / a_result.sum())
145
+
146
+ def test_attribute_mixing_matrix_multigraph(self):
147
+ mapping = {"one": 0, "two": 1, "red": 2, "blue": 3}
148
+ a_result = np.array([[4, 0, 0, 0], [0, 2, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])
149
+ a = nx.attribute_mixing_matrix(
150
+ self.M, "fish", mapping=mapping, normalized=False
151
+ )
152
+ np.testing.assert_equal(a, a_result)
153
+ a = nx.attribute_mixing_matrix(self.M, "fish", mapping=mapping)
154
+ np.testing.assert_equal(a, a_result / a_result.sum())
155
+
156
+ def test_attribute_mixing_matrix_negative(self):
157
+ mapping = {-2: 0, -3: 1, -4: 2}
158
+ a_result = np.array([[4.0, 1.0, 1.0], [1.0, 0.0, 0.0], [1.0, 0.0, 0.0]])
159
+ a = nx.attribute_mixing_matrix(
160
+ self.N, "margin", mapping=mapping, normalized=False
161
+ )
162
+ np.testing.assert_equal(a, a_result)
163
+ a = nx.attribute_mixing_matrix(self.N, "margin", mapping=mapping)
164
+ np.testing.assert_equal(a, a_result / float(a_result.sum()))
165
+
166
+ def test_attribute_mixing_matrix_float(self):
167
+ mapping = {0.5: 1, 1.5: 0}
168
+ a_result = np.array([[6.0, 1.0], [1.0, 0.0]])
169
+ a = nx.attribute_mixing_matrix(
170
+ self.F, "margin", mapping=mapping, normalized=False
171
+ )
172
+ np.testing.assert_equal(a, a_result)
173
+ a = nx.attribute_mixing_matrix(self.F, "margin", mapping=mapping)
174
+ np.testing.assert_equal(a, a_result / a_result.sum())
lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_neighbor_degree.py ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import networkx as nx
4
+
5
+
6
+ class TestAverageNeighbor:
7
+ def test_degree_p4(self):
8
+ G = nx.path_graph(4)
9
+ answer = {0: 2, 1: 1.5, 2: 1.5, 3: 2}
10
+ nd = nx.average_neighbor_degree(G)
11
+ assert nd == answer
12
+
13
+ D = G.to_directed()
14
+ nd = nx.average_neighbor_degree(D)
15
+ assert nd == answer
16
+
17
+ D = nx.DiGraph(G.edges(data=True))
18
+ nd = nx.average_neighbor_degree(D)
19
+ assert nd == {0: 1, 1: 1, 2: 0, 3: 0}
20
+ nd = nx.average_neighbor_degree(D, "in", "out")
21
+ assert nd == {0: 0, 1: 1, 2: 1, 3: 1}
22
+ nd = nx.average_neighbor_degree(D, "out", "in")
23
+ assert nd == {0: 1, 1: 1, 2: 1, 3: 0}
24
+ nd = nx.average_neighbor_degree(D, "in", "in")
25
+ assert nd == {0: 0, 1: 0, 2: 1, 3: 1}
26
+
27
+ def test_degree_p4_weighted(self):
28
+ G = nx.path_graph(4)
29
+ G[1][2]["weight"] = 4
30
+ answer = {0: 2, 1: 1.8, 2: 1.8, 3: 2}
31
+ nd = nx.average_neighbor_degree(G, weight="weight")
32
+ assert nd == answer
33
+
34
+ D = G.to_directed()
35
+ nd = nx.average_neighbor_degree(D, weight="weight")
36
+ assert nd == answer
37
+
38
+ D = nx.DiGraph(G.edges(data=True))
39
+ nd = nx.average_neighbor_degree(D, weight="weight")
40
+ assert nd == {0: 1, 1: 1, 2: 0, 3: 0}
41
+ nd = nx.average_neighbor_degree(D, "out", "out", weight="weight")
42
+ assert nd == {0: 1, 1: 1, 2: 0, 3: 0}
43
+ nd = nx.average_neighbor_degree(D, "in", "in", weight="weight")
44
+ assert nd == {0: 0, 1: 0, 2: 1, 3: 1}
45
+ nd = nx.average_neighbor_degree(D, "in", "out", weight="weight")
46
+ assert nd == {0: 0, 1: 1, 2: 1, 3: 1}
47
+ nd = nx.average_neighbor_degree(D, "out", "in", weight="weight")
48
+ assert nd == {0: 1, 1: 1, 2: 1, 3: 0}
49
+ nd = nx.average_neighbor_degree(D, source="in+out", weight="weight")
50
+ assert nd == {0: 1.0, 1: 1.0, 2: 0.8, 3: 1.0}
51
+ nd = nx.average_neighbor_degree(D, target="in+out", weight="weight")
52
+ assert nd == {0: 2.0, 1: 2.0, 2: 1.0, 3: 0.0}
53
+
54
+ D = G.to_directed()
55
+ nd = nx.average_neighbor_degree(D, weight="weight")
56
+ assert nd == answer
57
+ nd = nx.average_neighbor_degree(D, source="out", target="out", weight="weight")
58
+ assert nd == answer
59
+
60
+ D = G.to_directed()
61
+ nd = nx.average_neighbor_degree(D, source="in", target="in", weight="weight")
62
+ assert nd == answer
63
+
64
+ def test_degree_k4(self):
65
+ G = nx.complete_graph(4)
66
+ answer = {0: 3, 1: 3, 2: 3, 3: 3}
67
+ nd = nx.average_neighbor_degree(G)
68
+ assert nd == answer
69
+
70
+ D = G.to_directed()
71
+ nd = nx.average_neighbor_degree(D)
72
+ assert nd == answer
73
+
74
+ D = G.to_directed()
75
+ nd = nx.average_neighbor_degree(D)
76
+ assert nd == answer
77
+
78
+ D = G.to_directed()
79
+ nd = nx.average_neighbor_degree(D, source="in", target="in")
80
+ assert nd == answer
81
+
82
+ def test_degree_k4_nodes(self):
83
+ G = nx.complete_graph(4)
84
+ answer = {1: 3.0, 2: 3.0}
85
+ nd = nx.average_neighbor_degree(G, nodes=[1, 2])
86
+ assert nd == answer
87
+
88
+ def test_degree_barrat(self):
89
+ G = nx.star_graph(5)
90
+ G.add_edges_from([(5, 6), (5, 7), (5, 8), (5, 9)])
91
+ G[0][5]["weight"] = 5
92
+ nd = nx.average_neighbor_degree(G)[5]
93
+ assert nd == 1.8
94
+ nd = nx.average_neighbor_degree(G, weight="weight")[5]
95
+ assert nd == pytest.approx(3.222222, abs=1e-5)
96
+
97
+ def test_error_invalid_source_target(self):
98
+ G = nx.path_graph(4)
99
+ with pytest.raises(nx.NetworkXError):
100
+ nx.average_neighbor_degree(G, "error")
101
+ with pytest.raises(nx.NetworkXError):
102
+ nx.average_neighbor_degree(G, "in", "error")
103
+ G = G.to_directed()
104
+ with pytest.raises(nx.NetworkXError):
105
+ nx.average_neighbor_degree(G, "error")
106
+ with pytest.raises(nx.NetworkXError):
107
+ nx.average_neighbor_degree(G, "in", "error")
lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_pairs.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import networkx as nx
2
+
3
+ from .base_test import BaseTestAttributeMixing, BaseTestDegreeMixing
4
+
5
+
6
+ class TestAttributeMixingXY(BaseTestAttributeMixing):
7
+ def test_node_attribute_xy_undirected(self):
8
+ attrxy = sorted(nx.node_attribute_xy(self.G, "fish"))
9
+ attrxy_result = sorted(
10
+ [
11
+ ("one", "one"),
12
+ ("one", "one"),
13
+ ("two", "two"),
14
+ ("two", "two"),
15
+ ("one", "red"),
16
+ ("red", "one"),
17
+ ("blue", "two"),
18
+ ("two", "blue"),
19
+ ]
20
+ )
21
+ assert attrxy == attrxy_result
22
+
23
+ def test_node_attribute_xy_undirected_nodes(self):
24
+ attrxy = sorted(nx.node_attribute_xy(self.G, "fish", nodes=["one", "yellow"]))
25
+ attrxy_result = sorted([])
26
+ assert attrxy == attrxy_result
27
+
28
+ def test_node_attribute_xy_directed(self):
29
+ attrxy = sorted(nx.node_attribute_xy(self.D, "fish"))
30
+ attrxy_result = sorted(
31
+ [("one", "one"), ("two", "two"), ("one", "red"), ("two", "blue")]
32
+ )
33
+ assert attrxy == attrxy_result
34
+
35
+ def test_node_attribute_xy_multigraph(self):
36
+ attrxy = sorted(nx.node_attribute_xy(self.M, "fish"))
37
+ attrxy_result = [
38
+ ("one", "one"),
39
+ ("one", "one"),
40
+ ("one", "one"),
41
+ ("one", "one"),
42
+ ("two", "two"),
43
+ ("two", "two"),
44
+ ]
45
+ assert attrxy == attrxy_result
46
+
47
+ def test_node_attribute_xy_selfloop(self):
48
+ attrxy = sorted(nx.node_attribute_xy(self.S, "fish"))
49
+ attrxy_result = [("one", "one"), ("two", "two")]
50
+ assert attrxy == attrxy_result
51
+
52
+
53
+ class TestDegreeMixingXY(BaseTestDegreeMixing):
54
+ def test_node_degree_xy_undirected(self):
55
+ xy = sorted(nx.node_degree_xy(self.P4))
56
+ xy_result = sorted([(1, 2), (2, 1), (2, 2), (2, 2), (1, 2), (2, 1)])
57
+ assert xy == xy_result
58
+
59
+ def test_node_degree_xy_undirected_nodes(self):
60
+ xy = sorted(nx.node_degree_xy(self.P4, nodes=[0, 1, -1]))
61
+ xy_result = sorted([(1, 2), (2, 1)])
62
+ assert xy == xy_result
63
+
64
+ def test_node_degree_xy_directed(self):
65
+ xy = sorted(nx.node_degree_xy(self.D))
66
+ xy_result = sorted([(2, 1), (2, 3), (1, 3), (1, 3)])
67
+ assert xy == xy_result
68
+
69
+ def test_node_degree_xy_multigraph(self):
70
+ xy = sorted(nx.node_degree_xy(self.M))
71
+ xy_result = sorted(
72
+ [(2, 3), (2, 3), (3, 2), (3, 2), (2, 3), (3, 2), (1, 2), (2, 1)]
73
+ )
74
+ assert xy == xy_result
75
+
76
+ def test_node_degree_xy_selfloop(self):
77
+ xy = sorted(nx.node_degree_xy(self.S))
78
+ xy_result = sorted([(2, 2), (2, 2)])
79
+ assert xy == xy_result
80
+
81
+ def test_node_degree_xy_weighted(self):
82
+ G = nx.Graph()
83
+ G.add_edge(1, 2, weight=7)
84
+ G.add_edge(2, 3, weight=10)
85
+ xy = sorted(nx.node_degree_xy(G, weight="weight"))
86
+ xy_result = sorted([(7, 17), (17, 10), (17, 7), (10, 17)])
87
+ assert xy == xy_result
lib/python3.12/site-packages/networkx/algorithms/asteroidal.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Algorithms for asteroidal triples and asteroidal numbers in graphs.
3
+
4
+ An asteroidal triple in a graph G is a set of three non-adjacent vertices
5
+ u, v and w such that there exist a path between any two of them that avoids
6
+ closed neighborhood of the third. More formally, v_j, v_k belongs to the same
7
+ connected component of G - N[v_i], where N[v_i] denotes the closed neighborhood
8
+ of v_i. A graph which does not contain any asteroidal triples is called
9
+ an AT-free graph. The class of AT-free graphs is a graph class for which
10
+ many NP-complete problems are solvable in polynomial time. Amongst them,
11
+ independent set and coloring.
12
+ """
13
+
14
+ import networkx as nx
15
+ from networkx.utils import not_implemented_for
16
+
17
+ __all__ = ["is_at_free", "find_asteroidal_triple"]
18
+
19
+
20
+ @not_implemented_for("directed")
21
+ @not_implemented_for("multigraph")
22
+ @nx._dispatchable
23
+ def find_asteroidal_triple(G):
24
+ r"""Find an asteroidal triple in the given graph.
25
+
26
+ An asteroidal triple is a triple of non-adjacent vertices such that
27
+ there exists a path between any two of them which avoids the closed
28
+ neighborhood of the third. It checks all independent triples of vertices
29
+ and whether they are an asteroidal triple or not. This is done with the
30
+ help of a data structure called a component structure.
31
+ A component structure encodes information about which vertices belongs to
32
+ the same connected component when the closed neighborhood of a given vertex
33
+ is removed from the graph. The algorithm used to check is the trivial
34
+ one, outlined in [1]_, which has a runtime of
35
+ :math:`O(|V||\overline{E} + |V||E|)`, where the second term is the
36
+ creation of the component structure.
37
+
38
+ Parameters
39
+ ----------
40
+ G : NetworkX Graph
41
+ The graph to check whether is AT-free or not
42
+
43
+ Returns
44
+ -------
45
+ list or None
46
+ An asteroidal triple is returned as a list of nodes. If no asteroidal
47
+ triple exists, i.e. the graph is AT-free, then None is returned.
48
+
49
+ Notes
50
+ -----
51
+ The component structure and the algorithm is described in [1]_. The current
52
+ implementation implements the trivial algorithm for simple graphs.
53
+
54
+ References
55
+ ----------
56
+ .. [1] Ekkehard Köhler,
57
+ "Recognizing Graphs without asteroidal triples",
58
+ Journal of Discrete Algorithms 2, pages 439-452, 2004.
59
+ https://www.sciencedirect.com/science/article/pii/S157086670400019X
60
+ """
61
+ V = set(G.nodes)
62
+
63
+ if len(V) < 6:
64
+ # An asteroidal triple cannot exist in a graph with 5 or less vertices.
65
+ return None
66
+
67
+ component_structure = create_component_structure(G)
68
+
69
+ for u, v in nx.non_edges(G):
70
+ u_neighborhood = set(G[u]).union([u])
71
+ v_neighborhood = set(G[v]).union([v])
72
+ union_of_neighborhoods = u_neighborhood.union(v_neighborhood)
73
+ for w in V - union_of_neighborhoods:
74
+ # Check for each pair of vertices whether they belong to the
75
+ # same connected component when the closed neighborhood of the
76
+ # third is removed.
77
+ if (
78
+ component_structure[u][v] == component_structure[u][w]
79
+ and component_structure[v][u] == component_structure[v][w]
80
+ and component_structure[w][u] == component_structure[w][v]
81
+ ):
82
+ return [u, v, w]
83
+ return None
84
+
85
+
86
+ @not_implemented_for("directed")
87
+ @not_implemented_for("multigraph")
88
+ @nx._dispatchable
89
+ def is_at_free(G):
90
+ """Check if a graph is AT-free.
91
+
92
+ The method uses the `find_asteroidal_triple` method to recognize
93
+ an AT-free graph. If no asteroidal triple is found the graph is
94
+ AT-free and True is returned. If at least one asteroidal triple is
95
+ found the graph is not AT-free and False is returned.
96
+
97
+ Parameters
98
+ ----------
99
+ G : NetworkX Graph
100
+ The graph to check whether is AT-free or not.
101
+
102
+ Returns
103
+ -------
104
+ bool
105
+ True if G is AT-free and False otherwise.
106
+
107
+ Examples
108
+ --------
109
+ >>> G = nx.Graph([(0, 1), (0, 2), (1, 2), (1, 3), (1, 4), (4, 5)])
110
+ >>> nx.is_at_free(G)
111
+ True
112
+
113
+ >>> G = nx.cycle_graph(6)
114
+ >>> nx.is_at_free(G)
115
+ False
116
+ """
117
+ return find_asteroidal_triple(G) is None
118
+
119
+
120
+ @not_implemented_for("directed")
121
+ @not_implemented_for("multigraph")
122
+ @nx._dispatchable
123
+ def create_component_structure(G):
124
+ r"""Create component structure for G.
125
+
126
+ A *component structure* is an `nxn` array, denoted `c`, where `n` is
127
+ the number of vertices, where each row and column corresponds to a vertex.
128
+
129
+ .. math::
130
+ c_{uv} = \begin{cases} 0, if v \in N[u] \\
131
+ k, if v \in component k of G \setminus N[u] \end{cases}
132
+
133
+ Where `k` is an arbitrary label for each component. The structure is used
134
+ to simplify the detection of asteroidal triples.
135
+
136
+ Parameters
137
+ ----------
138
+ G : NetworkX Graph
139
+ Undirected, simple graph.
140
+
141
+ Returns
142
+ -------
143
+ component_structure : dictionary
144
+ A dictionary of dictionaries, keyed by pairs of vertices.
145
+
146
+ """
147
+ V = set(G.nodes)
148
+ component_structure = {}
149
+ for v in V:
150
+ label = 0
151
+ closed_neighborhood = set(G[v]).union({v})
152
+ row_dict = {}
153
+ for u in closed_neighborhood:
154
+ row_dict[u] = 0
155
+
156
+ G_reduced = G.subgraph(set(G.nodes) - closed_neighborhood)
157
+ for cc in nx.connected_components(G_reduced):
158
+ label += 1
159
+ for u in cc:
160
+ row_dict[u] = label
161
+
162
+ component_structure[v] = row_dict
163
+
164
+ return component_structure
lib/python3.12/site-packages/networkx/algorithms/boundary.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Routines to find the boundary of a set of nodes.
2
+
3
+ An edge boundary is a set of edges, each of which has exactly one
4
+ endpoint in a given set of nodes (or, in the case of directed graphs,
5
+ the set of edges whose source node is in the set).
6
+
7
+ A node boundary of a set *S* of nodes is the set of (out-)neighbors of
8
+ nodes in *S* that are outside *S*.
9
+
10
+ """
11
+
12
+ from itertools import chain
13
+
14
+ import networkx as nx
15
+
16
+ __all__ = ["edge_boundary", "node_boundary"]
17
+
18
+
19
+ @nx._dispatchable(edge_attrs={"data": "default"}, preserve_edge_attrs="data")
20
+ def edge_boundary(G, nbunch1, nbunch2=None, data=False, keys=False, default=None):
21
+ """Returns the edge boundary of `nbunch1`.
22
+
23
+ The *edge boundary* of a set *S* with respect to a set *T* is the
24
+ set of edges (*u*, *v*) such that *u* is in *S* and *v* is in *T*.
25
+ If *T* is not specified, it is assumed to be the set of all nodes
26
+ not in *S*.
27
+
28
+ Parameters
29
+ ----------
30
+ G : NetworkX graph
31
+
32
+ nbunch1 : iterable
33
+ Iterable of nodes in the graph representing the set of nodes
34
+ whose edge boundary will be returned. (This is the set *S* from
35
+ the definition above.)
36
+
37
+ nbunch2 : iterable
38
+ Iterable of nodes representing the target (or "exterior") set of
39
+ nodes. (This is the set *T* from the definition above.) If not
40
+ specified, this is assumed to be the set of all nodes in `G`
41
+ not in `nbunch1`.
42
+
43
+ keys : bool
44
+ This parameter has the same meaning as in
45
+ :meth:`MultiGraph.edges`.
46
+
47
+ data : bool or object
48
+ This parameter has the same meaning as in
49
+ :meth:`MultiGraph.edges`.
50
+
51
+ default : object
52
+ This parameter has the same meaning as in
53
+ :meth:`MultiGraph.edges`.
54
+
55
+ Returns
56
+ -------
57
+ iterator
58
+ An iterator over the edges in the boundary of `nbunch1` with
59
+ respect to `nbunch2`. If `keys`, `data`, or `default`
60
+ are specified and `G` is a multigraph, then edges are returned
61
+ with keys and/or data, as in :meth:`MultiGraph.edges`.
62
+
63
+ Examples
64
+ --------
65
+ >>> G = nx.wheel_graph(6)
66
+
67
+ When nbunch2=None:
68
+
69
+ >>> list(nx.edge_boundary(G, (1, 3)))
70
+ [(1, 0), (1, 2), (1, 5), (3, 0), (3, 2), (3, 4)]
71
+
72
+ When nbunch2 is given:
73
+
74
+ >>> list(nx.edge_boundary(G, (1, 3), (2, 0)))
75
+ [(1, 0), (1, 2), (3, 0), (3, 2)]
76
+
77
+ Notes
78
+ -----
79
+ Any element of `nbunch` that is not in the graph `G` will be
80
+ ignored.
81
+
82
+ `nbunch1` and `nbunch2` are usually meant to be disjoint, but in
83
+ the interest of speed and generality, that is not required here.
84
+
85
+ """
86
+ nset1 = {n for n in nbunch1 if n in G}
87
+ # Here we create an iterator over edges incident to nodes in the set
88
+ # `nset1`. The `Graph.edges()` method does not provide a guarantee
89
+ # on the orientation of the edges, so our algorithm below must
90
+ # handle the case in which exactly one orientation, either (u, v) or
91
+ # (v, u), appears in this iterable.
92
+ if G.is_multigraph():
93
+ edges = G.edges(nset1, data=data, keys=keys, default=default)
94
+ else:
95
+ edges = G.edges(nset1, data=data, default=default)
96
+ # If `nbunch2` is not provided, then it is assumed to be the set
97
+ # complement of `nbunch1`. For the sake of efficiency, this is
98
+ # implemented by using the `not in` operator, instead of by creating
99
+ # an additional set and using the `in` operator.
100
+ if nbunch2 is None:
101
+ return (e for e in edges if (e[0] in nset1) ^ (e[1] in nset1))
102
+ nset2 = set(nbunch2)
103
+ return (
104
+ e
105
+ for e in edges
106
+ if (e[0] in nset1 and e[1] in nset2) or (e[1] in nset1 and e[0] in nset2)
107
+ )
108
+
109
+
110
+ @nx._dispatchable
111
+ def node_boundary(G, nbunch1, nbunch2=None):
112
+ """Returns the node boundary of `nbunch1`.
113
+
114
+ The *node boundary* of a set *S* with respect to a set *T* is the
115
+ set of nodes *v* in *T* such that for some *u* in *S*, there is an
116
+ edge joining *u* to *v*. If *T* is not specified, it is assumed to
117
+ be the set of all nodes not in *S*.
118
+
119
+ Parameters
120
+ ----------
121
+ G : NetworkX graph
122
+
123
+ nbunch1 : iterable
124
+ Iterable of nodes in the graph representing the set of nodes
125
+ whose node boundary will be returned. (This is the set *S* from
126
+ the definition above.)
127
+
128
+ nbunch2 : iterable
129
+ Iterable of nodes representing the target (or "exterior") set of
130
+ nodes. (This is the set *T* from the definition above.) If not
131
+ specified, this is assumed to be the set of all nodes in `G`
132
+ not in `nbunch1`.
133
+
134
+ Returns
135
+ -------
136
+ set
137
+ The node boundary of `nbunch1` with respect to `nbunch2`.
138
+
139
+ Examples
140
+ --------
141
+ >>> G = nx.wheel_graph(6)
142
+
143
+ When nbunch2=None:
144
+
145
+ >>> list(nx.node_boundary(G, (3, 4)))
146
+ [0, 2, 5]
147
+
148
+ When nbunch2 is given:
149
+
150
+ >>> list(nx.node_boundary(G, (3, 4), (0, 1, 5)))
151
+ [0, 5]
152
+
153
+ Notes
154
+ -----
155
+ Any element of `nbunch` that is not in the graph `G` will be
156
+ ignored.
157
+
158
+ `nbunch1` and `nbunch2` are usually meant to be disjoint, but in
159
+ the interest of speed and generality, that is not required here.
160
+
161
+ """
162
+ nset1 = {n for n in nbunch1 if n in G}
163
+ bdy = set(chain.from_iterable(G[v] for v in nset1)) - nset1
164
+ # If `nbunch2` is not specified, it is assumed to be the set
165
+ # complement of `nbunch1`.
166
+ if nbunch2 is not None:
167
+ bdy &= set(nbunch2)
168
+ return bdy
lib/python3.12/site-packages/networkx/algorithms/bridges.py ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Bridge-finding algorithms."""
2
+
3
+ from itertools import chain
4
+
5
+ import networkx as nx
6
+ from networkx.utils import not_implemented_for
7
+
8
+ __all__ = ["bridges", "has_bridges", "local_bridges"]
9
+
10
+
11
+ @not_implemented_for("directed")
12
+ @nx._dispatchable
13
+ def bridges(G, root=None):
14
+ """Generate all bridges in a graph.
15
+
16
+ A *bridge* in a graph is an edge whose removal causes the number of
17
+ connected components of the graph to increase. Equivalently, a bridge is an
18
+ edge that does not belong to any cycle. Bridges are also known as cut-edges,
19
+ isthmuses, or cut arcs.
20
+
21
+ Parameters
22
+ ----------
23
+ G : undirected graph
24
+
25
+ root : node (optional)
26
+ A node in the graph `G`. If specified, only the bridges in the
27
+ connected component containing this node will be returned.
28
+
29
+ Yields
30
+ ------
31
+ e : edge
32
+ An edge in the graph whose removal disconnects the graph (or
33
+ causes the number of connected components to increase).
34
+
35
+ Raises
36
+ ------
37
+ NodeNotFound
38
+ If `root` is not in the graph `G`.
39
+
40
+ NetworkXNotImplemented
41
+ If `G` is a directed graph.
42
+
43
+ Examples
44
+ --------
45
+ The barbell graph with parameter zero has a single bridge:
46
+
47
+ >>> G = nx.barbell_graph(10, 0)
48
+ >>> list(nx.bridges(G))
49
+ [(9, 10)]
50
+
51
+ Notes
52
+ -----
53
+ This is an implementation of the algorithm described in [1]_. An edge is a
54
+ bridge if and only if it is not contained in any chain. Chains are found
55
+ using the :func:`networkx.chain_decomposition` function.
56
+
57
+ The algorithm described in [1]_ requires a simple graph. If the provided
58
+ graph is a multigraph, we convert it to a simple graph and verify that any
59
+ bridges discovered by the chain decomposition algorithm are not multi-edges.
60
+
61
+ Ignoring polylogarithmic factors, the worst-case time complexity is the
62
+ same as the :func:`networkx.chain_decomposition` function,
63
+ $O(m + n)$, where $n$ is the number of nodes in the graph and $m$ is
64
+ the number of edges.
65
+
66
+ References
67
+ ----------
68
+ .. [1] https://en.wikipedia.org/wiki/Bridge_%28graph_theory%29#Bridge-Finding_with_Chain_Decompositions
69
+ """
70
+ multigraph = G.is_multigraph()
71
+ H = nx.Graph(G) if multigraph else G
72
+ chains = nx.chain_decomposition(H, root=root)
73
+ chain_edges = set(chain.from_iterable(chains))
74
+ if root is not None:
75
+ H = H.subgraph(nx.node_connected_component(H, root)).copy()
76
+ for u, v in H.edges():
77
+ if (u, v) not in chain_edges and (v, u) not in chain_edges:
78
+ if multigraph and len(G[u][v]) > 1:
79
+ continue
80
+ yield u, v
81
+
82
+
83
+ @not_implemented_for("directed")
84
+ @nx._dispatchable
85
+ def has_bridges(G, root=None):
86
+ """Decide whether a graph has any bridges.
87
+
88
+ A *bridge* in a graph is an edge whose removal causes the number of
89
+ connected components of the graph to increase.
90
+
91
+ Parameters
92
+ ----------
93
+ G : undirected graph
94
+
95
+ root : node (optional)
96
+ A node in the graph `G`. If specified, only the bridges in the
97
+ connected component containing this node will be considered.
98
+
99
+ Returns
100
+ -------
101
+ bool
102
+ Whether the graph (or the connected component containing `root`)
103
+ has any bridges.
104
+
105
+ Raises
106
+ ------
107
+ NodeNotFound
108
+ If `root` is not in the graph `G`.
109
+
110
+ NetworkXNotImplemented
111
+ If `G` is a directed graph.
112
+
113
+ Examples
114
+ --------
115
+ The barbell graph with parameter zero has a single bridge::
116
+
117
+ >>> G = nx.barbell_graph(10, 0)
118
+ >>> nx.has_bridges(G)
119
+ True
120
+
121
+ On the other hand, the cycle graph has no bridges::
122
+
123
+ >>> G = nx.cycle_graph(5)
124
+ >>> nx.has_bridges(G)
125
+ False
126
+
127
+ Notes
128
+ -----
129
+ This implementation uses the :func:`networkx.bridges` function, so
130
+ it shares its worst-case time complexity, $O(m + n)$, ignoring
131
+ polylogarithmic factors, where $n$ is the number of nodes in the
132
+ graph and $m$ is the number of edges.
133
+
134
+ """
135
+ try:
136
+ next(bridges(G, root=root))
137
+ except StopIteration:
138
+ return False
139
+ else:
140
+ return True
141
+
142
+
143
+ @not_implemented_for("multigraph")
144
+ @not_implemented_for("directed")
145
+ @nx._dispatchable(edge_attrs="weight")
146
+ def local_bridges(G, with_span=True, weight=None):
147
+ """Iterate over local bridges of `G` optionally computing the span
148
+
149
+ A *local bridge* is an edge whose endpoints have no common neighbors.
150
+ That is, the edge is not part of a triangle in the graph.
151
+
152
+ The *span* of a *local bridge* is the shortest path length between
153
+ the endpoints if the local bridge is removed.
154
+
155
+ Parameters
156
+ ----------
157
+ G : undirected graph
158
+
159
+ with_span : bool
160
+ If True, yield a 3-tuple `(u, v, span)`
161
+
162
+ weight : function, string or None (default: None)
163
+ If function, used to compute edge weights for the span.
164
+ If string, the edge data attribute used in calculating span.
165
+ If None, all edges have weight 1.
166
+
167
+ Yields
168
+ ------
169
+ e : edge
170
+ The local bridges as an edge 2-tuple of nodes `(u, v)` or
171
+ as a 3-tuple `(u, v, span)` when `with_span is True`.
172
+
173
+ Raises
174
+ ------
175
+ NetworkXNotImplemented
176
+ If `G` is a directed graph or multigraph.
177
+
178
+ Examples
179
+ --------
180
+ A cycle graph has every edge a local bridge with span N-1.
181
+
182
+ >>> G = nx.cycle_graph(9)
183
+ >>> (0, 8, 8) in set(nx.local_bridges(G))
184
+ True
185
+ """
186
+ if with_span is not True:
187
+ for u, v in G.edges:
188
+ if not (set(G[u]) & set(G[v])):
189
+ yield u, v
190
+ else:
191
+ wt = nx.weighted._weight_function(G, weight)
192
+ for u, v in G.edges:
193
+ if not (set(G[u]) & set(G[v])):
194
+ enodes = {u, v}
195
+
196
+ def hide_edge(n, nbr, d):
197
+ if n not in enodes or nbr not in enodes:
198
+ return wt(n, nbr, d)
199
+ return None
200
+
201
+ try:
202
+ span = nx.shortest_path_length(G, u, v, weight=hide_edge)
203
+ yield u, v, span
204
+ except nx.NetworkXNoPath:
205
+ yield u, v, float("inf")
lib/python3.12/site-packages/networkx/algorithms/broadcasting.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Routines to calculate the broadcast time of certain graphs.
2
+
3
+ Broadcasting is an information dissemination problem in which a node in a graph,
4
+ called the originator, must distribute a message to all other nodes by placing
5
+ a series of calls along the edges of the graph. Once informed, other nodes aid
6
+ the originator in distributing the message.
7
+
8
+ The broadcasting must be completed as quickly as possible subject to the
9
+ following constraints:
10
+ - Each call requires one unit of time.
11
+ - A node can only participate in one call per unit of time.
12
+ - Each call only involves two adjacent nodes: a sender and a receiver.
13
+ """
14
+
15
+ import networkx as nx
16
+ from networkx.utils import not_implemented_for
17
+
18
+ __all__ = [
19
+ "tree_broadcast_center",
20
+ "tree_broadcast_time",
21
+ ]
22
+
23
+
24
+ def _get_max_broadcast_value(G, U, v, values):
25
+ adj = sorted(set(G.neighbors(v)) & U, key=values.get, reverse=True)
26
+ return max(values[u] + i for i, u in enumerate(adj, start=1))
27
+
28
+
29
+ def _get_broadcast_centers(G, v, values, target):
30
+ adj = sorted(G.neighbors(v), key=values.get, reverse=True)
31
+ j = next(i for i, u in enumerate(adj, start=1) if values[u] + i == target)
32
+ return set([v] + adj[:j])
33
+
34
+
35
+ @not_implemented_for("directed")
36
+ @not_implemented_for("multigraph")
37
+ @nx._dispatchable
38
+ def tree_broadcast_center(G):
39
+ """Return the broadcast center of a tree.
40
+
41
+ The broadcast center of a graph `G` denotes the set of nodes having
42
+ minimum broadcast time [1]_. This function implements a linear algorithm
43
+ for determining the broadcast center of a tree with ``n`` nodes. As a
44
+ by-product, it also determines the broadcast time from the broadcast center.
45
+
46
+ Parameters
47
+ ----------
48
+ G : Graph
49
+ The graph should be an undirected tree.
50
+
51
+ Returns
52
+ -------
53
+ b_T, b_C : (int, set) tuple
54
+ Minimum broadcast time of the broadcast center in `G`, set of nodes
55
+ in the broadcast center.
56
+
57
+ Raises
58
+ ------
59
+ NetworkXNotImplemented
60
+ If `G` is directed or is a multigraph.
61
+
62
+ NotATree
63
+ If `G` is not a tree.
64
+
65
+ References
66
+ ----------
67
+ .. [1] Slater, P.J., Cockayne, E.J., Hedetniemi, S.T,
68
+ Information dissemination in trees. SIAM J.Comput. 10(4), 692–701 (1981)
69
+ """
70
+ # Assert that the graph G is a tree
71
+ if not nx.is_tree(G):
72
+ raise nx.NotATree("G is not a tree")
73
+ # step 0
74
+ if (n := len(G)) < 3:
75
+ return n - 1, set(G)
76
+
77
+ # step 1
78
+ U = {node for node, deg in G.degree if deg == 1}
79
+ values = {n: 0 for n in U}
80
+ T = G.copy()
81
+ T.remove_nodes_from(U)
82
+
83
+ # step 2
84
+ W = {node for node, deg in T.degree if deg == 1}
85
+ values.update((w, G.degree[w] - 1) for w in W)
86
+
87
+ # step 3
88
+ while len(T) >= 2:
89
+ # step 4
90
+ w = min(W, key=values.get)
91
+ v = next(T.neighbors(w))
92
+
93
+ # step 5
94
+ U.add(w)
95
+ W.remove(w)
96
+ T.remove_node(w)
97
+
98
+ # step 6
99
+ if T.degree(v) == 1:
100
+ # update t(v)
101
+ values.update({v: _get_max_broadcast_value(G, U, v, values)})
102
+ W.add(v)
103
+
104
+ # step 7
105
+ v = nx.utils.arbitrary_element(T)
106
+ b_T = _get_max_broadcast_value(G, U, v, values)
107
+ return b_T, _get_broadcast_centers(G, v, values, b_T)
108
+
109
+
110
+ @not_implemented_for("directed")
111
+ @not_implemented_for("multigraph")
112
+ @nx._dispatchable
113
+ def tree_broadcast_time(G, node=None):
114
+ """Return the minimum broadcast time of a (node in a) tree.
115
+
116
+ The minimum broadcast time of a node is defined as the minimum amount
117
+ of time required to complete broadcasting starting from that node.
118
+ The broadcast time of a graph is the maximum over
119
+ all nodes of the minimum broadcast time from that node [1]_.
120
+ This function returns the minimum broadcast time of `node`.
121
+ If `node` is `None`, the broadcast time for the graph is returned.
122
+
123
+ Parameters
124
+ ----------
125
+ G : Graph
126
+ The graph should be an undirected tree.
127
+
128
+ node : node, optional (default=None)
129
+ Starting node for the broadcasting. If `None`, the algorithm
130
+ returns the broadcast time of the graph instead.
131
+
132
+ Returns
133
+ -------
134
+ int
135
+ Minimum broadcast time of `node` in `G`, or broadcast time of `G`
136
+ if no node is provided.
137
+
138
+ Raises
139
+ ------
140
+ NetworkXNotImplemented
141
+ If `G` is directed or is a multigraph.
142
+
143
+ NodeNotFound
144
+ If `node` is not a node in `G`.
145
+
146
+ NotATree
147
+ If `G` is not a tree.
148
+
149
+ References
150
+ ----------
151
+ .. [1] Harutyunyan, H. A. and Li, Z.
152
+ "A Simple Construction of Broadcast Graphs."
153
+ In Computing and Combinatorics. COCOON 2019
154
+ (Ed. D. Z. Du and C. Tian.) Springer, pp. 240-253, 2019.
155
+ """
156
+ if node is not None and node not in G:
157
+ err = f"node {node} not in G"
158
+ raise nx.NodeNotFound(err)
159
+ b_T, b_C = tree_broadcast_center(G)
160
+ if node is None:
161
+ return b_T + sum(1 for _ in nx.bfs_layers(G, b_C)) - 1
162
+ return b_T + next(
163
+ d for d, layer in enumerate(nx.bfs_layers(G, b_C)) if node in layer
164
+ )
lib/python3.12/site-packages/networkx/algorithms/chains.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Functions for finding chains in a graph."""
2
+
3
+ import networkx as nx
4
+ from networkx.utils import not_implemented_for
5
+
6
+ __all__ = ["chain_decomposition"]
7
+
8
+
9
+ @not_implemented_for("directed")
10
+ @not_implemented_for("multigraph")
11
+ @nx._dispatchable
12
+ def chain_decomposition(G, root=None):
13
+ """Returns the chain decomposition of a graph.
14
+
15
+ The *chain decomposition* of a graph with respect a depth-first
16
+ search tree is a set of cycles or paths derived from the set of
17
+ fundamental cycles of the tree in the following manner. Consider
18
+ each fundamental cycle with respect to the given tree, represented
19
+ as a list of edges beginning with the nontree edge oriented away
20
+ from the root of the tree. For each fundamental cycle, if it
21
+ overlaps with any previous fundamental cycle, just take the initial
22
+ non-overlapping segment, which is a path instead of a cycle. Each
23
+ cycle or path is called a *chain*. For more information, see [1]_.
24
+
25
+ Parameters
26
+ ----------
27
+ G : undirected graph
28
+
29
+ root : node (optional)
30
+ A node in the graph `G`. If specified, only the chain
31
+ decomposition for the connected component containing this node
32
+ will be returned. This node indicates the root of the depth-first
33
+ search tree.
34
+
35
+ Yields
36
+ ------
37
+ chain : list
38
+ A list of edges representing a chain. There is no guarantee on
39
+ the orientation of the edges in each chain (for example, if a
40
+ chain includes the edge joining nodes 1 and 2, the chain may
41
+ include either (1, 2) or (2, 1)).
42
+
43
+ Raises
44
+ ------
45
+ NodeNotFound
46
+ If `root` is not in the graph `G`.
47
+
48
+ Examples
49
+ --------
50
+ >>> G = nx.Graph([(0, 1), (1, 4), (3, 4), (3, 5), (4, 5)])
51
+ >>> list(nx.chain_decomposition(G))
52
+ [[(4, 5), (5, 3), (3, 4)]]
53
+
54
+ Notes
55
+ -----
56
+ The worst-case running time of this implementation is linear in the
57
+ number of nodes and number of edges [1]_.
58
+
59
+ References
60
+ ----------
61
+ .. [1] Jens M. Schmidt (2013). "A simple test on 2-vertex-
62
+ and 2-edge-connectivity." *Information Processing Letters*,
63
+ 113, 241–244. Elsevier. <https://doi.org/10.1016/j.ipl.2013.01.016>
64
+
65
+ """
66
+
67
+ def _dfs_cycle_forest(G, root=None):
68
+ """Builds a directed graph composed of cycles from the given graph.
69
+
70
+ `G` is an undirected simple graph. `root` is a node in the graph
71
+ from which the depth-first search is started.
72
+
73
+ This function returns both the depth-first search cycle graph
74
+ (as a :class:`~networkx.DiGraph`) and the list of nodes in
75
+ depth-first preorder. The depth-first search cycle graph is a
76
+ directed graph whose edges are the edges of `G` oriented toward
77
+ the root if the edge is a tree edge and away from the root if
78
+ the edge is a non-tree edge. If `root` is not specified, this
79
+ performs a depth-first search on each connected component of `G`
80
+ and returns a directed forest instead.
81
+
82
+ If `root` is not in the graph, this raises :exc:`KeyError`.
83
+
84
+ """
85
+ # Create a directed graph from the depth-first search tree with
86
+ # root node `root` in which tree edges are directed toward the
87
+ # root and nontree edges are directed away from the root. For
88
+ # each node with an incident nontree edge, this creates a
89
+ # directed cycle starting with the nontree edge and returning to
90
+ # that node.
91
+ #
92
+ # The `parent` node attribute stores the parent of each node in
93
+ # the DFS tree. The `nontree` edge attribute indicates whether
94
+ # the edge is a tree edge or a nontree edge.
95
+ #
96
+ # We also store the order of the nodes found in the depth-first
97
+ # search in the `nodes` list.
98
+ H = nx.DiGraph()
99
+ nodes = []
100
+ for u, v, d in nx.dfs_labeled_edges(G, source=root):
101
+ if d == "forward":
102
+ # `dfs_labeled_edges()` yields (root, root, 'forward')
103
+ # if it is beginning the search on a new connected
104
+ # component.
105
+ if u == v:
106
+ H.add_node(v, parent=None)
107
+ nodes.append(v)
108
+ else:
109
+ H.add_node(v, parent=u)
110
+ H.add_edge(v, u, nontree=False)
111
+ nodes.append(v)
112
+ # `dfs_labeled_edges` considers nontree edges in both
113
+ # orientations, so we need to not add the edge if it its
114
+ # other orientation has been added.
115
+ elif d == "nontree" and v not in H[u]:
116
+ H.add_edge(v, u, nontree=True)
117
+ else:
118
+ # Do nothing on 'reverse' edges; we only care about
119
+ # forward and nontree edges.
120
+ pass
121
+ return H, nodes
122
+
123
+ def _build_chain(G, u, v, visited):
124
+ """Generate the chain starting from the given nontree edge.
125
+
126
+ `G` is a DFS cycle graph as constructed by
127
+ :func:`_dfs_cycle_graph`. The edge (`u`, `v`) is a nontree edge
128
+ that begins a chain. `visited` is a set representing the nodes
129
+ in `G` that have already been visited.
130
+
131
+ This function yields the edges in an initial segment of the
132
+ fundamental cycle of `G` starting with the nontree edge (`u`,
133
+ `v`) that includes all the edges up until the first node that
134
+ appears in `visited`. The tree edges are given by the 'parent'
135
+ node attribute. The `visited` set is updated to add each node in
136
+ an edge yielded by this function.
137
+
138
+ """
139
+ while v not in visited:
140
+ yield u, v
141
+ visited.add(v)
142
+ u, v = v, G.nodes[v]["parent"]
143
+ yield u, v
144
+
145
+ # Check if the root is in the graph G. If not, raise NodeNotFound
146
+ if root is not None and root not in G:
147
+ raise nx.NodeNotFound(f"Root node {root} is not in graph")
148
+
149
+ # Create a directed version of H that has the DFS edges directed
150
+ # toward the root and the nontree edges directed away from the root
151
+ # (in each connected component).
152
+ H, nodes = _dfs_cycle_forest(G, root)
153
+
154
+ # Visit the nodes again in DFS order. For each node, and for each
155
+ # nontree edge leaving that node, compute the fundamental cycle for
156
+ # that nontree edge starting with that edge. If the fundamental
157
+ # cycle overlaps with any visited nodes, just take the prefix of the
158
+ # cycle up to the point of visited nodes.
159
+ #
160
+ # We repeat this process for each connected component (implicitly,
161
+ # since `nodes` already has a list of the nodes grouped by connected
162
+ # component).
163
+ visited = set()
164
+ for u in nodes:
165
+ visited.add(u)
166
+ # For each nontree edge going out of node u...
167
+ edges = ((u, v) for u, v, d in H.out_edges(u, data="nontree") if d)
168
+ for u, v in edges:
169
+ # Create the cycle or cycle prefix starting with the
170
+ # nontree edge.
171
+ chain = list(_build_chain(H, u, v, visited))
172
+ yield chain
lib/python3.12/site-packages/networkx/algorithms/chordal.py ADDED
@@ -0,0 +1,443 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Algorithms for chordal graphs.
3
+
4
+ A graph is chordal if every cycle of length at least 4 has a chord
5
+ (an edge joining two nodes not adjacent in the cycle).
6
+ https://en.wikipedia.org/wiki/Chordal_graph
7
+ """
8
+
9
+ import sys
10
+
11
+ import networkx as nx
12
+ from networkx.algorithms.components import connected_components
13
+ from networkx.utils import arbitrary_element, not_implemented_for
14
+
15
+ __all__ = [
16
+ "is_chordal",
17
+ "find_induced_nodes",
18
+ "chordal_graph_cliques",
19
+ "chordal_graph_treewidth",
20
+ "NetworkXTreewidthBoundExceeded",
21
+ "complete_to_chordal_graph",
22
+ ]
23
+
24
+
25
+ class NetworkXTreewidthBoundExceeded(nx.NetworkXException):
26
+ """Exception raised when a treewidth bound has been provided and it has
27
+ been exceeded"""
28
+
29
+
30
+ @not_implemented_for("directed")
31
+ @not_implemented_for("multigraph")
32
+ @nx._dispatchable
33
+ def is_chordal(G):
34
+ """Checks whether G is a chordal graph.
35
+
36
+ A graph is chordal if every cycle of length at least 4 has a chord
37
+ (an edge joining two nodes not adjacent in the cycle).
38
+
39
+ Parameters
40
+ ----------
41
+ G : graph
42
+ A NetworkX graph.
43
+
44
+ Returns
45
+ -------
46
+ chordal : bool
47
+ True if G is a chordal graph and False otherwise.
48
+
49
+ Raises
50
+ ------
51
+ NetworkXNotImplemented
52
+ The algorithm does not support DiGraph, MultiGraph and MultiDiGraph.
53
+
54
+ Examples
55
+ --------
56
+ >>> e = [
57
+ ... (1, 2),
58
+ ... (1, 3),
59
+ ... (2, 3),
60
+ ... (2, 4),
61
+ ... (3, 4),
62
+ ... (3, 5),
63
+ ... (3, 6),
64
+ ... (4, 5),
65
+ ... (4, 6),
66
+ ... (5, 6),
67
+ ... ]
68
+ >>> G = nx.Graph(e)
69
+ >>> nx.is_chordal(G)
70
+ True
71
+
72
+ Notes
73
+ -----
74
+ The routine tries to go through every node following maximum cardinality
75
+ search. It returns False when it finds that the separator for any node
76
+ is not a clique. Based on the algorithms in [1]_.
77
+
78
+ Self loops are ignored.
79
+
80
+ References
81
+ ----------
82
+ .. [1] R. E. Tarjan and M. Yannakakis, Simple linear-time algorithms
83
+ to test chordality of graphs, test acyclicity of hypergraphs, and
84
+ selectively reduce acyclic hypergraphs, SIAM J. Comput., 13 (1984),
85
+ pp. 566–579.
86
+ """
87
+ if len(G.nodes) <= 3:
88
+ return True
89
+ return len(_find_chordality_breaker(G)) == 0
90
+
91
+
92
+ @nx._dispatchable
93
+ def find_induced_nodes(G, s, t, treewidth_bound=sys.maxsize):
94
+ """Returns the set of induced nodes in the path from s to t.
95
+
96
+ Parameters
97
+ ----------
98
+ G : graph
99
+ A chordal NetworkX graph
100
+ s : node
101
+ Source node to look for induced nodes
102
+ t : node
103
+ Destination node to look for induced nodes
104
+ treewidth_bound: float
105
+ Maximum treewidth acceptable for the graph H. The search
106
+ for induced nodes will end as soon as the treewidth_bound is exceeded.
107
+
108
+ Returns
109
+ -------
110
+ induced_nodes : Set of nodes
111
+ The set of induced nodes in the path from s to t in G
112
+
113
+ Raises
114
+ ------
115
+ NetworkXError
116
+ The algorithm does not support DiGraph, MultiGraph and MultiDiGraph.
117
+ If the input graph is an instance of one of these classes, a
118
+ :exc:`NetworkXError` is raised.
119
+ The algorithm can only be applied to chordal graphs. If the input
120
+ graph is found to be non-chordal, a :exc:`NetworkXError` is raised.
121
+
122
+ Examples
123
+ --------
124
+ >>> G = nx.Graph()
125
+ >>> G = nx.generators.classic.path_graph(10)
126
+ >>> induced_nodes = nx.find_induced_nodes(G, 1, 9, 2)
127
+ >>> sorted(induced_nodes)
128
+ [1, 2, 3, 4, 5, 6, 7, 8, 9]
129
+
130
+ Notes
131
+ -----
132
+ G must be a chordal graph and (s,t) an edge that is not in G.
133
+
134
+ If a treewidth_bound is provided, the search for induced nodes will end
135
+ as soon as the treewidth_bound is exceeded.
136
+
137
+ The algorithm is inspired by Algorithm 4 in [1]_.
138
+ A formal definition of induced node can also be found on that reference.
139
+
140
+ Self Loops are ignored
141
+
142
+ References
143
+ ----------
144
+ .. [1] Learning Bounded Treewidth Bayesian Networks.
145
+ Gal Elidan, Stephen Gould; JMLR, 9(Dec):2699--2731, 2008.
146
+ http://jmlr.csail.mit.edu/papers/volume9/elidan08a/elidan08a.pdf
147
+ """
148
+ if not is_chordal(G):
149
+ raise nx.NetworkXError("Input graph is not chordal.")
150
+
151
+ H = nx.Graph(G)
152
+ H.add_edge(s, t)
153
+ induced_nodes = set()
154
+ triplet = _find_chordality_breaker(H, s, treewidth_bound)
155
+ while triplet:
156
+ (u, v, w) = triplet
157
+ induced_nodes.update(triplet)
158
+ for n in triplet:
159
+ if n != s:
160
+ H.add_edge(s, n)
161
+ triplet = _find_chordality_breaker(H, s, treewidth_bound)
162
+ if induced_nodes:
163
+ # Add t and the second node in the induced path from s to t.
164
+ induced_nodes.add(t)
165
+ for u in G[s]:
166
+ if len(induced_nodes & set(G[u])) == 2:
167
+ induced_nodes.add(u)
168
+ break
169
+ return induced_nodes
170
+
171
+
172
+ @nx._dispatchable
173
+ def chordal_graph_cliques(G):
174
+ """Returns all maximal cliques of a chordal graph.
175
+
176
+ The algorithm breaks the graph in connected components and performs a
177
+ maximum cardinality search in each component to get the cliques.
178
+
179
+ Parameters
180
+ ----------
181
+ G : graph
182
+ A NetworkX graph
183
+
184
+ Yields
185
+ ------
186
+ frozenset of nodes
187
+ Maximal cliques, each of which is a frozenset of
188
+ nodes in `G`. The order of cliques is arbitrary.
189
+
190
+ Raises
191
+ ------
192
+ NetworkXError
193
+ The algorithm does not support DiGraph, MultiGraph and MultiDiGraph.
194
+ The algorithm can only be applied to chordal graphs. If the input
195
+ graph is found to be non-chordal, a :exc:`NetworkXError` is raised.
196
+
197
+ Examples
198
+ --------
199
+ >>> e = [
200
+ ... (1, 2),
201
+ ... (1, 3),
202
+ ... (2, 3),
203
+ ... (2, 4),
204
+ ... (3, 4),
205
+ ... (3, 5),
206
+ ... (3, 6),
207
+ ... (4, 5),
208
+ ... (4, 6),
209
+ ... (5, 6),
210
+ ... (7, 8),
211
+ ... ]
212
+ >>> G = nx.Graph(e)
213
+ >>> G.add_node(9)
214
+ >>> cliques = [c for c in chordal_graph_cliques(G)]
215
+ >>> cliques[0]
216
+ frozenset({1, 2, 3})
217
+ """
218
+ for C in (G.subgraph(c).copy() for c in connected_components(G)):
219
+ if C.number_of_nodes() == 1:
220
+ if nx.number_of_selfloops(C) > 0:
221
+ raise nx.NetworkXError("Input graph is not chordal.")
222
+ yield frozenset(C.nodes())
223
+ else:
224
+ unnumbered = set(C.nodes())
225
+ v = arbitrary_element(C)
226
+ unnumbered.remove(v)
227
+ numbered = {v}
228
+ clique_wanna_be = {v}
229
+ while unnumbered:
230
+ v = _max_cardinality_node(C, unnumbered, numbered)
231
+ unnumbered.remove(v)
232
+ numbered.add(v)
233
+ new_clique_wanna_be = set(C.neighbors(v)) & numbered
234
+ sg = C.subgraph(clique_wanna_be)
235
+ if _is_complete_graph(sg):
236
+ new_clique_wanna_be.add(v)
237
+ if not new_clique_wanna_be >= clique_wanna_be:
238
+ yield frozenset(clique_wanna_be)
239
+ clique_wanna_be = new_clique_wanna_be
240
+ else:
241
+ raise nx.NetworkXError("Input graph is not chordal.")
242
+ yield frozenset(clique_wanna_be)
243
+
244
+
245
+ @nx._dispatchable
246
+ def chordal_graph_treewidth(G):
247
+ """Returns the treewidth of the chordal graph G.
248
+
249
+ Parameters
250
+ ----------
251
+ G : graph
252
+ A NetworkX graph
253
+
254
+ Returns
255
+ -------
256
+ treewidth : int
257
+ The size of the largest clique in the graph minus one.
258
+
259
+ Raises
260
+ ------
261
+ NetworkXError
262
+ The algorithm does not support DiGraph, MultiGraph and MultiDiGraph.
263
+ The algorithm can only be applied to chordal graphs. If the input
264
+ graph is found to be non-chordal, a :exc:`NetworkXError` is raised.
265
+
266
+ Examples
267
+ --------
268
+ >>> e = [
269
+ ... (1, 2),
270
+ ... (1, 3),
271
+ ... (2, 3),
272
+ ... (2, 4),
273
+ ... (3, 4),
274
+ ... (3, 5),
275
+ ... (3, 6),
276
+ ... (4, 5),
277
+ ... (4, 6),
278
+ ... (5, 6),
279
+ ... (7, 8),
280
+ ... ]
281
+ >>> G = nx.Graph(e)
282
+ >>> G.add_node(9)
283
+ >>> nx.chordal_graph_treewidth(G)
284
+ 3
285
+
286
+ References
287
+ ----------
288
+ .. [1] https://en.wikipedia.org/wiki/Tree_decomposition#Treewidth
289
+ """
290
+ if not is_chordal(G):
291
+ raise nx.NetworkXError("Input graph is not chordal.")
292
+
293
+ max_clique = -1
294
+ for clique in nx.chordal_graph_cliques(G):
295
+ max_clique = max(max_clique, len(clique))
296
+ return max_clique - 1
297
+
298
+
299
+ def _is_complete_graph(G):
300
+ """Returns True if G is a complete graph."""
301
+ if nx.number_of_selfloops(G) > 0:
302
+ raise nx.NetworkXError("Self loop found in _is_complete_graph()")
303
+ n = G.number_of_nodes()
304
+ if n < 2:
305
+ return True
306
+ e = G.number_of_edges()
307
+ max_edges = (n * (n - 1)) / 2
308
+ return e == max_edges
309
+
310
+
311
+ def _find_missing_edge(G):
312
+ """Given a non-complete graph G, returns a missing edge."""
313
+ nodes = set(G)
314
+ for u in G:
315
+ missing = nodes - set(list(G[u].keys()) + [u])
316
+ if missing:
317
+ return (u, missing.pop())
318
+
319
+
320
+ def _max_cardinality_node(G, choices, wanna_connect):
321
+ """Returns a the node in choices that has more connections in G
322
+ to nodes in wanna_connect.
323
+ """
324
+ max_number = -1
325
+ for x in choices:
326
+ number = len([y for y in G[x] if y in wanna_connect])
327
+ if number > max_number:
328
+ max_number = number
329
+ max_cardinality_node = x
330
+ return max_cardinality_node
331
+
332
+
333
+ def _find_chordality_breaker(G, s=None, treewidth_bound=sys.maxsize):
334
+ """Given a graph G, starts a max cardinality search
335
+ (starting from s if s is given and from an arbitrary node otherwise)
336
+ trying to find a non-chordal cycle.
337
+
338
+ If it does find one, it returns (u,v,w) where u,v,w are the three
339
+ nodes that together with s are involved in the cycle.
340
+
341
+ It ignores any self loops.
342
+ """
343
+ if len(G) == 0:
344
+ raise nx.NetworkXPointlessConcept("Graph has no nodes.")
345
+ unnumbered = set(G)
346
+ if s is None:
347
+ s = arbitrary_element(G)
348
+ unnumbered.remove(s)
349
+ numbered = {s}
350
+ current_treewidth = -1
351
+ while unnumbered: # and current_treewidth <= treewidth_bound:
352
+ v = _max_cardinality_node(G, unnumbered, numbered)
353
+ unnumbered.remove(v)
354
+ numbered.add(v)
355
+ clique_wanna_be = set(G[v]) & numbered
356
+ sg = G.subgraph(clique_wanna_be)
357
+ if _is_complete_graph(sg):
358
+ # The graph seems to be chordal by now. We update the treewidth
359
+ current_treewidth = max(current_treewidth, len(clique_wanna_be))
360
+ if current_treewidth > treewidth_bound:
361
+ raise nx.NetworkXTreewidthBoundExceeded(
362
+ f"treewidth_bound exceeded: {current_treewidth}"
363
+ )
364
+ else:
365
+ # sg is not a clique,
366
+ # look for an edge that is not included in sg
367
+ (u, w) = _find_missing_edge(sg)
368
+ return (u, v, w)
369
+ return ()
370
+
371
+
372
+ @not_implemented_for("directed")
373
+ @nx._dispatchable(returns_graph=True)
374
+ def complete_to_chordal_graph(G):
375
+ """Return a copy of G completed to a chordal graph
376
+
377
+ Adds edges to a copy of G to create a chordal graph. A graph G=(V,E) is
378
+ called chordal if for each cycle with length bigger than 3, there exist
379
+ two non-adjacent nodes connected by an edge (called a chord).
380
+
381
+ Parameters
382
+ ----------
383
+ G : NetworkX graph
384
+ Undirected graph
385
+
386
+ Returns
387
+ -------
388
+ H : NetworkX graph
389
+ The chordal enhancement of G
390
+ alpha : Dictionary
391
+ The elimination ordering of nodes of G
392
+
393
+ Notes
394
+ -----
395
+ There are different approaches to calculate the chordal
396
+ enhancement of a graph. The algorithm used here is called
397
+ MCS-M and gives at least minimal (local) triangulation of graph. Note
398
+ that this triangulation is not necessarily a global minimum.
399
+
400
+ https://en.wikipedia.org/wiki/Chordal_graph
401
+
402
+ References
403
+ ----------
404
+ .. [1] Berry, Anne & Blair, Jean & Heggernes, Pinar & Peyton, Barry. (2004)
405
+ Maximum Cardinality Search for Computing Minimal Triangulations of
406
+ Graphs. Algorithmica. 39. 287-298. 10.1007/s00453-004-1084-3.
407
+
408
+ Examples
409
+ --------
410
+ >>> from networkx.algorithms.chordal import complete_to_chordal_graph
411
+ >>> G = nx.wheel_graph(10)
412
+ >>> H, alpha = complete_to_chordal_graph(G)
413
+ """
414
+ H = G.copy()
415
+ alpha = {node: 0 for node in H}
416
+ if nx.is_chordal(H):
417
+ return H, alpha
418
+ chords = set()
419
+ weight = {node: 0 for node in H.nodes()}
420
+ unnumbered_nodes = list(H.nodes())
421
+ for i in range(len(H.nodes()), 0, -1):
422
+ # get the node in unnumbered_nodes with the maximum weight
423
+ z = max(unnumbered_nodes, key=lambda node: weight[node])
424
+ unnumbered_nodes.remove(z)
425
+ alpha[z] = i
426
+ update_nodes = []
427
+ for y in unnumbered_nodes:
428
+ if G.has_edge(y, z):
429
+ update_nodes.append(y)
430
+ else:
431
+ # y_weight will be bigger than node weights between y and z
432
+ y_weight = weight[y]
433
+ lower_nodes = [
434
+ node for node in unnumbered_nodes if weight[node] < y_weight
435
+ ]
436
+ if nx.has_path(H.subgraph(lower_nodes + [z, y]), y, z):
437
+ update_nodes.append(y)
438
+ chords.add((z, y))
439
+ # during calculation of paths the weights should not be updated
440
+ for node in update_nodes:
441
+ weight[node] += 1
442
+ H.add_edges_from(chords)
443
+ return H, alpha
lib/python3.12/site-packages/networkx/algorithms/clique.py ADDED
@@ -0,0 +1,818 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Functions for finding and manipulating cliques.
2
+
3
+ Finding the largest clique in a graph is NP-complete problem, so most of
4
+ these algorithms have an exponential running time; for more information,
5
+ see the Wikipedia article on the clique problem [1]_.
6
+
7
+ .. [1] clique problem:: https://en.wikipedia.org/wiki/Clique_problem
8
+
9
+ """
10
+
11
+ from collections import Counter, defaultdict, deque
12
+ from itertools import chain, combinations, islice
13
+
14
+ import networkx as nx
15
+ from networkx.utils import not_implemented_for
16
+
17
+ __all__ = [
18
+ "find_cliques",
19
+ "find_cliques_recursive",
20
+ "make_max_clique_graph",
21
+ "make_clique_bipartite",
22
+ "node_clique_number",
23
+ "number_of_cliques",
24
+ "enumerate_all_cliques",
25
+ "max_weight_clique",
26
+ ]
27
+
28
+
29
+ @not_implemented_for("directed")
30
+ @nx._dispatchable
31
+ def enumerate_all_cliques(G):
32
+ """Returns all cliques in an undirected graph.
33
+
34
+ This function returns an iterator over cliques, each of which is a
35
+ list of nodes. The iteration is ordered by cardinality of the
36
+ cliques: first all cliques of size one, then all cliques of size
37
+ two, etc.
38
+
39
+ Parameters
40
+ ----------
41
+ G : NetworkX graph
42
+ An undirected graph.
43
+
44
+ Returns
45
+ -------
46
+ iterator
47
+ An iterator over cliques, each of which is a list of nodes in
48
+ `G`. The cliques are ordered according to size.
49
+
50
+ Notes
51
+ -----
52
+ To obtain a list of all cliques, use
53
+ `list(enumerate_all_cliques(G))`. However, be aware that in the
54
+ worst-case, the length of this list can be exponential in the number
55
+ of nodes in the graph (for example, when the graph is the complete
56
+ graph). This function avoids storing all cliques in memory by only
57
+ keeping current candidate node lists in memory during its search.
58
+
59
+ The implementation is adapted from the algorithm by Zhang, et
60
+ al. (2005) [1]_ to output all cliques discovered.
61
+
62
+ This algorithm ignores self-loops and parallel edges, since cliques
63
+ are not conventionally defined with such edges.
64
+
65
+ References
66
+ ----------
67
+ .. [1] Yun Zhang, Abu-Khzam, F.N., Baldwin, N.E., Chesler, E.J.,
68
+ Langston, M.A., Samatova, N.F.,
69
+ "Genome-Scale Computational Approaches to Memory-Intensive
70
+ Applications in Systems Biology".
71
+ *Supercomputing*, 2005. Proceedings of the ACM/IEEE SC 2005
72
+ Conference, pp. 12, 12--18 Nov. 2005.
73
+ <https://doi.org/10.1109/SC.2005.29>.
74
+
75
+ """
76
+ index = {}
77
+ nbrs = {}
78
+ for u in G:
79
+ index[u] = len(index)
80
+ # Neighbors of u that appear after u in the iteration order of G.
81
+ nbrs[u] = {v for v in G[u] if v not in index}
82
+
83
+ queue = deque(([u], sorted(nbrs[u], key=index.__getitem__)) for u in G)
84
+ # Loop invariants:
85
+ # 1. len(base) is nondecreasing.
86
+ # 2. (base + cnbrs) is sorted with respect to the iteration order of G.
87
+ # 3. cnbrs is a set of common neighbors of nodes in base.
88
+ while queue:
89
+ base, cnbrs = map(list, queue.popleft())
90
+ yield base
91
+ for i, u in enumerate(cnbrs):
92
+ # Use generators to reduce memory consumption.
93
+ queue.append(
94
+ (
95
+ chain(base, [u]),
96
+ filter(nbrs[u].__contains__, islice(cnbrs, i + 1, None)),
97
+ )
98
+ )
99
+
100
+
101
+ @not_implemented_for("directed")
102
+ @nx._dispatchable
103
+ def find_cliques(G, nodes=None):
104
+ """Returns all maximal cliques in an undirected graph.
105
+
106
+ For each node *n*, a *maximal clique for n* is a largest complete
107
+ subgraph containing *n*. The largest maximal clique is sometimes
108
+ called the *maximum clique*.
109
+
110
+ This function returns an iterator over cliques, each of which is a
111
+ list of nodes. It is an iterative implementation, so should not
112
+ suffer from recursion depth issues.
113
+
114
+ This function accepts a list of `nodes` and only the maximal cliques
115
+ containing all of these `nodes` are returned. It can considerably speed up
116
+ the running time if some specific cliques are desired.
117
+
118
+ Parameters
119
+ ----------
120
+ G : NetworkX graph
121
+ An undirected graph.
122
+
123
+ nodes : list, optional (default=None)
124
+ If provided, only yield *maximal cliques* containing all nodes in `nodes`.
125
+ If `nodes` isn't a clique itself, a ValueError is raised.
126
+
127
+ Returns
128
+ -------
129
+ iterator
130
+ An iterator over maximal cliques, each of which is a list of
131
+ nodes in `G`. If `nodes` is provided, only the maximal cliques
132
+ containing all the nodes in `nodes` are returned. The order of
133
+ cliques is arbitrary.
134
+
135
+ Raises
136
+ ------
137
+ ValueError
138
+ If `nodes` is not a clique.
139
+
140
+ Examples
141
+ --------
142
+ >>> from pprint import pprint # For nice dict formatting
143
+ >>> G = nx.karate_club_graph()
144
+ >>> sum(1 for c in nx.find_cliques(G)) # The number of maximal cliques in G
145
+ 36
146
+ >>> max(nx.find_cliques(G), key=len) # The largest maximal clique in G
147
+ [0, 1, 2, 3, 13]
148
+
149
+ The size of the largest maximal clique is known as the *clique number* of
150
+ the graph, which can be found directly with:
151
+
152
+ >>> max(len(c) for c in nx.find_cliques(G))
153
+ 5
154
+
155
+ One can also compute the number of maximal cliques in `G` that contain a given
156
+ node. The following produces a dictionary keyed by node whose
157
+ values are the number of maximal cliques in `G` that contain the node:
158
+
159
+ >>> from collections import Counter
160
+ >>> from itertools import chain
161
+ >>> counts = Counter(chain.from_iterable(nx.find_cliques(G)))
162
+ >>> pprint(dict(counts))
163
+ {0: 13,
164
+ 1: 6,
165
+ 2: 7,
166
+ 3: 3,
167
+ 4: 2,
168
+ 5: 3,
169
+ 6: 3,
170
+ 7: 1,
171
+ 8: 3,
172
+ 9: 2,
173
+ 10: 2,
174
+ 11: 1,
175
+ 12: 1,
176
+ 13: 2,
177
+ 14: 1,
178
+ 15: 1,
179
+ 16: 1,
180
+ 17: 1,
181
+ 18: 1,
182
+ 19: 2,
183
+ 20: 1,
184
+ 21: 1,
185
+ 22: 1,
186
+ 23: 3,
187
+ 24: 2,
188
+ 25: 2,
189
+ 26: 1,
190
+ 27: 3,
191
+ 28: 2,
192
+ 29: 2,
193
+ 30: 2,
194
+ 31: 4,
195
+ 32: 9,
196
+ 33: 14}
197
+
198
+ Or, similarly, the maximal cliques in `G` that contain a given node.
199
+ For example, the 4 maximal cliques that contain node 31:
200
+
201
+ >>> [c for c in nx.find_cliques(G) if 31 in c]
202
+ [[0, 31], [33, 32, 31], [33, 28, 31], [24, 25, 31]]
203
+
204
+ See Also
205
+ --------
206
+ find_cliques_recursive
207
+ A recursive version of the same algorithm.
208
+
209
+ Notes
210
+ -----
211
+ To obtain a list of all maximal cliques, use
212
+ `list(find_cliques(G))`. However, be aware that in the worst-case,
213
+ the length of this list can be exponential in the number of nodes in
214
+ the graph. This function avoids storing all cliques in memory by
215
+ only keeping current candidate node lists in memory during its search.
216
+
217
+ This implementation is based on the algorithm published by Bron and
218
+ Kerbosch (1973) [1]_, as adapted by Tomita, Tanaka and Takahashi
219
+ (2006) [2]_ and discussed in Cazals and Karande (2008) [3]_. It
220
+ essentially unrolls the recursion used in the references to avoid
221
+ issues of recursion stack depth (for a recursive implementation, see
222
+ :func:`find_cliques_recursive`).
223
+
224
+ This algorithm ignores self-loops and parallel edges, since cliques
225
+ are not conventionally defined with such edges.
226
+
227
+ References
228
+ ----------
229
+ .. [1] Bron, C. and Kerbosch, J.
230
+ "Algorithm 457: finding all cliques of an undirected graph".
231
+ *Communications of the ACM* 16, 9 (Sep. 1973), 575--577.
232
+ <http://portal.acm.org/citation.cfm?doid=362342.362367>
233
+
234
+ .. [2] Etsuji Tomita, Akira Tanaka, Haruhisa Takahashi,
235
+ "The worst-case time complexity for generating all maximal
236
+ cliques and computational experiments",
237
+ *Theoretical Computer Science*, Volume 363, Issue 1,
238
+ Computing and Combinatorics,
239
+ 10th Annual International Conference on
240
+ Computing and Combinatorics (COCOON 2004), 25 October 2006, Pages 28--42
241
+ <https://doi.org/10.1016/j.tcs.2006.06.015>
242
+
243
+ .. [3] F. Cazals, C. Karande,
244
+ "A note on the problem of reporting maximal cliques",
245
+ *Theoretical Computer Science*,
246
+ Volume 407, Issues 1--3, 6 November 2008, Pages 564--568,
247
+ <https://doi.org/10.1016/j.tcs.2008.05.010>
248
+
249
+ """
250
+ if len(G) == 0:
251
+ return
252
+
253
+ adj = {u: {v for v in G[u] if v != u} for u in G}
254
+
255
+ # Initialize Q with the given nodes and subg, cand with their nbrs
256
+ Q = nodes[:] if nodes is not None else []
257
+ cand = set(G)
258
+ for node in Q:
259
+ if node not in cand:
260
+ raise ValueError(f"The given `nodes` {nodes} do not form a clique")
261
+ cand &= adj[node]
262
+
263
+ if not cand:
264
+ yield Q[:]
265
+ return
266
+
267
+ subg = cand.copy()
268
+ stack = []
269
+ Q.append(None)
270
+
271
+ u = max(subg, key=lambda u: len(cand & adj[u]))
272
+ ext_u = cand - adj[u]
273
+
274
+ try:
275
+ while True:
276
+ if ext_u:
277
+ q = ext_u.pop()
278
+ cand.remove(q)
279
+ Q[-1] = q
280
+ adj_q = adj[q]
281
+ subg_q = subg & adj_q
282
+ if not subg_q:
283
+ yield Q[:]
284
+ else:
285
+ cand_q = cand & adj_q
286
+ if cand_q:
287
+ stack.append((subg, cand, ext_u))
288
+ Q.append(None)
289
+ subg = subg_q
290
+ cand = cand_q
291
+ u = max(subg, key=lambda u: len(cand & adj[u]))
292
+ ext_u = cand - adj[u]
293
+ else:
294
+ Q.pop()
295
+ subg, cand, ext_u = stack.pop()
296
+ except IndexError:
297
+ pass
298
+
299
+
300
+ @not_implemented_for("directed")
301
+ @nx._dispatchable
302
+ def find_cliques_recursive(G, nodes=None):
303
+ """Returns all maximal cliques in a graph.
304
+
305
+ For each node *v*, a *maximal clique for v* is a largest complete
306
+ subgraph containing *v*. The largest maximal clique is sometimes
307
+ called the *maximum clique*.
308
+
309
+ This function returns an iterator over cliques, each of which is a
310
+ list of nodes. It is a recursive implementation, so may suffer from
311
+ recursion depth issues, but is included for pedagogical reasons.
312
+ For a non-recursive implementation, see :func:`find_cliques`.
313
+
314
+ This function accepts a list of `nodes` and only the maximal cliques
315
+ containing all of these `nodes` are returned. It can considerably speed up
316
+ the running time if some specific cliques are desired.
317
+
318
+ Parameters
319
+ ----------
320
+ G : NetworkX graph
321
+ An undirected graph.
322
+
323
+ nodes : list, optional (default=None)
324
+ If provided, only yield *maximal cliques* containing all nodes in `nodes`.
325
+ If `nodes` isn't a clique itself, a ValueError is raised.
326
+
327
+ Returns
328
+ -------
329
+ iterator
330
+ An iterator over maximal cliques, each of which is a list of
331
+ nodes in `G`. If `nodes` is provided, only the maximal cliques
332
+ containing all the nodes in `nodes` are yielded. The order of
333
+ cliques is arbitrary.
334
+
335
+ Raises
336
+ ------
337
+ NetworkXNotImplemented
338
+ If `G` is directed.
339
+
340
+ ValueError
341
+ If `nodes` is not a clique.
342
+
343
+ See Also
344
+ --------
345
+ find_cliques
346
+ An iterative version of the same algorithm. See docstring for examples.
347
+
348
+ Notes
349
+ -----
350
+ To obtain a list of all maximal cliques, use
351
+ `list(find_cliques_recursive(G))`. However, be aware that in the
352
+ worst-case, the length of this list can be exponential in the number
353
+ of nodes in the graph. This function avoids storing all cliques in memory
354
+ by only keeping current candidate node lists in memory during its search.
355
+
356
+ This implementation is based on the algorithm published by Bron and
357
+ Kerbosch (1973) [1]_, as adapted by Tomita, Tanaka and Takahashi
358
+ (2006) [2]_ and discussed in Cazals and Karande (2008) [3]_. For a
359
+ non-recursive implementation, see :func:`find_cliques`.
360
+
361
+ This algorithm ignores self-loops and parallel edges, since cliques
362
+ are not conventionally defined with such edges.
363
+
364
+ References
365
+ ----------
366
+ .. [1] Bron, C. and Kerbosch, J.
367
+ "Algorithm 457: finding all cliques of an undirected graph".
368
+ *Communications of the ACM* 16, 9 (Sep. 1973), 575--577.
369
+ <http://portal.acm.org/citation.cfm?doid=362342.362367>
370
+
371
+ .. [2] Etsuji Tomita, Akira Tanaka, Haruhisa Takahashi,
372
+ "The worst-case time complexity for generating all maximal
373
+ cliques and computational experiments",
374
+ *Theoretical Computer Science*, Volume 363, Issue 1,
375
+ Computing and Combinatorics,
376
+ 10th Annual International Conference on
377
+ Computing and Combinatorics (COCOON 2004), 25 October 2006, Pages 28--42
378
+ <https://doi.org/10.1016/j.tcs.2006.06.015>
379
+
380
+ .. [3] F. Cazals, C. Karande,
381
+ "A note on the problem of reporting maximal cliques",
382
+ *Theoretical Computer Science*,
383
+ Volume 407, Issues 1--3, 6 November 2008, Pages 564--568,
384
+ <https://doi.org/10.1016/j.tcs.2008.05.010>
385
+
386
+ """
387
+ if len(G) == 0:
388
+ return iter([])
389
+
390
+ adj = {u: {v for v in G[u] if v != u} for u in G}
391
+
392
+ # Initialize Q with the given nodes and subg, cand with their nbrs
393
+ Q = nodes[:] if nodes is not None else []
394
+ cand_init = set(G)
395
+ for node in Q:
396
+ if node not in cand_init:
397
+ raise ValueError(f"The given `nodes` {nodes} do not form a clique")
398
+ cand_init &= adj[node]
399
+
400
+ if not cand_init:
401
+ return iter([Q])
402
+
403
+ subg_init = cand_init.copy()
404
+
405
+ def expand(subg, cand):
406
+ u = max(subg, key=lambda u: len(cand & adj[u]))
407
+ for q in cand - adj[u]:
408
+ cand.remove(q)
409
+ Q.append(q)
410
+ adj_q = adj[q]
411
+ subg_q = subg & adj_q
412
+ if not subg_q:
413
+ yield Q[:]
414
+ else:
415
+ cand_q = cand & adj_q
416
+ if cand_q:
417
+ yield from expand(subg_q, cand_q)
418
+ Q.pop()
419
+
420
+ return expand(subg_init, cand_init)
421
+
422
+
423
+ @nx._dispatchable(returns_graph=True)
424
+ def make_max_clique_graph(G, create_using=None):
425
+ """Returns the maximal clique graph of the given graph.
426
+
427
+ The nodes of the maximal clique graph of `G` are the cliques of
428
+ `G` and an edge joins two cliques if the cliques are not disjoint.
429
+
430
+ Parameters
431
+ ----------
432
+ G : NetworkX graph
433
+
434
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
435
+ Graph type to create. If graph instance, then cleared before populated.
436
+
437
+ Returns
438
+ -------
439
+ NetworkX graph
440
+ A graph whose nodes are the cliques of `G` and whose edges
441
+ join two cliques if they are not disjoint.
442
+
443
+ Notes
444
+ -----
445
+ This function behaves like the following code::
446
+
447
+ import networkx as nx
448
+
449
+ G = nx.make_clique_bipartite(G)
450
+ cliques = [v for v in G.nodes() if G.nodes[v]["bipartite"] == 0]
451
+ G = nx.bipartite.projected_graph(G, cliques)
452
+ G = nx.relabel_nodes(G, {-v: v - 1 for v in G})
453
+
454
+ It should be faster, though, since it skips all the intermediate
455
+ steps.
456
+
457
+ """
458
+ if create_using is None:
459
+ B = G.__class__()
460
+ else:
461
+ B = nx.empty_graph(0, create_using)
462
+ cliques = list(enumerate(set(c) for c in find_cliques(G)))
463
+ # Add a numbered node for each clique.
464
+ B.add_nodes_from(i for i, c in cliques)
465
+ # Join cliques by an edge if they share a node.
466
+ clique_pairs = combinations(cliques, 2)
467
+ B.add_edges_from((i, j) for (i, c1), (j, c2) in clique_pairs if c1 & c2)
468
+ return B
469
+
470
+
471
+ @nx._dispatchable(returns_graph=True)
472
+ def make_clique_bipartite(G, fpos=None, create_using=None, name=None):
473
+ """Returns the bipartite clique graph corresponding to `G`.
474
+
475
+ In the returned bipartite graph, the "bottom" nodes are the nodes of
476
+ `G` and the "top" nodes represent the maximal cliques of `G`.
477
+ There is an edge from node *v* to clique *C* in the returned graph
478
+ if and only if *v* is an element of *C*.
479
+
480
+ Parameters
481
+ ----------
482
+ G : NetworkX graph
483
+ An undirected graph.
484
+
485
+ fpos : bool
486
+ If True or not None, the returned graph will have an
487
+ additional attribute, `pos`, a dictionary mapping node to
488
+ position in the Euclidean plane.
489
+
490
+ create_using : NetworkX graph constructor, optional (default=nx.Graph)
491
+ Graph type to create. If graph instance, then cleared before populated.
492
+
493
+ Returns
494
+ -------
495
+ NetworkX graph
496
+ A bipartite graph whose "bottom" set is the nodes of the graph
497
+ `G`, whose "top" set is the cliques of `G`, and whose edges
498
+ join nodes of `G` to the cliques that contain them.
499
+
500
+ The nodes of the graph `G` have the node attribute
501
+ 'bipartite' set to 1 and the nodes representing cliques
502
+ have the node attribute 'bipartite' set to 0, as is the
503
+ convention for bipartite graphs in NetworkX.
504
+
505
+ """
506
+ B = nx.empty_graph(0, create_using)
507
+ B.clear()
508
+ # The "bottom" nodes in the bipartite graph are the nodes of the
509
+ # original graph, G.
510
+ B.add_nodes_from(G, bipartite=1)
511
+ for i, cl in enumerate(find_cliques(G)):
512
+ # The "top" nodes in the bipartite graph are the cliques. These
513
+ # nodes get negative numbers as labels.
514
+ name = -i - 1
515
+ B.add_node(name, bipartite=0)
516
+ B.add_edges_from((v, name) for v in cl)
517
+ return B
518
+
519
+
520
+ @nx._dispatchable
521
+ def node_clique_number(G, nodes=None, cliques=None, separate_nodes=False):
522
+ """Returns the size of the largest maximal clique containing each given node.
523
+
524
+ Returns a single or list depending on input nodes.
525
+ An optional list of cliques can be input if already computed.
526
+
527
+ Parameters
528
+ ----------
529
+ G : NetworkX graph
530
+ An undirected graph.
531
+
532
+ cliques : list, optional (default=None)
533
+ A list of cliques, each of which is itself a list of nodes.
534
+ If not specified, the list of all cliques will be computed
535
+ using :func:`find_cliques`.
536
+
537
+ Returns
538
+ -------
539
+ int or dict
540
+ If `nodes` is a single node, returns the size of the
541
+ largest maximal clique in `G` containing that node.
542
+ Otherwise return a dict keyed by node to the size
543
+ of the largest maximal clique containing that node.
544
+
545
+ See Also
546
+ --------
547
+ find_cliques
548
+ find_cliques yields the maximal cliques of G.
549
+ It accepts a `nodes` argument which restricts consideration to
550
+ maximal cliques containing all the given `nodes`.
551
+ The search for the cliques is optimized for `nodes`.
552
+ number_of_cliques
553
+ """
554
+ if cliques is None:
555
+ if nodes is not None:
556
+ # Use ego_graph to decrease size of graph
557
+ # check for single node
558
+ if nodes in G:
559
+ return max(len(c) for c in find_cliques(nx.ego_graph(G, nodes)))
560
+ # handle multiple nodes
561
+ return {
562
+ n: max(len(c) for c in find_cliques(nx.ego_graph(G, n))) for n in nodes
563
+ }
564
+
565
+ # nodes is None--find all cliques
566
+ cliques = list(find_cliques(G))
567
+
568
+ # single node requested
569
+ if nodes in G:
570
+ return max(len(c) for c in cliques if nodes in c)
571
+
572
+ # multiple nodes requested
573
+ # preprocess all nodes (faster than one at a time for even 2 nodes)
574
+ size_for_n = defaultdict(int)
575
+ for c in cliques:
576
+ size_of_c = len(c)
577
+ for n in c:
578
+ if size_for_n[n] < size_of_c:
579
+ size_for_n[n] = size_of_c
580
+ if nodes is None:
581
+ return size_for_n
582
+ return {n: size_for_n[n] for n in nodes}
583
+
584
+
585
+ def number_of_cliques(G, nodes=None, cliques=None):
586
+ """Return the number of maximal cliques each node is part of.
587
+
588
+ Output is a single value or dict depending on `nodes`.
589
+ Optional list of cliques can be input if already computed.
590
+
591
+ Parameters
592
+ ----------
593
+ G : NetworkX graph
594
+ An undirected graph.
595
+
596
+ nodes : list or None, optional (default=None)
597
+ A list of nodes to return the number of maximal cliques for.
598
+ If `None`, return the number of maximal cliques for all nodes.
599
+
600
+ cliques : list or None, optional (default=None)
601
+ A precomputed list of maximal cliques to use for the calculation.
602
+
603
+ Returns
604
+ -------
605
+ int or dict
606
+ If `nodes` is a single node, return the number of maximal cliques it is
607
+ part of. If `nodes` is a list, return a dictionary keyed by node to the
608
+ number of maximal cliques it is part of.
609
+
610
+ Raises
611
+ ------
612
+ NetworkXNotImplemented
613
+ If `G` is directed.
614
+
615
+ See Also
616
+ --------
617
+ find_cliques
618
+ node_clique_number
619
+
620
+ Examples
621
+ --------
622
+ Compute the number of maximal cliques a node is part of:
623
+
624
+ >>> G = nx.complete_graph(3)
625
+ >>> nx.add_cycle(G, [0, 3, 4])
626
+ >>> nx.number_of_cliques(G, nodes=0)
627
+ 2
628
+ >>> nx.number_of_cliques(G, nodes=1)
629
+ 1
630
+
631
+ Or, for a list of nodes:
632
+
633
+ >>> nx.number_of_cliques(G, nodes=[0, 1])
634
+ {0: 2, 1: 1}
635
+
636
+ If no explicit `nodes` are provided, all nodes are considered:
637
+
638
+ >>> nx.number_of_cliques(G)
639
+ {0: 2, 1: 1, 2: 1, 3: 1, 4: 1}
640
+
641
+ The list of maximal cliques can also be precomputed:
642
+
643
+ >>> cl = list(nx.find_cliques(G))
644
+ >>> nx.number_of_cliques(G, cliques=cl)
645
+ {0: 2, 1: 1, 2: 1, 3: 1, 4: 1}
646
+ """
647
+ if cliques is None:
648
+ cliques = find_cliques(G)
649
+
650
+ if nodes is None:
651
+ nodes = list(G.nodes()) # none, get entire graph
652
+
653
+ if not isinstance(nodes, list): # check for a list
654
+ v = nodes
655
+ # assume it is a single value
656
+ numcliq = sum(1 for c in cliques if v in c)
657
+ else:
658
+ numcliq = Counter(chain.from_iterable(cliques))
659
+ numcliq = {v: numcliq[v] for v in nodes} # return a dict
660
+ return numcliq
661
+
662
+
663
+ class MaxWeightClique:
664
+ """A class for the maximum weight clique algorithm.
665
+
666
+ This class is a helper for the `max_weight_clique` function. The class
667
+ should not normally be used directly.
668
+
669
+ Parameters
670
+ ----------
671
+ G : NetworkX graph
672
+ The undirected graph for which a maximum weight clique is sought
673
+ weight : string or None, optional (default='weight')
674
+ The node attribute that holds the integer value used as a weight.
675
+ If None, then each node has weight 1.
676
+
677
+ Attributes
678
+ ----------
679
+ G : NetworkX graph
680
+ The undirected graph for which a maximum weight clique is sought
681
+ node_weights: dict
682
+ The weight of each node
683
+ incumbent_nodes : list
684
+ The nodes of the incumbent clique (the best clique found so far)
685
+ incumbent_weight: int
686
+ The weight of the incumbent clique
687
+ """
688
+
689
+ def __init__(self, G, weight):
690
+ self.G = G
691
+ self.incumbent_nodes = []
692
+ self.incumbent_weight = 0
693
+
694
+ if weight is None:
695
+ self.node_weights = {v: 1 for v in G.nodes()}
696
+ else:
697
+ for v in G.nodes():
698
+ if weight not in G.nodes[v]:
699
+ errmsg = f"Node {v!r} does not have the requested weight field."
700
+ raise KeyError(errmsg)
701
+ if not isinstance(G.nodes[v][weight], int):
702
+ errmsg = f"The {weight!r} field of node {v!r} is not an integer."
703
+ raise ValueError(errmsg)
704
+ self.node_weights = {v: G.nodes[v][weight] for v in G.nodes()}
705
+
706
+ def update_incumbent_if_improved(self, C, C_weight):
707
+ """Update the incumbent if the node set C has greater weight.
708
+
709
+ C is assumed to be a clique.
710
+ """
711
+ if C_weight > self.incumbent_weight:
712
+ self.incumbent_nodes = C[:]
713
+ self.incumbent_weight = C_weight
714
+
715
+ def greedily_find_independent_set(self, P):
716
+ """Greedily find an independent set of nodes from a set of
717
+ nodes P."""
718
+ independent_set = []
719
+ P = P[:]
720
+ while P:
721
+ v = P[0]
722
+ independent_set.append(v)
723
+ P = [w for w in P if v != w and not self.G.has_edge(v, w)]
724
+ return independent_set
725
+
726
+ def find_branching_nodes(self, P, target):
727
+ """Find a set of nodes to branch on."""
728
+ residual_wt = {v: self.node_weights[v] for v in P}
729
+ total_wt = 0
730
+ P = P[:]
731
+ while P:
732
+ independent_set = self.greedily_find_independent_set(P)
733
+ min_wt_in_class = min(residual_wt[v] for v in independent_set)
734
+ total_wt += min_wt_in_class
735
+ if total_wt > target:
736
+ break
737
+ for v in independent_set:
738
+ residual_wt[v] -= min_wt_in_class
739
+ P = [v for v in P if residual_wt[v] != 0]
740
+ return P
741
+
742
+ def expand(self, C, C_weight, P):
743
+ """Look for the best clique that contains all the nodes in C and zero or
744
+ more of the nodes in P, backtracking if it can be shown that no such
745
+ clique has greater weight than the incumbent.
746
+ """
747
+ self.update_incumbent_if_improved(C, C_weight)
748
+ branching_nodes = self.find_branching_nodes(P, self.incumbent_weight - C_weight)
749
+ while branching_nodes:
750
+ v = branching_nodes.pop()
751
+ P.remove(v)
752
+ new_C = C + [v]
753
+ new_C_weight = C_weight + self.node_weights[v]
754
+ new_P = [w for w in P if self.G.has_edge(v, w)]
755
+ self.expand(new_C, new_C_weight, new_P)
756
+
757
+ def find_max_weight_clique(self):
758
+ """Find a maximum weight clique."""
759
+ # Sort nodes in reverse order of degree for speed
760
+ nodes = sorted(self.G.nodes(), key=lambda v: self.G.degree(v), reverse=True)
761
+ nodes = [v for v in nodes if self.node_weights[v] > 0]
762
+ self.expand([], 0, nodes)
763
+
764
+
765
+ @not_implemented_for("directed")
766
+ @nx._dispatchable(node_attrs="weight")
767
+ def max_weight_clique(G, weight="weight"):
768
+ """Find a maximum weight clique in G.
769
+
770
+ A *clique* in a graph is a set of nodes such that every two distinct nodes
771
+ are adjacent. The *weight* of a clique is the sum of the weights of its
772
+ nodes. A *maximum weight clique* of graph G is a clique C in G such that
773
+ no clique in G has weight greater than the weight of C.
774
+
775
+ Parameters
776
+ ----------
777
+ G : NetworkX graph
778
+ Undirected graph
779
+ weight : string or None, optional (default='weight')
780
+ The node attribute that holds the integer value used as a weight.
781
+ If None, then each node has weight 1.
782
+
783
+ Returns
784
+ -------
785
+ clique : list
786
+ the nodes of a maximum weight clique
787
+ weight : int
788
+ the weight of a maximum weight clique
789
+
790
+ Notes
791
+ -----
792
+ The implementation is recursive, and therefore it may run into recursion
793
+ depth issues if G contains a clique whose number of nodes is close to the
794
+ recursion depth limit.
795
+
796
+ At each search node, the algorithm greedily constructs a weighted
797
+ independent set cover of part of the graph in order to find a small set of
798
+ nodes on which to branch. The algorithm is very similar to the algorithm
799
+ of Tavares et al. [1]_, other than the fact that the NetworkX version does
800
+ not use bitsets. This style of algorithm for maximum weight clique (and
801
+ maximum weight independent set, which is the same problem but on the
802
+ complement graph) has a decades-long history. See Algorithm B of Warren
803
+ and Hicks [2]_ and the references in that paper.
804
+
805
+ References
806
+ ----------
807
+ .. [1] Tavares, W.A., Neto, M.B.C., Rodrigues, C.D., Michelon, P.: Um
808
+ algoritmo de branch and bound para o problema da clique máxima
809
+ ponderada. Proceedings of XLVII SBPO 1 (2015).
810
+
811
+ .. [2] Warren, Jeffrey S, Hicks, Illya V.: Combinatorial Branch-and-Bound
812
+ for the Maximum Weight Independent Set Problem. Technical Report,
813
+ Texas A&M University (2016).
814
+ """
815
+
816
+ mwc = MaxWeightClique(G, weight)
817
+ mwc.find_max_weight_clique()
818
+ return mwc.incumbent_nodes, mwc.incumbent_weight
lib/python3.12/site-packages/networkx/algorithms/cluster.py ADDED
@@ -0,0 +1,732 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Algorithms to characterize the number of triangles in a graph."""
2
+
3
+ from collections import Counter
4
+ from itertools import chain, combinations
5
+
6
+ import networkx as nx
7
+ from networkx.utils import not_implemented_for
8
+
9
+ __all__ = [
10
+ "triangles",
11
+ "all_triangles",
12
+ "average_clustering",
13
+ "clustering",
14
+ "transitivity",
15
+ "square_clustering",
16
+ "generalized_degree",
17
+ ]
18
+
19
+
20
+ @not_implemented_for("directed")
21
+ @nx._dispatchable
22
+ def triangles(G, nodes=None):
23
+ """Compute the number of triangles.
24
+
25
+ Finds the number of triangles that include a node as one vertex.
26
+
27
+ Parameters
28
+ ----------
29
+ G : graph
30
+ A networkx graph
31
+
32
+ nodes : node, iterable of nodes, or None (default=None)
33
+ If a singleton node, return the number of triangles for that node.
34
+ If an iterable, compute the number of triangles for each of those nodes.
35
+ If `None` (the default) compute the number of triangles for all nodes in `G`.
36
+
37
+ Returns
38
+ -------
39
+ out : dict or int
40
+ If `nodes` is a container of nodes, returns number of triangles keyed by node (dict).
41
+ If `nodes` is a specific node, returns number of triangles for the node (int).
42
+
43
+ Examples
44
+ --------
45
+ >>> G = nx.complete_graph(5)
46
+ >>> print(nx.triangles(G, 0))
47
+ 6
48
+ >>> print(nx.triangles(G))
49
+ {0: 6, 1: 6, 2: 6, 3: 6, 4: 6}
50
+ >>> print(list(nx.triangles(G, [0, 1]).values()))
51
+ [6, 6]
52
+
53
+ The total number of unique triangles in `G` can be determined by summing
54
+ the number of triangles for each node and dividing by 3 (because a given
55
+ triangle gets counted three times, once for each of its nodes).
56
+
57
+ >>> sum(nx.triangles(G).values()) // 3
58
+ 10
59
+
60
+ Notes
61
+ -----
62
+ Self loops are ignored.
63
+
64
+ """
65
+ if nodes is not None:
66
+ # If `nodes` represents a single node, return only its number of triangles
67
+ if nodes in G:
68
+ return next(_triangles_and_degree_iter(G, nodes))[2] // 2
69
+
70
+ # if `nodes` is a container of nodes, then return a
71
+ # dictionary mapping node to number of triangles.
72
+ return {v: t // 2 for v, d, t, _ in _triangles_and_degree_iter(G, nodes)}
73
+
74
+ # if nodes is None, then compute triangles for the complete graph
75
+
76
+ # dict used to avoid visiting the same nodes twice
77
+ # this allows calculating/counting each triangle only once
78
+ later_nbrs = {}
79
+
80
+ # iterate over the nodes in a graph
81
+ for node, neighbors in G.adjacency():
82
+ later_nbrs[node] = {n for n in neighbors if n not in later_nbrs and n != node}
83
+
84
+ # instantiate Counter for each node to include isolated nodes
85
+ # add 1 to the count if a nodes neighbor's neighbor is also a neighbor
86
+ triangle_counts = Counter(dict.fromkeys(G, 0))
87
+ for node1, neighbors in later_nbrs.items():
88
+ for node2 in neighbors:
89
+ third_nodes = neighbors & later_nbrs[node2]
90
+ m = len(third_nodes)
91
+ triangle_counts[node1] += m
92
+ triangle_counts[node2] += m
93
+ triangle_counts.update(third_nodes)
94
+
95
+ return dict(triangle_counts)
96
+
97
+
98
+ @not_implemented_for("multigraph")
99
+ def _triangles_and_degree_iter(G, nodes=None):
100
+ """Return an iterator of (node, degree, triangles, generalized degree).
101
+
102
+ This double counts triangles so you may want to divide by 2.
103
+ See degree(), triangles() and generalized_degree() for definitions
104
+ and details.
105
+
106
+ """
107
+ if nodes is None:
108
+ nodes_nbrs = G.adj.items()
109
+ else:
110
+ nodes_nbrs = ((n, G[n]) for n in G.nbunch_iter(nodes))
111
+
112
+ for v, v_nbrs in nodes_nbrs:
113
+ vs = set(v_nbrs) - {v}
114
+ gen_degree = Counter(len(vs & (set(G[w]) - {w})) for w in vs)
115
+ ntriangles = sum(k * val for k, val in gen_degree.items())
116
+ yield (v, len(vs), ntriangles, gen_degree)
117
+
118
+
119
+ @not_implemented_for("multigraph")
120
+ def _weighted_triangles_and_degree_iter(G, nodes=None, weight="weight"):
121
+ """Return an iterator of (node, degree, weighted_triangles).
122
+
123
+ Used for weighted clustering.
124
+ Note: this returns the geometric average weight of edges in the triangle.
125
+ Also, each triangle is counted twice (each direction).
126
+ So you may want to divide by 2.
127
+
128
+ """
129
+ import numpy as np
130
+
131
+ if weight is None or G.number_of_edges() == 0:
132
+ max_weight = 1
133
+ else:
134
+ max_weight = max(d.get(weight, 1) for u, v, d in G.edges(data=True))
135
+ if nodes is None:
136
+ nodes_nbrs = G.adj.items()
137
+ else:
138
+ nodes_nbrs = ((n, G[n]) for n in G.nbunch_iter(nodes))
139
+
140
+ def wt(u, v):
141
+ return G[u][v].get(weight, 1) / max_weight
142
+
143
+ for i, nbrs in nodes_nbrs:
144
+ inbrs = set(nbrs) - {i}
145
+ weighted_triangles = 0
146
+ seen = set()
147
+ for j in inbrs:
148
+ seen.add(j)
149
+ # This avoids counting twice -- we double at the end.
150
+ jnbrs = set(G[j]) - seen
151
+ # Only compute the edge weight once, before the inner inner
152
+ # loop.
153
+ wij = wt(i, j)
154
+ weighted_triangles += np.cbrt(
155
+ [(wij * wt(j, k) * wt(k, i)) for k in inbrs & jnbrs]
156
+ ).sum()
157
+ yield (i, len(inbrs), 2 * float(weighted_triangles))
158
+
159
+
160
+ @not_implemented_for("multigraph")
161
+ def _directed_triangles_and_degree_iter(G, nodes=None):
162
+ """Return an iterator of
163
+ (node, total_degree, reciprocal_degree, directed_triangles).
164
+
165
+ Used for directed clustering.
166
+ Note that unlike `_triangles_and_degree_iter()`, this function counts
167
+ directed triangles so does not count triangles twice.
168
+
169
+ """
170
+ nodes_nbrs = ((n, G._pred[n], G._succ[n]) for n in G.nbunch_iter(nodes))
171
+
172
+ for i, preds, succs in nodes_nbrs:
173
+ ipreds = set(preds) - {i}
174
+ isuccs = set(succs) - {i}
175
+
176
+ directed_triangles = 0
177
+ for j in chain(ipreds, isuccs):
178
+ jpreds = set(G._pred[j]) - {j}
179
+ jsuccs = set(G._succ[j]) - {j}
180
+ directed_triangles += sum(
181
+ 1
182
+ for k in chain(
183
+ (ipreds & jpreds),
184
+ (ipreds & jsuccs),
185
+ (isuccs & jpreds),
186
+ (isuccs & jsuccs),
187
+ )
188
+ )
189
+ dtotal = len(ipreds) + len(isuccs)
190
+ dbidirectional = len(ipreds & isuccs)
191
+ yield (i, dtotal, dbidirectional, directed_triangles)
192
+
193
+
194
+ @not_implemented_for("multigraph")
195
+ def _directed_weighted_triangles_and_degree_iter(G, nodes=None, weight="weight"):
196
+ """Return an iterator of
197
+ (node, total_degree, reciprocal_degree, directed_weighted_triangles).
198
+
199
+ Used for directed weighted clustering.
200
+ Note that unlike `_weighted_triangles_and_degree_iter()`, this function counts
201
+ directed triangles so does not count triangles twice.
202
+
203
+ """
204
+ import numpy as np
205
+
206
+ if weight is None or G.number_of_edges() == 0:
207
+ max_weight = 1
208
+ else:
209
+ max_weight = max(d.get(weight, 1) for u, v, d in G.edges(data=True))
210
+
211
+ nodes_nbrs = ((n, G._pred[n], G._succ[n]) for n in G.nbunch_iter(nodes))
212
+
213
+ def wt(u, v):
214
+ return G[u][v].get(weight, 1) / max_weight
215
+
216
+ for i, preds, succs in nodes_nbrs:
217
+ ipreds = set(preds) - {i}
218
+ isuccs = set(succs) - {i}
219
+
220
+ directed_triangles = 0
221
+ for j in ipreds:
222
+ jpreds = set(G._pred[j]) - {j}
223
+ jsuccs = set(G._succ[j]) - {j}
224
+ directed_triangles += np.cbrt(
225
+ [(wt(j, i) * wt(k, i) * wt(k, j)) for k in ipreds & jpreds]
226
+ ).sum()
227
+ directed_triangles += np.cbrt(
228
+ [(wt(j, i) * wt(k, i) * wt(j, k)) for k in ipreds & jsuccs]
229
+ ).sum()
230
+ directed_triangles += np.cbrt(
231
+ [(wt(j, i) * wt(i, k) * wt(k, j)) for k in isuccs & jpreds]
232
+ ).sum()
233
+ directed_triangles += np.cbrt(
234
+ [(wt(j, i) * wt(i, k) * wt(j, k)) for k in isuccs & jsuccs]
235
+ ).sum()
236
+
237
+ for j in isuccs:
238
+ jpreds = set(G._pred[j]) - {j}
239
+ jsuccs = set(G._succ[j]) - {j}
240
+ directed_triangles += np.cbrt(
241
+ [(wt(i, j) * wt(k, i) * wt(k, j)) for k in ipreds & jpreds]
242
+ ).sum()
243
+ directed_triangles += np.cbrt(
244
+ [(wt(i, j) * wt(k, i) * wt(j, k)) for k in ipreds & jsuccs]
245
+ ).sum()
246
+ directed_triangles += np.cbrt(
247
+ [(wt(i, j) * wt(i, k) * wt(k, j)) for k in isuccs & jpreds]
248
+ ).sum()
249
+ directed_triangles += np.cbrt(
250
+ [(wt(i, j) * wt(i, k) * wt(j, k)) for k in isuccs & jsuccs]
251
+ ).sum()
252
+
253
+ dtotal = len(ipreds) + len(isuccs)
254
+ dbidirectional = len(ipreds & isuccs)
255
+ yield (i, dtotal, dbidirectional, float(directed_triangles))
256
+
257
+
258
+ @not_implemented_for("directed")
259
+ @nx._dispatchable
260
+ def all_triangles(G, nbunch=None):
261
+ """
262
+ Yields all unique triangles in an undirected graph.
263
+
264
+ A triangle is a set of three distinct nodes where each node is connected to
265
+ the other two.
266
+
267
+ Parameters
268
+ ----------
269
+ G : NetworkX graph
270
+ An undirected graph.
271
+
272
+ nbunch : node, iterable of nodes, or None (default=None)
273
+ If a node or iterable of nodes, only triangles involving at least one
274
+ node in `nbunch` are yielded.
275
+ If ``None``, yields all unique triangles in the graph.
276
+
277
+ Yields
278
+ ------
279
+ tuple
280
+ A tuple of three nodes forming a triangle ``(u, v, w)``.
281
+
282
+ Examples
283
+ --------
284
+ >>> G = nx.complete_graph(4)
285
+ >>> sorted([sorted(t) for t in all_triangles(G)])
286
+ [[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]]
287
+
288
+ Notes
289
+ -----
290
+ This algorithm ensures each triangle is yielded once using an internal node ordering.
291
+ In multigraphs, triangles are identified by their unique set of nodes,
292
+ ignoring multiple edges between the same nodes. Self-loops are ignored.
293
+ Runs in ``O(m * d)`` time in the worst case, where ``m`` the number of edges
294
+ and ``d`` the maximum degree.
295
+
296
+ See Also
297
+ --------
298
+ :func:`~networkx.algorithms.triads.all_triads` : related function for directed graphs
299
+ """
300
+ if nbunch is None:
301
+ nbunch = relevant_nodes = G
302
+ else:
303
+ nbunch = dict.fromkeys(G.nbunch_iter(nbunch))
304
+ relevant_nodes = chain(
305
+ nbunch,
306
+ (nbr for node in nbunch for nbr in G.neighbors(node) if nbr not in nbunch),
307
+ )
308
+
309
+ node_to_id = {node: i for i, node in enumerate(relevant_nodes)}
310
+
311
+ for u in nbunch:
312
+ u_id = node_to_id[u]
313
+ u_nbrs = G._adj[u].keys()
314
+ for v in u_nbrs:
315
+ v_id = node_to_id.get(v, -1)
316
+ if v_id <= u_id:
317
+ continue
318
+ v_nbrs = G._adj[v].keys()
319
+ for w in v_nbrs & u_nbrs:
320
+ if node_to_id.get(w, -1) > v_id:
321
+ yield u, v, w
322
+
323
+
324
+ @nx._dispatchable(edge_attrs="weight")
325
+ def average_clustering(G, nodes=None, weight=None, count_zeros=True):
326
+ r"""Compute the average clustering coefficient for the graph G.
327
+
328
+ The clustering coefficient for the graph is the average,
329
+
330
+ .. math::
331
+
332
+ C = \frac{1}{n}\sum_{v \in G} c_v,
333
+
334
+ where :math:`n` is the number of nodes in `G`.
335
+
336
+ Parameters
337
+ ----------
338
+ G : graph
339
+
340
+ nodes : container of nodes, optional (default=all nodes in G)
341
+ Compute average clustering for nodes in this container.
342
+
343
+ weight : string or None, optional (default=None)
344
+ The edge attribute that holds the numerical value used as a weight.
345
+ If None, then each edge has weight 1.
346
+
347
+ count_zeros : bool
348
+ If False include only the nodes with nonzero clustering in the average.
349
+
350
+ Returns
351
+ -------
352
+ avg : float
353
+ Average clustering
354
+
355
+ Examples
356
+ --------
357
+ >>> G = nx.complete_graph(5)
358
+ >>> print(nx.average_clustering(G))
359
+ 1.0
360
+
361
+ Notes
362
+ -----
363
+ This is a space saving routine; it might be faster
364
+ to use the clustering function to get a list and then take the average.
365
+
366
+ Self loops are ignored.
367
+
368
+ References
369
+ ----------
370
+ .. [1] Generalizations of the clustering coefficient to weighted
371
+ complex networks by J. Saramäki, M. Kivelä, J.-P. Onnela,
372
+ K. Kaski, and J. Kertész, Physical Review E, 75 027105 (2007).
373
+ http://jponnela.com/web_documents/a9.pdf
374
+ .. [2] Marcus Kaiser, Mean clustering coefficients: the role of isolated
375
+ nodes and leafs on clustering measures for small-world networks.
376
+ https://arxiv.org/abs/0802.2512
377
+ """
378
+ c = clustering(G, nodes, weight=weight).values()
379
+ if not count_zeros:
380
+ c = [v for v in c if abs(v) > 0]
381
+ return sum(c) / len(c)
382
+
383
+
384
+ @nx._dispatchable(edge_attrs="weight")
385
+ def clustering(G, nodes=None, weight=None):
386
+ r"""Compute the clustering coefficient for nodes.
387
+
388
+ For unweighted graphs, the clustering of a node :math:`u`
389
+ is the fraction of possible triangles through that node that exist,
390
+
391
+ .. math::
392
+
393
+ c_u = \frac{2 T(u)}{deg(u)(deg(u)-1)},
394
+
395
+ where :math:`T(u)` is the number of triangles through node :math:`u` and
396
+ :math:`deg(u)` is the degree of :math:`u`.
397
+
398
+ For weighted graphs, there are several ways to define clustering [1]_.
399
+ the one used here is defined
400
+ as the geometric average of the subgraph edge weights [2]_,
401
+
402
+ .. math::
403
+
404
+ c_u = \frac{1}{deg(u)(deg(u)-1))}
405
+ \sum_{vw} (\hat{w}_{uv} \hat{w}_{uw} \hat{w}_{vw})^{1/3}.
406
+
407
+ The edge weights :math:`\hat{w}_{uv}` are normalized by the maximum weight
408
+ in the network :math:`\hat{w}_{uv} = w_{uv}/\max(w)`.
409
+
410
+ The value of :math:`c_u` is assigned to 0 if :math:`deg(u) < 2`.
411
+
412
+ Additionally, this weighted definition has been generalized to support negative edge weights [3]_.
413
+
414
+ For directed graphs, the clustering is similarly defined as the fraction
415
+ of all possible directed triangles or geometric average of the subgraph
416
+ edge weights for unweighted and weighted directed graph respectively [4]_.
417
+
418
+ .. math::
419
+
420
+ c_u = \frac{T(u)}{2(deg^{tot}(u)(deg^{tot}(u)-1) - 2deg^{\leftrightarrow}(u))},
421
+
422
+ where :math:`T(u)` is the number of directed triangles through node
423
+ :math:`u`, :math:`deg^{tot}(u)` is the sum of in degree and out degree of
424
+ :math:`u` and :math:`deg^{\leftrightarrow}(u)` is the reciprocal degree of
425
+ :math:`u`.
426
+
427
+
428
+ Parameters
429
+ ----------
430
+ G : graph
431
+
432
+ nodes : node, iterable of nodes, or None (default=None)
433
+ If a singleton node, return the number of triangles for that node.
434
+ If an iterable, compute the number of triangles for each of those nodes.
435
+ If `None` (the default) compute the number of triangles for all nodes in `G`.
436
+
437
+ weight : string or None, optional (default=None)
438
+ The edge attribute that holds the numerical value used as a weight.
439
+ If None, then each edge has weight 1.
440
+
441
+ Returns
442
+ -------
443
+ out : float, or dictionary
444
+ Clustering coefficient at specified nodes
445
+
446
+ Examples
447
+ --------
448
+ >>> G = nx.complete_graph(5)
449
+ >>> print(nx.clustering(G, 0))
450
+ 1.0
451
+ >>> print(nx.clustering(G))
452
+ {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0}
453
+
454
+ Notes
455
+ -----
456
+ Self loops are ignored.
457
+
458
+ References
459
+ ----------
460
+ .. [1] Generalizations of the clustering coefficient to weighted
461
+ complex networks by J. Saramäki, M. Kivelä, J.-P. Onnela,
462
+ K. Kaski, and J. Kertész, Physical Review E, 75 027105 (2007).
463
+ http://jponnela.com/web_documents/a9.pdf
464
+ .. [2] Intensity and coherence of motifs in weighted complex
465
+ networks by J. P. Onnela, J. Saramäki, J. Kertész, and K. Kaski,
466
+ Physical Review E, 71(6), 065103 (2005).
467
+ .. [3] Generalization of Clustering Coefficients to Signed Correlation Networks
468
+ by G. Costantini and M. Perugini, PloS one, 9(2), e88669 (2014).
469
+ .. [4] Clustering in complex directed networks by G. Fagiolo,
470
+ Physical Review E, 76(2), 026107 (2007).
471
+ """
472
+ if G.is_directed():
473
+ if weight is not None:
474
+ td_iter = _directed_weighted_triangles_and_degree_iter(G, nodes, weight)
475
+ clusterc = {
476
+ v: 0 if t == 0 else t / ((dt * (dt - 1) - 2 * db) * 2)
477
+ for v, dt, db, t in td_iter
478
+ }
479
+ else:
480
+ td_iter = _directed_triangles_and_degree_iter(G, nodes)
481
+ clusterc = {
482
+ v: 0 if t == 0 else t / ((dt * (dt - 1) - 2 * db) * 2)
483
+ for v, dt, db, t in td_iter
484
+ }
485
+ else:
486
+ # The formula 2*T/(d*(d-1)) from docs is t/(d*(d-1)) here b/c t==2*T
487
+ if weight is not None:
488
+ td_iter = _weighted_triangles_and_degree_iter(G, nodes, weight)
489
+ clusterc = {v: 0 if t == 0 else t / (d * (d - 1)) for v, d, t in td_iter}
490
+ else:
491
+ td_iter = _triangles_and_degree_iter(G, nodes)
492
+ clusterc = {v: 0 if t == 0 else t / (d * (d - 1)) for v, d, t, _ in td_iter}
493
+ if nodes in G:
494
+ # Return the value of the sole entry in the dictionary.
495
+ return clusterc[nodes]
496
+ return clusterc
497
+
498
+
499
+ @nx._dispatchable
500
+ def transitivity(G):
501
+ r"""Compute graph transitivity, the fraction of all possible triangles
502
+ present in G.
503
+
504
+ Possible triangles are identified by the number of "triads"
505
+ (two edges with a shared vertex).
506
+
507
+ The transitivity is
508
+
509
+ .. math::
510
+
511
+ T = 3\frac{\#triangles}{\#triads}.
512
+
513
+ Parameters
514
+ ----------
515
+ G : graph
516
+
517
+ Returns
518
+ -------
519
+ out : float
520
+ Transitivity
521
+
522
+ Notes
523
+ -----
524
+ Self loops are ignored.
525
+
526
+ Examples
527
+ --------
528
+ >>> G = nx.complete_graph(5)
529
+ >>> print(nx.transitivity(G))
530
+ 1.0
531
+ """
532
+ triangles_contri = [
533
+ (t, d * (d - 1)) for v, d, t, _ in _triangles_and_degree_iter(G)
534
+ ]
535
+ # If the graph is empty
536
+ if len(triangles_contri) == 0:
537
+ return 0
538
+ triangles, contri = map(sum, zip(*triangles_contri))
539
+ return 0 if triangles == 0 else triangles / contri
540
+
541
+
542
+ @nx._dispatchable
543
+ def square_clustering(G, nodes=None):
544
+ r"""Compute the squares clustering coefficient for nodes.
545
+
546
+ For each node return the fraction of possible squares that exist at
547
+ the node [1]_
548
+
549
+ .. math::
550
+ C_4(v) = \frac{ \sum_{u=1}^{k_v}
551
+ \sum_{w=u+1}^{k_v} q_v(u,w) }{ \sum_{u=1}^{k_v}
552
+ \sum_{w=u+1}^{k_v} [a_v(u,w) + q_v(u,w)]},
553
+
554
+ where :math:`q_v(u,w)` are the number of common neighbors of :math:`u` and
555
+ :math:`w` other than :math:`v` (ie squares), and :math:`a_v(u,w) = (k_u -
556
+ (1+q_v(u,w)+\theta_{uv})) + (k_w - (1+q_v(u,w)+\theta_{uw}))`, where
557
+ :math:`\theta_{uw} = 1` if :math:`u` and :math:`w` are connected and 0
558
+ otherwise. [2]_
559
+
560
+ Parameters
561
+ ----------
562
+ G : graph
563
+
564
+ nodes : container of nodes, optional (default=all nodes in G)
565
+ Compute clustering for nodes in this container.
566
+
567
+ Returns
568
+ -------
569
+ c4 : dictionary
570
+ A dictionary keyed by node with the square clustering coefficient value.
571
+
572
+ Examples
573
+ --------
574
+ >>> G = nx.complete_graph(5)
575
+ >>> print(nx.square_clustering(G, 0))
576
+ 1.0
577
+ >>> print(nx.square_clustering(G))
578
+ {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0}
579
+
580
+ Notes
581
+ -----
582
+ Self loops are ignored.
583
+
584
+ While :math:`C_3(v)` (triangle clustering) gives the probability that
585
+ two neighbors of node v are connected with each other, :math:`C_4(v)` is
586
+ the probability that two neighbors of node v share a common
587
+ neighbor different from v. This algorithm can be applied to both
588
+ bipartite and unipartite networks.
589
+
590
+ References
591
+ ----------
592
+ .. [1] Pedro G. Lind, Marta C. González, and Hans J. Herrmann. 2005
593
+ Cycles and clustering in bipartite networks.
594
+ Physical Review E (72) 056127.
595
+ .. [2] Zhang, Peng et al. Clustering Coefficient and Community Structure of
596
+ Bipartite Networks. Physica A: Statistical Mechanics and its Applications 387.27 (2008): 6869–6875.
597
+ https://arxiv.org/abs/0710.0117v1
598
+ """
599
+ if nodes is None:
600
+ node_iter = G
601
+ else:
602
+ node_iter = G.nbunch_iter(nodes)
603
+ clustering = {}
604
+ _G_adj = G._adj
605
+
606
+ class GAdj(dict):
607
+ """Calculate (and cache) node neighbor sets excluding self-loops."""
608
+
609
+ def __missing__(self, v):
610
+ v_neighbors = self[v] = set(_G_adj[v])
611
+ v_neighbors.discard(v) # Ignore self-loops
612
+ return v_neighbors
613
+
614
+ G_adj = GAdj() # Values are sets of neighbors (no self-loops)
615
+
616
+ for v in node_iter:
617
+ v_neighbors = G_adj[v]
618
+ v_degrees_m1 = len(v_neighbors) - 1 # degrees[v] - 1 (used below)
619
+ if v_degrees_m1 <= 0:
620
+ # Can't form a square without at least two neighbors
621
+ clustering[v] = 0
622
+ continue
623
+
624
+ # Count squares with nodes u-v-w-x from the current node v.
625
+ # Terms of the denominator: potential = uw_degrees - uw_count - triangles - squares
626
+ # uw_degrees: degrees[u] + degrees[w] for each u-w combo
627
+ uw_degrees = 0
628
+ # uw_count: 1 for each u and 1 for each w for all combos (degrees * (degrees - 1))
629
+ uw_count = len(v_neighbors) * v_degrees_m1
630
+ # triangles: 1 for each edge where u-w or w-u are connected (i.e. triangles)
631
+ triangles = 0
632
+ # squares: the number of squares (also the numerator)
633
+ squares = 0
634
+
635
+ # Iterate over all neighbors
636
+ for u in v_neighbors:
637
+ u_neighbors = G_adj[u]
638
+ uw_degrees += len(u_neighbors) * v_degrees_m1
639
+ # P2 from https://arxiv.org/abs/2007.11111
640
+ p2 = len(u_neighbors & v_neighbors)
641
+ # triangles is C_3, sigma_4 from https://arxiv.org/abs/2007.11111
642
+ # This double-counts triangles compared to `triangles` function
643
+ triangles += p2
644
+ # squares is C_4, sigma_12 from https://arxiv.org/abs/2007.11111
645
+ # Include this term, b/c a neighbor u can also be a neighbor of neighbor x
646
+ squares += p2 * (p2 - 1) # Will divide by 2 later
647
+
648
+ # And iterate over all neighbors of neighbors.
649
+ # These nodes x may be the corners opposite v in squares u-v-w-x.
650
+ two_hop_neighbors = set.union(*(G_adj[u] for u in v_neighbors))
651
+ two_hop_neighbors -= v_neighbors # Neighbors already counted above
652
+ two_hop_neighbors.discard(v)
653
+ for x in two_hop_neighbors:
654
+ p2 = len(v_neighbors & G_adj[x])
655
+ squares += p2 * (p2 - 1) # Will divide by 2 later
656
+
657
+ squares //= 2
658
+ potential = uw_degrees - uw_count - triangles - squares
659
+ if potential > 0:
660
+ clustering[v] = squares / potential
661
+ else:
662
+ clustering[v] = 0
663
+ if nodes in G:
664
+ # Return the value of the sole entry in the dictionary.
665
+ return clustering[nodes]
666
+ return clustering
667
+
668
+
669
+ @not_implemented_for("directed")
670
+ @nx._dispatchable
671
+ def generalized_degree(G, nodes=None):
672
+ r"""Compute the generalized degree for nodes.
673
+
674
+ For each node, the generalized degree shows how many edges of given
675
+ triangle multiplicity the node is connected to. The triangle multiplicity
676
+ of an edge is the number of triangles an edge participates in. The
677
+ generalized degree of node :math:`i` can be written as a vector
678
+ :math:`\mathbf{k}_i=(k_i^{(0)}, \dotsc, k_i^{(N-2)})` where
679
+ :math:`k_i^{(j)}` is the number of edges attached to node :math:`i` that
680
+ participate in :math:`j` triangles.
681
+
682
+ Parameters
683
+ ----------
684
+ G : graph
685
+
686
+ nodes : container of nodes, optional (default=all nodes in G)
687
+ Compute the generalized degree for nodes in this container.
688
+
689
+ Returns
690
+ -------
691
+ out : Counter, or dictionary of Counters
692
+ Generalized degree of specified nodes. The Counter is keyed by edge
693
+ triangle multiplicity.
694
+
695
+ Examples
696
+ --------
697
+ >>> G = nx.complete_graph(5)
698
+ >>> print(nx.generalized_degree(G, 0))
699
+ Counter({3: 4})
700
+ >>> print(nx.generalized_degree(G))
701
+ {0: Counter({3: 4}), 1: Counter({3: 4}), 2: Counter({3: 4}), 3: Counter({3: 4}), 4: Counter({3: 4})}
702
+
703
+ To recover the number of triangles attached to a node:
704
+
705
+ >>> k1 = nx.generalized_degree(G, 0)
706
+ >>> sum([k * v for k, v in k1.items()]) / 2 == nx.triangles(G, 0)
707
+ True
708
+
709
+ Notes
710
+ -----
711
+ Self loops are ignored.
712
+
713
+ In a network of N nodes, the highest triangle multiplicity an edge can have
714
+ is N-2.
715
+
716
+ The return value does not include a `zero` entry if no edges of a
717
+ particular triangle multiplicity are present.
718
+
719
+ The number of triangles node :math:`i` is attached to can be recovered from
720
+ the generalized degree :math:`\mathbf{k}_i=(k_i^{(0)}, \dotsc,
721
+ k_i^{(N-2)})` by :math:`(k_i^{(1)}+2k_i^{(2)}+\dotsc +(N-2)k_i^{(N-2)})/2`.
722
+
723
+ References
724
+ ----------
725
+ .. [1] Networks with arbitrary edge multiplicities by V. Zlatić,
726
+ D. Garlaschelli and G. Caldarelli, EPL (Europhysics Letters),
727
+ Volume 97, Number 2 (2012).
728
+ https://iopscience.iop.org/article/10.1209/0295-5075/97/28005
729
+ """
730
+ if nodes in G:
731
+ return next(_triangles_and_degree_iter(G, nodes))[3]
732
+ return {v: gd for v, d, t, gd in _triangles_and_degree_iter(G, nodes)}
lib/python3.12/site-packages/networkx/algorithms/communicability_alg.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Communicability.
3
+ """
4
+
5
+ import networkx as nx
6
+ from networkx.utils import not_implemented_for
7
+
8
+ __all__ = ["communicability", "communicability_exp"]
9
+
10
+
11
+ @not_implemented_for("directed")
12
+ @not_implemented_for("multigraph")
13
+ @nx._dispatchable
14
+ def communicability(G):
15
+ r"""Returns communicability between all pairs of nodes in G.
16
+
17
+ The communicability between pairs of nodes in G is the sum of
18
+ walks of different lengths starting at node u and ending at node v.
19
+
20
+ Parameters
21
+ ----------
22
+ G: graph
23
+
24
+ Returns
25
+ -------
26
+ comm: dictionary of dictionaries
27
+ Dictionary of dictionaries keyed by nodes with communicability
28
+ as the value.
29
+
30
+ Raises
31
+ ------
32
+ NetworkXError
33
+ If the graph is not undirected and simple.
34
+
35
+ See Also
36
+ --------
37
+ communicability_exp:
38
+ Communicability between all pairs of nodes in G using spectral
39
+ decomposition.
40
+ communicability_betweenness_centrality:
41
+ Communicability betweenness centrality for each node in G.
42
+
43
+ Notes
44
+ -----
45
+ This algorithm uses a spectral decomposition of the adjacency matrix.
46
+ Let G=(V,E) be a simple undirected graph. Using the connection between
47
+ the powers of the adjacency matrix and the number of walks in the graph,
48
+ the communicability between nodes `u` and `v` based on the graph spectrum
49
+ is [1]_
50
+
51
+ .. math::
52
+ C(u,v)=\sum_{j=1}^{n}\phi_{j}(u)\phi_{j}(v)e^{\lambda_{j}},
53
+
54
+ where `\phi_{j}(u)` is the `u\rm{th}` element of the `j\rm{th}` orthonormal
55
+ eigenvector of the adjacency matrix associated with the eigenvalue
56
+ `\lambda_{j}`.
57
+
58
+ References
59
+ ----------
60
+ .. [1] Ernesto Estrada, Naomichi Hatano,
61
+ "Communicability in complex networks",
62
+ Phys. Rev. E 77, 036111 (2008).
63
+ https://arxiv.org/abs/0707.0756
64
+
65
+ Examples
66
+ --------
67
+ >>> G = nx.Graph([(0, 1), (1, 2), (1, 5), (5, 4), (2, 4), (2, 3), (4, 3), (3, 6)])
68
+ >>> c = nx.communicability(G)
69
+ """
70
+ import numpy as np
71
+
72
+ nodelist = list(G) # ordering of nodes in matrix
73
+ A = nx.to_numpy_array(G, nodelist)
74
+ # convert to 0-1 matrix
75
+ A[A != 0.0] = 1
76
+ w, vec = np.linalg.eigh(A)
77
+ expw = np.exp(w)
78
+ mapping = dict(zip(nodelist, range(len(nodelist))))
79
+ c = {}
80
+ # computing communicabilities
81
+ for u in G:
82
+ c[u] = {}
83
+ for v in G:
84
+ s = 0
85
+ p = mapping[u]
86
+ q = mapping[v]
87
+ for j in range(len(nodelist)):
88
+ s += vec[:, j][p] * vec[:, j][q] * expw[j]
89
+ c[u][v] = float(s)
90
+ return c
91
+
92
+
93
+ @not_implemented_for("directed")
94
+ @not_implemented_for("multigraph")
95
+ @nx._dispatchable
96
+ def communicability_exp(G):
97
+ r"""Returns communicability between all pairs of nodes in G.
98
+
99
+ Communicability between pair of node (u,v) of node in G is the sum of
100
+ walks of different lengths starting at node u and ending at node v.
101
+
102
+ Parameters
103
+ ----------
104
+ G: graph
105
+
106
+ Returns
107
+ -------
108
+ comm: dictionary of dictionaries
109
+ Dictionary of dictionaries keyed by nodes with communicability
110
+ as the value.
111
+
112
+ Raises
113
+ ------
114
+ NetworkXError
115
+ If the graph is not undirected and simple.
116
+
117
+ See Also
118
+ --------
119
+ communicability:
120
+ Communicability between pairs of nodes in G.
121
+ communicability_betweenness_centrality:
122
+ Communicability betweenness centrality for each node in G.
123
+
124
+ Notes
125
+ -----
126
+ This algorithm uses matrix exponentiation of the adjacency matrix.
127
+
128
+ Let G=(V,E) be a simple undirected graph. Using the connection between
129
+ the powers of the adjacency matrix and the number of walks in the graph,
130
+ the communicability between nodes u and v is [1]_,
131
+
132
+ .. math::
133
+ C(u,v) = (e^A)_{uv},
134
+
135
+ where `A` is the adjacency matrix of G.
136
+
137
+ References
138
+ ----------
139
+ .. [1] Ernesto Estrada, Naomichi Hatano,
140
+ "Communicability in complex networks",
141
+ Phys. Rev. E 77, 036111 (2008).
142
+ https://arxiv.org/abs/0707.0756
143
+
144
+ Examples
145
+ --------
146
+ >>> G = nx.Graph([(0, 1), (1, 2), (1, 5), (5, 4), (2, 4), (2, 3), (4, 3), (3, 6)])
147
+ >>> c = nx.communicability_exp(G)
148
+ """
149
+ import scipy as sp
150
+
151
+ nodelist = list(G) # ordering of nodes in matrix
152
+ A = nx.to_numpy_array(G, nodelist)
153
+ # convert to 0-1 matrix
154
+ A[A != 0.0] = 1
155
+ # communicability matrix
156
+ expA = sp.linalg.expm(A)
157
+ mapping = dict(zip(nodelist, range(len(nodelist))))
158
+ c = {}
159
+ for u in G:
160
+ c[u] = {}
161
+ for v in G:
162
+ c[u][v] = float(expA[mapping[u], mapping[v]])
163
+ return c
lib/python3.12/site-packages/networkx/algorithms/connectivity/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Connectivity and cut algorithms"""
2
+
3
+ from .connectivity import *
4
+ from .cuts import *
5
+ from .edge_augmentation import *
6
+ from .edge_kcomponents import *
7
+ from .disjoint_paths import *
8
+ from .kcomponents import *
9
+ from .kcutsets import *
10
+ from .stoerwagner import *
11
+ from .utils import *
lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (500 Bytes). View file
 
lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/connectivity.cpython-312.pyc ADDED
Binary file (30.1 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/cuts.cpython-312.pyc ADDED
Binary file (23.7 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/disjoint_paths.cpython-312.pyc ADDED
Binary file (14.9 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/edge_augmentation.cpython-312.pyc ADDED
Binary file (47.6 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/edge_kcomponents.cpython-312.pyc ADDED
Binary file (22.7 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/kcomponents.cpython-312.pyc ADDED
Binary file (10.4 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/kcutsets.cpython-312.pyc ADDED
Binary file (8.78 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/stoerwagner.cpython-312.pyc ADDED
Binary file (6.21 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/utils.cpython-312.pyc ADDED
Binary file (4.32 kB). View file
 
lib/python3.12/site-packages/networkx/algorithms/connectivity/connectivity.py ADDED
@@ -0,0 +1,811 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Flow based connectivity algorithms
3
+ """
4
+
5
+ import itertools
6
+ from operator import itemgetter
7
+
8
+ import networkx as nx
9
+
10
+ # Define the default maximum flow function to use in all flow based
11
+ # connectivity algorithms.
12
+ from networkx.algorithms.flow import (
13
+ boykov_kolmogorov,
14
+ build_residual_network,
15
+ dinitz,
16
+ edmonds_karp,
17
+ preflow_push,
18
+ shortest_augmenting_path,
19
+ )
20
+
21
+ from .utils import build_auxiliary_edge_connectivity, build_auxiliary_node_connectivity
22
+
23
+ default_flow_func = edmonds_karp
24
+
25
+ __all__ = [
26
+ "average_node_connectivity",
27
+ "local_node_connectivity",
28
+ "node_connectivity",
29
+ "local_edge_connectivity",
30
+ "edge_connectivity",
31
+ "all_pairs_node_connectivity",
32
+ ]
33
+
34
+
35
+ @nx._dispatchable(graphs={"G": 0, "auxiliary?": 4}, preserve_graph_attrs={"auxiliary"})
36
+ def local_node_connectivity(
37
+ G, s, t, flow_func=None, auxiliary=None, residual=None, cutoff=None
38
+ ):
39
+ r"""Computes local node connectivity for nodes s and t.
40
+
41
+ Local node connectivity for two non adjacent nodes s and t is the
42
+ minimum number of nodes that must be removed (along with their incident
43
+ edges) to disconnect them.
44
+
45
+ This is a flow based implementation of node connectivity. We compute the
46
+ maximum flow on an auxiliary digraph build from the original input
47
+ graph (see below for details).
48
+
49
+ Parameters
50
+ ----------
51
+ G : NetworkX graph
52
+ Undirected graph
53
+
54
+ s : node
55
+ Source node
56
+
57
+ t : node
58
+ Target node
59
+
60
+ flow_func : function
61
+ A function for computing the maximum flow among a pair of nodes.
62
+ The function has to accept at least three parameters: a Digraph,
63
+ a source node, and a target node. And return a residual network
64
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
65
+ details). If flow_func is None, the default maximum flow function
66
+ (:meth:`edmonds_karp`) is used. See below for details. The choice
67
+ of the default function may change from version to version and
68
+ should not be relied on. Default value: None.
69
+
70
+ auxiliary : NetworkX DiGraph
71
+ Auxiliary digraph to compute flow based node connectivity. It has
72
+ to have a graph attribute called mapping with a dictionary mapping
73
+ node names in G and in the auxiliary digraph. If provided
74
+ it will be reused instead of recreated. Default value: None.
75
+
76
+ residual : NetworkX DiGraph
77
+ Residual network to compute maximum flow. If provided it will be
78
+ reused instead of recreated. Default value: None.
79
+
80
+ cutoff : integer, float, or None (default: None)
81
+ If specified, the maximum flow algorithm will terminate when the
82
+ flow value reaches or exceeds the cutoff. This only works for flows
83
+ that support the cutoff parameter (most do) and is ignored otherwise.
84
+
85
+ Returns
86
+ -------
87
+ K : integer
88
+ local node connectivity for nodes s and t
89
+
90
+ Examples
91
+ --------
92
+ This function is not imported in the base NetworkX namespace, so you
93
+ have to explicitly import it from the connectivity package:
94
+
95
+ >>> from networkx.algorithms.connectivity import local_node_connectivity
96
+
97
+ We use in this example the platonic icosahedral graph, which has node
98
+ connectivity 5.
99
+
100
+ >>> G = nx.icosahedral_graph()
101
+ >>> local_node_connectivity(G, 0, 6)
102
+ 5
103
+
104
+ If you need to compute local connectivity on several pairs of
105
+ nodes in the same graph, it is recommended that you reuse the
106
+ data structures that NetworkX uses in the computation: the
107
+ auxiliary digraph for node connectivity, and the residual
108
+ network for the underlying maximum flow computation.
109
+
110
+ Example of how to compute local node connectivity among
111
+ all pairs of nodes of the platonic icosahedral graph reusing
112
+ the data structures.
113
+
114
+ >>> import itertools
115
+ >>> # You also have to explicitly import the function for
116
+ >>> # building the auxiliary digraph from the connectivity package
117
+ >>> from networkx.algorithms.connectivity import build_auxiliary_node_connectivity
118
+ >>> H = build_auxiliary_node_connectivity(G)
119
+ >>> # And the function for building the residual network from the
120
+ >>> # flow package
121
+ >>> from networkx.algorithms.flow import build_residual_network
122
+ >>> # Note that the auxiliary digraph has an edge attribute named capacity
123
+ >>> R = build_residual_network(H, "capacity")
124
+ >>> result = dict.fromkeys(G, dict())
125
+ >>> # Reuse the auxiliary digraph and the residual network by passing them
126
+ >>> # as parameters
127
+ >>> for u, v in itertools.combinations(G, 2):
128
+ ... k = local_node_connectivity(G, u, v, auxiliary=H, residual=R)
129
+ ... result[u][v] = k
130
+ >>> all(result[u][v] == 5 for u, v in itertools.combinations(G, 2))
131
+ True
132
+
133
+ You can also use alternative flow algorithms for computing node
134
+ connectivity. For instance, in dense networks the algorithm
135
+ :meth:`shortest_augmenting_path` will usually perform better than
136
+ the default :meth:`edmonds_karp` which is faster for sparse
137
+ networks with highly skewed degree distributions. Alternative flow
138
+ functions have to be explicitly imported from the flow package.
139
+
140
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
141
+ >>> local_node_connectivity(G, 0, 6, flow_func=shortest_augmenting_path)
142
+ 5
143
+
144
+ Notes
145
+ -----
146
+ This is a flow based implementation of node connectivity. We compute the
147
+ maximum flow using, by default, the :meth:`edmonds_karp` algorithm (see:
148
+ :meth:`maximum_flow`) on an auxiliary digraph build from the original
149
+ input graph:
150
+
151
+ For an undirected graph G having `n` nodes and `m` edges we derive a
152
+ directed graph H with `2n` nodes and `2m+n` arcs by replacing each
153
+ original node `v` with two nodes `v_A`, `v_B` linked by an (internal)
154
+ arc in H. Then for each edge (`u`, `v`) in G we add two arcs
155
+ (`u_B`, `v_A`) and (`v_B`, `u_A`) in H. Finally we set the attribute
156
+ capacity = 1 for each arc in H [1]_ .
157
+
158
+ For a directed graph G having `n` nodes and `m` arcs we derive a
159
+ directed graph H with `2n` nodes and `m+n` arcs by replacing each
160
+ original node `v` with two nodes `v_A`, `v_B` linked by an (internal)
161
+ arc (`v_A`, `v_B`) in H. Then for each arc (`u`, `v`) in G we add one arc
162
+ (`u_B`, `v_A`) in H. Finally we set the attribute capacity = 1 for
163
+ each arc in H.
164
+
165
+ This is equal to the local node connectivity because the value of
166
+ a maximum s-t-flow is equal to the capacity of a minimum s-t-cut.
167
+
168
+ See also
169
+ --------
170
+ :meth:`local_edge_connectivity`
171
+ :meth:`node_connectivity`
172
+ :meth:`minimum_node_cut`
173
+ :meth:`maximum_flow`
174
+ :meth:`edmonds_karp`
175
+ :meth:`preflow_push`
176
+ :meth:`shortest_augmenting_path`
177
+
178
+ References
179
+ ----------
180
+ .. [1] Kammer, Frank and Hanjo Taubig. Graph Connectivity. in Brandes and
181
+ Erlebach, 'Network Analysis: Methodological Foundations', Lecture
182
+ Notes in Computer Science, Volume 3418, Springer-Verlag, 2005.
183
+ http://www.informatik.uni-augsburg.de/thi/personen/kammer/Graph_Connectivity.pdf
184
+
185
+ """
186
+ if flow_func is None:
187
+ flow_func = default_flow_func
188
+
189
+ if auxiliary is None:
190
+ H = build_auxiliary_node_connectivity(G)
191
+ else:
192
+ H = auxiliary
193
+
194
+ mapping = H.graph.get("mapping", None)
195
+ if mapping is None:
196
+ raise nx.NetworkXError("Invalid auxiliary digraph.")
197
+
198
+ kwargs = {"flow_func": flow_func, "residual": residual}
199
+
200
+ if flow_func is not preflow_push:
201
+ kwargs["cutoff"] = cutoff
202
+
203
+ if flow_func is shortest_augmenting_path:
204
+ kwargs["two_phase"] = True
205
+
206
+ return nx.maximum_flow_value(H, f"{mapping[s]}B", f"{mapping[t]}A", **kwargs)
207
+
208
+
209
+ @nx._dispatchable
210
+ def node_connectivity(G, s=None, t=None, flow_func=None):
211
+ r"""Returns node connectivity for a graph or digraph G.
212
+
213
+ Node connectivity is equal to the minimum number of nodes that
214
+ must be removed to disconnect G or render it trivial. If source
215
+ and target nodes are provided, this function returns the local node
216
+ connectivity: the minimum number of nodes that must be removed to break
217
+ all paths from source to target in G.
218
+
219
+ Parameters
220
+ ----------
221
+ G : NetworkX graph
222
+ Undirected graph
223
+
224
+ s : node
225
+ Source node. Optional. Default value: None.
226
+
227
+ t : node
228
+ Target node. Optional. Default value: None.
229
+
230
+ flow_func : function
231
+ A function for computing the maximum flow among a pair of nodes.
232
+ The function has to accept at least three parameters: a Digraph,
233
+ a source node, and a target node. And return a residual network
234
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
235
+ details). If flow_func is None, the default maximum flow function
236
+ (:meth:`edmonds_karp`) is used. See below for details. The
237
+ choice of the default function may change from version
238
+ to version and should not be relied on. Default value: None.
239
+
240
+ Returns
241
+ -------
242
+ K : integer
243
+ Node connectivity of G, or local node connectivity if source
244
+ and target are provided.
245
+
246
+ Examples
247
+ --------
248
+ >>> # Platonic icosahedral graph is 5-node-connected
249
+ >>> G = nx.icosahedral_graph()
250
+ >>> nx.node_connectivity(G)
251
+ 5
252
+
253
+ You can use alternative flow algorithms for the underlying maximum
254
+ flow computation. In dense networks the algorithm
255
+ :meth:`shortest_augmenting_path` will usually perform better
256
+ than the default :meth:`edmonds_karp`, which is faster for
257
+ sparse networks with highly skewed degree distributions. Alternative
258
+ flow functions have to be explicitly imported from the flow package.
259
+
260
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
261
+ >>> nx.node_connectivity(G, flow_func=shortest_augmenting_path)
262
+ 5
263
+
264
+ If you specify a pair of nodes (source and target) as parameters,
265
+ this function returns the value of local node connectivity.
266
+
267
+ >>> nx.node_connectivity(G, 3, 7)
268
+ 5
269
+
270
+ If you need to perform several local computations among different
271
+ pairs of nodes on the same graph, it is recommended that you reuse
272
+ the data structures used in the maximum flow computations. See
273
+ :meth:`local_node_connectivity` for details.
274
+
275
+ Notes
276
+ -----
277
+ This is a flow based implementation of node connectivity. The
278
+ algorithm works by solving $O((n-\delta-1+\delta(\delta-1)/2))$
279
+ maximum flow problems on an auxiliary digraph. Where $\delta$
280
+ is the minimum degree of G. For details about the auxiliary
281
+ digraph and the computation of local node connectivity see
282
+ :meth:`local_node_connectivity`. This implementation is based
283
+ on algorithm 11 in [1]_.
284
+
285
+ See also
286
+ --------
287
+ :meth:`local_node_connectivity`
288
+ :meth:`edge_connectivity`
289
+ :meth:`maximum_flow`
290
+ :meth:`edmonds_karp`
291
+ :meth:`preflow_push`
292
+ :meth:`shortest_augmenting_path`
293
+
294
+ References
295
+ ----------
296
+ .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms.
297
+ http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
298
+
299
+ """
300
+ if (s is not None and t is None) or (s is None and t is not None):
301
+ raise nx.NetworkXError("Both source and target must be specified.")
302
+
303
+ # Local node connectivity
304
+ if s is not None and t is not None:
305
+ if s not in G:
306
+ raise nx.NetworkXError(f"node {s} not in graph")
307
+ if t not in G:
308
+ raise nx.NetworkXError(f"node {t} not in graph")
309
+ return local_node_connectivity(G, s, t, flow_func=flow_func)
310
+
311
+ # Global node connectivity
312
+ if G.is_directed():
313
+ if not nx.is_weakly_connected(G):
314
+ return 0
315
+ iter_func = itertools.permutations
316
+ # It is necessary to consider both predecessors
317
+ # and successors for directed graphs
318
+
319
+ def neighbors(v):
320
+ return itertools.chain.from_iterable([G.predecessors(v), G.successors(v)])
321
+
322
+ else:
323
+ if not nx.is_connected(G):
324
+ return 0
325
+ iter_func = itertools.combinations
326
+ neighbors = G.neighbors
327
+
328
+ # Reuse the auxiliary digraph and the residual network
329
+ H = build_auxiliary_node_connectivity(G)
330
+ R = build_residual_network(H, "capacity")
331
+ kwargs = {"flow_func": flow_func, "auxiliary": H, "residual": R}
332
+
333
+ # Pick a node with minimum degree
334
+ # Node connectivity is bounded by degree.
335
+ v, K = min(G.degree(), key=itemgetter(1))
336
+ # compute local node connectivity with all its non-neighbors nodes
337
+ for w in set(G) - set(neighbors(v)) - {v}:
338
+ kwargs["cutoff"] = K
339
+ K = min(K, local_node_connectivity(G, v, w, **kwargs))
340
+ # Also for non adjacent pairs of neighbors of v
341
+ for x, y in iter_func(neighbors(v), 2):
342
+ if y in G[x]:
343
+ continue
344
+ kwargs["cutoff"] = K
345
+ K = min(K, local_node_connectivity(G, x, y, **kwargs))
346
+
347
+ return K
348
+
349
+
350
+ @nx._dispatchable
351
+ def average_node_connectivity(G, flow_func=None):
352
+ r"""Returns the average connectivity of a graph G.
353
+
354
+ The average connectivity `\bar{\kappa}` of a graph G is the average
355
+ of local node connectivity over all pairs of nodes of G [1]_ .
356
+
357
+ .. math::
358
+
359
+ \bar{\kappa}(G) = \frac{\sum_{u,v} \kappa_{G}(u,v)}{{n \choose 2}}
360
+
361
+ Parameters
362
+ ----------
363
+
364
+ G : NetworkX graph
365
+ Undirected graph
366
+
367
+ flow_func : function
368
+ A function for computing the maximum flow among a pair of nodes.
369
+ The function has to accept at least three parameters: a Digraph,
370
+ a source node, and a target node. And return a residual network
371
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
372
+ details). If flow_func is None, the default maximum flow function
373
+ (:meth:`edmonds_karp`) is used. See :meth:`local_node_connectivity`
374
+ for details. The choice of the default function may change from
375
+ version to version and should not be relied on. Default value: None.
376
+
377
+ Returns
378
+ -------
379
+ K : float
380
+ Average node connectivity
381
+
382
+ See also
383
+ --------
384
+ :meth:`local_node_connectivity`
385
+ :meth:`node_connectivity`
386
+ :meth:`edge_connectivity`
387
+ :meth:`maximum_flow`
388
+ :meth:`edmonds_karp`
389
+ :meth:`preflow_push`
390
+ :meth:`shortest_augmenting_path`
391
+
392
+ References
393
+ ----------
394
+ .. [1] Beineke, L., O. Oellermann, and R. Pippert (2002). The average
395
+ connectivity of a graph. Discrete mathematics 252(1-3), 31-45.
396
+ http://www.sciencedirect.com/science/article/pii/S0012365X01001807
397
+
398
+ """
399
+ if G.is_directed():
400
+ iter_func = itertools.permutations
401
+ else:
402
+ iter_func = itertools.combinations
403
+
404
+ # Reuse the auxiliary digraph and the residual network
405
+ H = build_auxiliary_node_connectivity(G)
406
+ R = build_residual_network(H, "capacity")
407
+ kwargs = {"flow_func": flow_func, "auxiliary": H, "residual": R}
408
+
409
+ num, den = 0, 0
410
+ for u, v in iter_func(G, 2):
411
+ num += local_node_connectivity(G, u, v, **kwargs)
412
+ den += 1
413
+
414
+ if den == 0: # Null Graph
415
+ return 0
416
+ return num / den
417
+
418
+
419
+ @nx._dispatchable
420
+ def all_pairs_node_connectivity(G, nbunch=None, flow_func=None):
421
+ """Compute node connectivity between all pairs of nodes of G.
422
+
423
+ Parameters
424
+ ----------
425
+ G : NetworkX graph
426
+ Undirected graph
427
+
428
+ nbunch: container
429
+ Container of nodes. If provided node connectivity will be computed
430
+ only over pairs of nodes in nbunch.
431
+
432
+ flow_func : function
433
+ A function for computing the maximum flow among a pair of nodes.
434
+ The function has to accept at least three parameters: a Digraph,
435
+ a source node, and a target node. And return a residual network
436
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
437
+ details). If flow_func is None, the default maximum flow function
438
+ (:meth:`edmonds_karp`) is used. See below for details. The
439
+ choice of the default function may change from version
440
+ to version and should not be relied on. Default value: None.
441
+
442
+ Returns
443
+ -------
444
+ all_pairs : dict
445
+ A dictionary with node connectivity between all pairs of nodes
446
+ in G, or in nbunch if provided.
447
+
448
+ See also
449
+ --------
450
+ :meth:`local_node_connectivity`
451
+ :meth:`edge_connectivity`
452
+ :meth:`local_edge_connectivity`
453
+ :meth:`maximum_flow`
454
+ :meth:`edmonds_karp`
455
+ :meth:`preflow_push`
456
+ :meth:`shortest_augmenting_path`
457
+
458
+ """
459
+ if nbunch is None:
460
+ nbunch = G
461
+ else:
462
+ nbunch = set(nbunch)
463
+
464
+ directed = G.is_directed()
465
+ if directed:
466
+ iter_func = itertools.permutations
467
+ else:
468
+ iter_func = itertools.combinations
469
+
470
+ all_pairs = {n: {} for n in nbunch}
471
+
472
+ # Reuse auxiliary digraph and residual network
473
+ H = build_auxiliary_node_connectivity(G)
474
+ mapping = H.graph["mapping"]
475
+ R = build_residual_network(H, "capacity")
476
+ kwargs = {"flow_func": flow_func, "auxiliary": H, "residual": R}
477
+
478
+ for u, v in iter_func(nbunch, 2):
479
+ K = local_node_connectivity(G, u, v, **kwargs)
480
+ all_pairs[u][v] = K
481
+ if not directed:
482
+ all_pairs[v][u] = K
483
+
484
+ return all_pairs
485
+
486
+
487
+ @nx._dispatchable(graphs={"G": 0, "auxiliary?": 4})
488
+ def local_edge_connectivity(
489
+ G, s, t, flow_func=None, auxiliary=None, residual=None, cutoff=None
490
+ ):
491
+ r"""Returns local edge connectivity for nodes s and t in G.
492
+
493
+ Local edge connectivity for two nodes s and t is the minimum number
494
+ of edges that must be removed to disconnect them.
495
+
496
+ This is a flow based implementation of edge connectivity. We compute the
497
+ maximum flow on an auxiliary digraph build from the original
498
+ network (see below for details). This is equal to the local edge
499
+ connectivity because the value of a maximum s-t-flow is equal to the
500
+ capacity of a minimum s-t-cut (Ford and Fulkerson theorem) [1]_ .
501
+
502
+ Parameters
503
+ ----------
504
+ G : NetworkX graph
505
+ Undirected or directed graph
506
+
507
+ s : node
508
+ Source node
509
+
510
+ t : node
511
+ Target node
512
+
513
+ flow_func : function
514
+ A function for computing the maximum flow among a pair of nodes.
515
+ The function has to accept at least three parameters: a Digraph,
516
+ a source node, and a target node. And return a residual network
517
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
518
+ details). If flow_func is None, the default maximum flow function
519
+ (:meth:`edmonds_karp`) is used. See below for details. The
520
+ choice of the default function may change from version
521
+ to version and should not be relied on. Default value: None.
522
+
523
+ auxiliary : NetworkX DiGraph
524
+ Auxiliary digraph for computing flow based edge connectivity. If
525
+ provided it will be reused instead of recreated. Default value: None.
526
+
527
+ residual : NetworkX DiGraph
528
+ Residual network to compute maximum flow. If provided it will be
529
+ reused instead of recreated. Default value: None.
530
+
531
+ cutoff : integer, float, or None (default: None)
532
+ If specified, the maximum flow algorithm will terminate when the
533
+ flow value reaches or exceeds the cutoff. This only works for flows
534
+ that support the cutoff parameter (most do) and is ignored otherwise.
535
+
536
+ Returns
537
+ -------
538
+ K : integer
539
+ local edge connectivity for nodes s and t.
540
+
541
+ Examples
542
+ --------
543
+ This function is not imported in the base NetworkX namespace, so you
544
+ have to explicitly import it from the connectivity package:
545
+
546
+ >>> from networkx.algorithms.connectivity import local_edge_connectivity
547
+
548
+ We use in this example the platonic icosahedral graph, which has edge
549
+ connectivity 5.
550
+
551
+ >>> G = nx.icosahedral_graph()
552
+ >>> local_edge_connectivity(G, 0, 6)
553
+ 5
554
+
555
+ If you need to compute local connectivity on several pairs of
556
+ nodes in the same graph, it is recommended that you reuse the
557
+ data structures that NetworkX uses in the computation: the
558
+ auxiliary digraph for edge connectivity, and the residual
559
+ network for the underlying maximum flow computation.
560
+
561
+ Example of how to compute local edge connectivity among
562
+ all pairs of nodes of the platonic icosahedral graph reusing
563
+ the data structures.
564
+
565
+ >>> import itertools
566
+ >>> # You also have to explicitly import the function for
567
+ >>> # building the auxiliary digraph from the connectivity package
568
+ >>> from networkx.algorithms.connectivity import build_auxiliary_edge_connectivity
569
+ >>> H = build_auxiliary_edge_connectivity(G)
570
+ >>> # And the function for building the residual network from the
571
+ >>> # flow package
572
+ >>> from networkx.algorithms.flow import build_residual_network
573
+ >>> # Note that the auxiliary digraph has an edge attribute named capacity
574
+ >>> R = build_residual_network(H, "capacity")
575
+ >>> result = dict.fromkeys(G, dict())
576
+ >>> # Reuse the auxiliary digraph and the residual network by passing them
577
+ >>> # as parameters
578
+ >>> for u, v in itertools.combinations(G, 2):
579
+ ... k = local_edge_connectivity(G, u, v, auxiliary=H, residual=R)
580
+ ... result[u][v] = k
581
+ >>> all(result[u][v] == 5 for u, v in itertools.combinations(G, 2))
582
+ True
583
+
584
+ You can also use alternative flow algorithms for computing edge
585
+ connectivity. For instance, in dense networks the algorithm
586
+ :meth:`shortest_augmenting_path` will usually perform better than
587
+ the default :meth:`edmonds_karp` which is faster for sparse
588
+ networks with highly skewed degree distributions. Alternative flow
589
+ functions have to be explicitly imported from the flow package.
590
+
591
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
592
+ >>> local_edge_connectivity(G, 0, 6, flow_func=shortest_augmenting_path)
593
+ 5
594
+
595
+ Notes
596
+ -----
597
+ This is a flow based implementation of edge connectivity. We compute the
598
+ maximum flow using, by default, the :meth:`edmonds_karp` algorithm on an
599
+ auxiliary digraph build from the original input graph:
600
+
601
+ If the input graph is undirected, we replace each edge (`u`,`v`) with
602
+ two reciprocal arcs (`u`, `v`) and (`v`, `u`) and then we set the attribute
603
+ 'capacity' for each arc to 1. If the input graph is directed we simply
604
+ add the 'capacity' attribute. This is an implementation of algorithm 1
605
+ in [1]_.
606
+
607
+ The maximum flow in the auxiliary network is equal to the local edge
608
+ connectivity because the value of a maximum s-t-flow is equal to the
609
+ capacity of a minimum s-t-cut (Ford and Fulkerson theorem).
610
+
611
+ See also
612
+ --------
613
+ :meth:`edge_connectivity`
614
+ :meth:`local_node_connectivity`
615
+ :meth:`node_connectivity`
616
+ :meth:`maximum_flow`
617
+ :meth:`edmonds_karp`
618
+ :meth:`preflow_push`
619
+ :meth:`shortest_augmenting_path`
620
+
621
+ References
622
+ ----------
623
+ .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms.
624
+ http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
625
+
626
+ """
627
+ if flow_func is None:
628
+ flow_func = default_flow_func
629
+
630
+ if auxiliary is None:
631
+ H = build_auxiliary_edge_connectivity(G)
632
+ else:
633
+ H = auxiliary
634
+
635
+ kwargs = {"flow_func": flow_func, "residual": residual}
636
+
637
+ if flow_func is not preflow_push:
638
+ kwargs["cutoff"] = cutoff
639
+
640
+ if flow_func is shortest_augmenting_path:
641
+ kwargs["two_phase"] = True
642
+
643
+ return nx.maximum_flow_value(H, s, t, **kwargs)
644
+
645
+
646
+ @nx._dispatchable
647
+ def edge_connectivity(G, s=None, t=None, flow_func=None, cutoff=None):
648
+ r"""Returns the edge connectivity of the graph or digraph G.
649
+
650
+ The edge connectivity is equal to the minimum number of edges that
651
+ must be removed to disconnect G or render it trivial. If source
652
+ and target nodes are provided, this function returns the local edge
653
+ connectivity: the minimum number of edges that must be removed to
654
+ break all paths from source to target in G.
655
+
656
+ Parameters
657
+ ----------
658
+ G : NetworkX graph
659
+ Undirected or directed graph
660
+
661
+ s : node
662
+ Source node. Optional. Default value: None.
663
+
664
+ t : node
665
+ Target node. Optional. Default value: None.
666
+
667
+ flow_func : function
668
+ A function for computing the maximum flow among a pair of nodes.
669
+ The function has to accept at least three parameters: a Digraph,
670
+ a source node, and a target node. And return a residual network
671
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
672
+ details). If flow_func is None, the default maximum flow function
673
+ (:meth:`edmonds_karp`) is used. See below for details. The
674
+ choice of the default function may change from version
675
+ to version and should not be relied on. Default value: None.
676
+
677
+ cutoff : integer, float, or None (default: None)
678
+ If specified, the maximum flow algorithm will terminate when the
679
+ flow value reaches or exceeds the cutoff. This only works for flows
680
+ that support the cutoff parameter (most do) and is ignored otherwise.
681
+
682
+ Returns
683
+ -------
684
+ K : integer
685
+ Edge connectivity for G, or local edge connectivity if source
686
+ and target were provided
687
+
688
+ Examples
689
+ --------
690
+ >>> # Platonic icosahedral graph is 5-edge-connected
691
+ >>> G = nx.icosahedral_graph()
692
+ >>> nx.edge_connectivity(G)
693
+ 5
694
+
695
+ You can use alternative flow algorithms for the underlying
696
+ maximum flow computation. In dense networks the algorithm
697
+ :meth:`shortest_augmenting_path` will usually perform better
698
+ than the default :meth:`edmonds_karp`, which is faster for
699
+ sparse networks with highly skewed degree distributions.
700
+ Alternative flow functions have to be explicitly imported
701
+ from the flow package.
702
+
703
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
704
+ >>> nx.edge_connectivity(G, flow_func=shortest_augmenting_path)
705
+ 5
706
+
707
+ If you specify a pair of nodes (source and target) as parameters,
708
+ this function returns the value of local edge connectivity.
709
+
710
+ >>> nx.edge_connectivity(G, 3, 7)
711
+ 5
712
+
713
+ If you need to perform several local computations among different
714
+ pairs of nodes on the same graph, it is recommended that you reuse
715
+ the data structures used in the maximum flow computations. See
716
+ :meth:`local_edge_connectivity` for details.
717
+
718
+ Notes
719
+ -----
720
+ This is a flow based implementation of global edge connectivity.
721
+ For undirected graphs the algorithm works by finding a 'small'
722
+ dominating set of nodes of G (see algorithm 7 in [1]_ ) and
723
+ computing local maximum flow (see :meth:`local_edge_connectivity`)
724
+ between an arbitrary node in the dominating set and the rest of
725
+ nodes in it. This is an implementation of algorithm 6 in [1]_ .
726
+ For directed graphs, the algorithm does n calls to the maximum
727
+ flow function. This is an implementation of algorithm 8 in [1]_ .
728
+
729
+ See also
730
+ --------
731
+ :meth:`local_edge_connectivity`
732
+ :meth:`local_node_connectivity`
733
+ :meth:`node_connectivity`
734
+ :meth:`maximum_flow`
735
+ :meth:`edmonds_karp`
736
+ :meth:`preflow_push`
737
+ :meth:`shortest_augmenting_path`
738
+ :meth:`k_edge_components`
739
+ :meth:`k_edge_subgraphs`
740
+
741
+ References
742
+ ----------
743
+ .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms.
744
+ http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
745
+
746
+ """
747
+ if (s is not None and t is None) or (s is None and t is not None):
748
+ raise nx.NetworkXError("Both source and target must be specified.")
749
+
750
+ # Local edge connectivity
751
+ if s is not None and t is not None:
752
+ if s not in G:
753
+ raise nx.NetworkXError(f"node {s} not in graph")
754
+ if t not in G:
755
+ raise nx.NetworkXError(f"node {t} not in graph")
756
+ return local_edge_connectivity(G, s, t, flow_func=flow_func, cutoff=cutoff)
757
+
758
+ # Global edge connectivity
759
+ # reuse auxiliary digraph and residual network
760
+ H = build_auxiliary_edge_connectivity(G)
761
+ R = build_residual_network(H, "capacity")
762
+ kwargs = {"flow_func": flow_func, "auxiliary": H, "residual": R}
763
+
764
+ if G.is_directed():
765
+ # Algorithm 8 in [1]
766
+ if not nx.is_weakly_connected(G):
767
+ return 0
768
+
769
+ # initial value for \lambda is minimum degree
770
+ L = min(d for n, d in G.degree())
771
+ nodes = list(G)
772
+ n = len(nodes)
773
+
774
+ if cutoff is not None:
775
+ L = min(cutoff, L)
776
+
777
+ for i in range(n):
778
+ kwargs["cutoff"] = L
779
+ try:
780
+ L = min(L, local_edge_connectivity(G, nodes[i], nodes[i + 1], **kwargs))
781
+ except IndexError: # last node!
782
+ L = min(L, local_edge_connectivity(G, nodes[i], nodes[0], **kwargs))
783
+ return L
784
+ else: # undirected
785
+ # Algorithm 6 in [1]
786
+ if not nx.is_connected(G):
787
+ return 0
788
+
789
+ # initial value for \lambda is minimum degree
790
+ L = min(d for n, d in G.degree())
791
+
792
+ if cutoff is not None:
793
+ L = min(cutoff, L)
794
+
795
+ # A dominating set is \lambda-covering
796
+ # We need a dominating set with at least two nodes
797
+ for node in G:
798
+ D = nx.dominating_set(G, start_with=node)
799
+ v = D.pop()
800
+ if D:
801
+ break
802
+ else:
803
+ # in complete graphs the dominating sets will always be of one node
804
+ # thus we return min degree
805
+ return L
806
+
807
+ for w in D:
808
+ kwargs["cutoff"] = L
809
+ L = min(L, local_edge_connectivity(G, v, w, **kwargs))
810
+
811
+ return L
lib/python3.12/site-packages/networkx/algorithms/connectivity/cuts.py ADDED
@@ -0,0 +1,616 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Flow based cut algorithms
3
+ """
4
+
5
+ import itertools
6
+
7
+ import networkx as nx
8
+
9
+ # Define the default maximum flow function to use in all flow based
10
+ # cut algorithms.
11
+ from networkx.algorithms.flow import build_residual_network, edmonds_karp
12
+
13
+ from .utils import build_auxiliary_edge_connectivity, build_auxiliary_node_connectivity
14
+
15
+ default_flow_func = edmonds_karp
16
+
17
+ __all__ = [
18
+ "minimum_st_node_cut",
19
+ "minimum_node_cut",
20
+ "minimum_st_edge_cut",
21
+ "minimum_edge_cut",
22
+ ]
23
+
24
+
25
+ @nx._dispatchable(
26
+ graphs={"G": 0, "auxiliary?": 4},
27
+ preserve_edge_attrs={"auxiliary": {"capacity": float("inf")}},
28
+ preserve_graph_attrs={"auxiliary"},
29
+ )
30
+ def minimum_st_edge_cut(G, s, t, flow_func=None, auxiliary=None, residual=None):
31
+ """Returns the edges of the cut-set of a minimum (s, t)-cut.
32
+
33
+ This function returns the set of edges of minimum cardinality that,
34
+ if removed, would destroy all paths among source and target in G.
35
+ Edge weights are not considered. See :meth:`minimum_cut` for
36
+ computing minimum cuts considering edge weights.
37
+
38
+ Parameters
39
+ ----------
40
+ G : NetworkX graph
41
+
42
+ s : node
43
+ Source node for the flow.
44
+
45
+ t : node
46
+ Sink node for the flow.
47
+
48
+ auxiliary : NetworkX DiGraph
49
+ Auxiliary digraph to compute flow based node connectivity. It has
50
+ to have a graph attribute called mapping with a dictionary mapping
51
+ node names in G and in the auxiliary digraph. If provided
52
+ it will be reused instead of recreated. Default value: None.
53
+
54
+ flow_func : function
55
+ A function for computing the maximum flow among a pair of nodes.
56
+ The function has to accept at least three parameters: a Digraph,
57
+ a source node, and a target node. And return a residual network
58
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
59
+ details). If flow_func is None, the default maximum flow function
60
+ (:meth:`edmonds_karp`) is used. See :meth:`node_connectivity` for
61
+ details. The choice of the default function may change from version
62
+ to version and should not be relied on. Default value: None.
63
+
64
+ residual : NetworkX DiGraph
65
+ Residual network to compute maximum flow. If provided it will be
66
+ reused instead of recreated. Default value: None.
67
+
68
+ Returns
69
+ -------
70
+ cutset : set
71
+ Set of edges that, if removed from the graph, will disconnect it.
72
+
73
+ See also
74
+ --------
75
+ :meth:`minimum_cut`
76
+ :meth:`minimum_node_cut`
77
+ :meth:`minimum_edge_cut`
78
+ :meth:`stoer_wagner`
79
+ :meth:`node_connectivity`
80
+ :meth:`edge_connectivity`
81
+ :meth:`maximum_flow`
82
+ :meth:`edmonds_karp`
83
+ :meth:`preflow_push`
84
+ :meth:`shortest_augmenting_path`
85
+
86
+ Examples
87
+ --------
88
+ This function is not imported in the base NetworkX namespace, so you
89
+ have to explicitly import it from the connectivity package:
90
+
91
+ >>> from networkx.algorithms.connectivity import minimum_st_edge_cut
92
+
93
+ We use in this example the platonic icosahedral graph, which has edge
94
+ connectivity 5.
95
+
96
+ >>> G = nx.icosahedral_graph()
97
+ >>> len(minimum_st_edge_cut(G, 0, 6))
98
+ 5
99
+
100
+ If you need to compute local edge cuts on several pairs of
101
+ nodes in the same graph, it is recommended that you reuse the
102
+ data structures that NetworkX uses in the computation: the
103
+ auxiliary digraph for edge connectivity, and the residual
104
+ network for the underlying maximum flow computation.
105
+
106
+ Example of how to compute local edge cuts among all pairs of
107
+ nodes of the platonic icosahedral graph reusing the data
108
+ structures.
109
+
110
+ >>> import itertools
111
+ >>> # You also have to explicitly import the function for
112
+ >>> # building the auxiliary digraph from the connectivity package
113
+ >>> from networkx.algorithms.connectivity import build_auxiliary_edge_connectivity
114
+ >>> H = build_auxiliary_edge_connectivity(G)
115
+ >>> # And the function for building the residual network from the
116
+ >>> # flow package
117
+ >>> from networkx.algorithms.flow import build_residual_network
118
+ >>> # Note that the auxiliary digraph has an edge attribute named capacity
119
+ >>> R = build_residual_network(H, "capacity")
120
+ >>> result = dict.fromkeys(G, dict())
121
+ >>> # Reuse the auxiliary digraph and the residual network by passing them
122
+ >>> # as parameters
123
+ >>> for u, v in itertools.combinations(G, 2):
124
+ ... k = len(minimum_st_edge_cut(G, u, v, auxiliary=H, residual=R))
125
+ ... result[u][v] = k
126
+ >>> all(result[u][v] == 5 for u, v in itertools.combinations(G, 2))
127
+ True
128
+
129
+ You can also use alternative flow algorithms for computing edge
130
+ cuts. For instance, in dense networks the algorithm
131
+ :meth:`shortest_augmenting_path` will usually perform better than
132
+ the default :meth:`edmonds_karp` which is faster for sparse
133
+ networks with highly skewed degree distributions. Alternative flow
134
+ functions have to be explicitly imported from the flow package.
135
+
136
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
137
+ >>> len(minimum_st_edge_cut(G, 0, 6, flow_func=shortest_augmenting_path))
138
+ 5
139
+
140
+ """
141
+ if flow_func is None:
142
+ flow_func = default_flow_func
143
+
144
+ if auxiliary is None:
145
+ H = build_auxiliary_edge_connectivity(G)
146
+ else:
147
+ H = auxiliary
148
+
149
+ kwargs = {"capacity": "capacity", "flow_func": flow_func, "residual": residual}
150
+
151
+ cut_value, partition = nx.minimum_cut(H, s, t, **kwargs)
152
+ reachable, non_reachable = partition
153
+ # Any edge in the original graph linking the two sets in the
154
+ # partition is part of the edge cutset
155
+ cutset = set()
156
+ for u, nbrs in ((n, G[n]) for n in reachable):
157
+ cutset.update((u, v) for v in nbrs if v in non_reachable)
158
+
159
+ return cutset
160
+
161
+
162
+ @nx._dispatchable(
163
+ graphs={"G": 0, "auxiliary?": 4},
164
+ preserve_node_attrs={"auxiliary": {"id": None}},
165
+ preserve_graph_attrs={"auxiliary"},
166
+ )
167
+ def minimum_st_node_cut(G, s, t, flow_func=None, auxiliary=None, residual=None):
168
+ r"""Returns a set of nodes of minimum cardinality that disconnect source
169
+ from target in G.
170
+
171
+ This function returns the set of nodes of minimum cardinality that,
172
+ if removed, would destroy all paths among source and target in G.
173
+
174
+ Parameters
175
+ ----------
176
+ G : NetworkX graph
177
+
178
+ s : node
179
+ Source node.
180
+
181
+ t : node
182
+ Target node.
183
+
184
+ flow_func : function
185
+ A function for computing the maximum flow among a pair of nodes.
186
+ The function has to accept at least three parameters: a Digraph,
187
+ a source node, and a target node. And return a residual network
188
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
189
+ details). If flow_func is None, the default maximum flow function
190
+ (:meth:`edmonds_karp`) is used. See below for details. The choice
191
+ of the default function may change from version to version and
192
+ should not be relied on. Default value: None.
193
+
194
+ auxiliary : NetworkX DiGraph
195
+ Auxiliary digraph to compute flow based node connectivity. It has
196
+ to have a graph attribute called mapping with a dictionary mapping
197
+ node names in G and in the auxiliary digraph. If provided
198
+ it will be reused instead of recreated. Default value: None.
199
+
200
+ residual : NetworkX DiGraph
201
+ Residual network to compute maximum flow. If provided it will be
202
+ reused instead of recreated. Default value: None.
203
+
204
+ Returns
205
+ -------
206
+ cutset : set
207
+ Set of nodes that, if removed, would destroy all paths between
208
+ source and target in G.
209
+
210
+ Returns an empty set if source and target are either in different
211
+ components or are directly connected by an edge, as no node removal
212
+ can destroy the path.
213
+
214
+ Examples
215
+ --------
216
+ This function is not imported in the base NetworkX namespace, so you
217
+ have to explicitly import it from the connectivity package:
218
+
219
+ >>> from networkx.algorithms.connectivity import minimum_st_node_cut
220
+
221
+ We use in this example the platonic icosahedral graph, which has node
222
+ connectivity 5.
223
+
224
+ >>> G = nx.icosahedral_graph()
225
+ >>> len(minimum_st_node_cut(G, 0, 6))
226
+ 5
227
+
228
+ If you need to compute local st cuts between several pairs of
229
+ nodes in the same graph, it is recommended that you reuse the
230
+ data structures that NetworkX uses in the computation: the
231
+ auxiliary digraph for node connectivity and node cuts, and the
232
+ residual network for the underlying maximum flow computation.
233
+
234
+ Example of how to compute local st node cuts reusing the data
235
+ structures:
236
+
237
+ >>> # You also have to explicitly import the function for
238
+ >>> # building the auxiliary digraph from the connectivity package
239
+ >>> from networkx.algorithms.connectivity import build_auxiliary_node_connectivity
240
+ >>> H = build_auxiliary_node_connectivity(G)
241
+ >>> # And the function for building the residual network from the
242
+ >>> # flow package
243
+ >>> from networkx.algorithms.flow import build_residual_network
244
+ >>> # Note that the auxiliary digraph has an edge attribute named capacity
245
+ >>> R = build_residual_network(H, "capacity")
246
+ >>> # Reuse the auxiliary digraph and the residual network by passing them
247
+ >>> # as parameters
248
+ >>> len(minimum_st_node_cut(G, 0, 6, auxiliary=H, residual=R))
249
+ 5
250
+
251
+ You can also use alternative flow algorithms for computing minimum st
252
+ node cuts. For instance, in dense networks the algorithm
253
+ :meth:`shortest_augmenting_path` will usually perform better than
254
+ the default :meth:`edmonds_karp` which is faster for sparse
255
+ networks with highly skewed degree distributions. Alternative flow
256
+ functions have to be explicitly imported from the flow package.
257
+
258
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
259
+ >>> len(minimum_st_node_cut(G, 0, 6, flow_func=shortest_augmenting_path))
260
+ 5
261
+
262
+ Notes
263
+ -----
264
+ This is a flow based implementation of minimum node cut. The algorithm
265
+ is based in solving a number of maximum flow computations to determine
266
+ the capacity of the minimum cut on an auxiliary directed network that
267
+ corresponds to the minimum node cut of G. It handles both directed
268
+ and undirected graphs. This implementation is based on algorithm 11
269
+ in [1]_.
270
+
271
+ See also
272
+ --------
273
+ :meth:`minimum_node_cut`
274
+ :meth:`minimum_edge_cut`
275
+ :meth:`stoer_wagner`
276
+ :meth:`node_connectivity`
277
+ :meth:`edge_connectivity`
278
+ :meth:`maximum_flow`
279
+ :meth:`edmonds_karp`
280
+ :meth:`preflow_push`
281
+ :meth:`shortest_augmenting_path`
282
+
283
+ References
284
+ ----------
285
+ .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms.
286
+ http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
287
+
288
+ """
289
+ if auxiliary is None:
290
+ H = build_auxiliary_node_connectivity(G)
291
+ else:
292
+ H = auxiliary
293
+
294
+ mapping = H.graph.get("mapping", None)
295
+ if mapping is None:
296
+ raise nx.NetworkXError("Invalid auxiliary digraph.")
297
+ if G.has_edge(s, t) or G.has_edge(t, s):
298
+ return set()
299
+ kwargs = {"flow_func": flow_func, "residual": residual, "auxiliary": H}
300
+
301
+ # The edge cut in the auxiliary digraph corresponds to the node cut in the
302
+ # original graph.
303
+ edge_cut = minimum_st_edge_cut(H, f"{mapping[s]}B", f"{mapping[t]}A", **kwargs)
304
+ # Each node in the original graph maps to two nodes of the auxiliary graph
305
+ node_cut = {H.nodes[node]["id"] for edge in edge_cut for node in edge}
306
+ return node_cut - {s, t}
307
+
308
+
309
+ @nx._dispatchable
310
+ def minimum_node_cut(G, s=None, t=None, flow_func=None):
311
+ r"""Returns a set of nodes of minimum cardinality that disconnects G.
312
+
313
+ If source and target nodes are provided, this function returns the
314
+ set of nodes of minimum cardinality that, if removed, would destroy
315
+ all paths among source and target in G. If not, it returns a set
316
+ of nodes of minimum cardinality that disconnects G.
317
+
318
+ Parameters
319
+ ----------
320
+ G : NetworkX graph
321
+
322
+ s : node
323
+ Source node. Optional. Default value: None.
324
+
325
+ t : node
326
+ Target node. Optional. Default value: None.
327
+
328
+ flow_func : function
329
+ A function for computing the maximum flow among a pair of nodes.
330
+ The function has to accept at least three parameters: a Digraph,
331
+ a source node, and a target node. And return a residual network
332
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
333
+ details). If flow_func is None, the default maximum flow function
334
+ (:meth:`edmonds_karp`) is used. See below for details. The
335
+ choice of the default function may change from version
336
+ to version and should not be relied on. Default value: None.
337
+
338
+ Returns
339
+ -------
340
+ cutset : set
341
+ Set of nodes that, if removed, would disconnect G. If source
342
+ and target nodes are provided, the set contains the nodes that
343
+ if removed, would destroy all paths between source and target.
344
+
345
+ Examples
346
+ --------
347
+ >>> # Platonic icosahedral graph has node connectivity 5
348
+ >>> G = nx.icosahedral_graph()
349
+ >>> node_cut = nx.minimum_node_cut(G)
350
+ >>> len(node_cut)
351
+ 5
352
+
353
+ You can use alternative flow algorithms for the underlying maximum
354
+ flow computation. In dense networks the algorithm
355
+ :meth:`shortest_augmenting_path` will usually perform better
356
+ than the default :meth:`edmonds_karp`, which is faster for
357
+ sparse networks with highly skewed degree distributions. Alternative
358
+ flow functions have to be explicitly imported from the flow package.
359
+
360
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
361
+ >>> node_cut == nx.minimum_node_cut(G, flow_func=shortest_augmenting_path)
362
+ True
363
+
364
+ If you specify a pair of nodes (source and target) as parameters,
365
+ this function returns a local st node cut.
366
+
367
+ >>> len(nx.minimum_node_cut(G, 3, 7))
368
+ 5
369
+
370
+ If you need to perform several local st cuts among different
371
+ pairs of nodes on the same graph, it is recommended that you reuse
372
+ the data structures used in the maximum flow computations. See
373
+ :meth:`minimum_st_node_cut` for details.
374
+
375
+ Notes
376
+ -----
377
+ This is a flow based implementation of minimum node cut. The algorithm
378
+ is based in solving a number of maximum flow computations to determine
379
+ the capacity of the minimum cut on an auxiliary directed network that
380
+ corresponds to the minimum node cut of G. It handles both directed
381
+ and undirected graphs. This implementation is based on algorithm 11
382
+ in [1]_.
383
+
384
+ See also
385
+ --------
386
+ :meth:`minimum_st_node_cut`
387
+ :meth:`minimum_cut`
388
+ :meth:`minimum_edge_cut`
389
+ :meth:`stoer_wagner`
390
+ :meth:`node_connectivity`
391
+ :meth:`edge_connectivity`
392
+ :meth:`maximum_flow`
393
+ :meth:`edmonds_karp`
394
+ :meth:`preflow_push`
395
+ :meth:`shortest_augmenting_path`
396
+
397
+ References
398
+ ----------
399
+ .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms.
400
+ http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
401
+
402
+ """
403
+ if (s is not None and t is None) or (s is None and t is not None):
404
+ raise nx.NetworkXError("Both source and target must be specified.")
405
+
406
+ # Local minimum node cut.
407
+ if s is not None and t is not None:
408
+ if s not in G:
409
+ raise nx.NetworkXError(f"node {s} not in graph")
410
+ if t not in G:
411
+ raise nx.NetworkXError(f"node {t} not in graph")
412
+ return minimum_st_node_cut(G, s, t, flow_func=flow_func)
413
+
414
+ # Global minimum node cut.
415
+ # Analog to the algorithm 11 for global node connectivity in [1].
416
+ if G.is_directed():
417
+ if not nx.is_weakly_connected(G):
418
+ raise nx.NetworkXError("Input graph is not connected")
419
+ iter_func = itertools.permutations
420
+
421
+ def neighbors(v):
422
+ return itertools.chain.from_iterable([G.predecessors(v), G.successors(v)])
423
+
424
+ else:
425
+ if not nx.is_connected(G):
426
+ raise nx.NetworkXError("Input graph is not connected")
427
+ iter_func = itertools.combinations
428
+ neighbors = G.neighbors
429
+
430
+ # Reuse the auxiliary digraph and the residual network.
431
+ H = build_auxiliary_node_connectivity(G)
432
+ R = build_residual_network(H, "capacity")
433
+ kwargs = {"flow_func": flow_func, "auxiliary": H, "residual": R}
434
+
435
+ # Choose a node with minimum degree.
436
+ v = min(G, key=G.degree)
437
+ # Initial node cutset is all neighbors of the node with minimum degree.
438
+ min_cut = set(G[v])
439
+ # Compute st node cuts between v and all its non-neighbors nodes in G.
440
+ for w in set(G) - set(neighbors(v)) - {v}:
441
+ this_cut = minimum_st_node_cut(G, v, w, **kwargs)
442
+ if len(min_cut) >= len(this_cut):
443
+ min_cut = this_cut
444
+ # Also for non adjacent pairs of neighbors of v.
445
+ for x, y in iter_func(neighbors(v), 2):
446
+ if y in G[x]:
447
+ continue
448
+ this_cut = minimum_st_node_cut(G, x, y, **kwargs)
449
+ if len(min_cut) >= len(this_cut):
450
+ min_cut = this_cut
451
+
452
+ return min_cut
453
+
454
+
455
+ @nx._dispatchable
456
+ def minimum_edge_cut(G, s=None, t=None, flow_func=None):
457
+ r"""Returns a set of edges of minimum cardinality that disconnects G.
458
+
459
+ If source and target nodes are provided, this function returns the
460
+ set of edges of minimum cardinality that, if removed, would break
461
+ all paths among source and target in G. If not, it returns a set of
462
+ edges of minimum cardinality that disconnects G.
463
+
464
+ Parameters
465
+ ----------
466
+ G : NetworkX graph
467
+
468
+ s : node
469
+ Source node. Optional. Default value: None.
470
+
471
+ t : node
472
+ Target node. Optional. Default value: None.
473
+
474
+ flow_func : function
475
+ A function for computing the maximum flow among a pair of nodes.
476
+ The function has to accept at least three parameters: a Digraph,
477
+ a source node, and a target node. And return a residual network
478
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
479
+ details). If flow_func is None, the default maximum flow function
480
+ (:meth:`edmonds_karp`) is used. See below for details. The
481
+ choice of the default function may change from version
482
+ to version and should not be relied on. Default value: None.
483
+
484
+ Returns
485
+ -------
486
+ cutset : set
487
+ Set of edges that, if removed, would disconnect G. If source
488
+ and target nodes are provided, the set contains the edges that
489
+ if removed, would destroy all paths between source and target.
490
+
491
+ Examples
492
+ --------
493
+ >>> # Platonic icosahedral graph has edge connectivity 5
494
+ >>> G = nx.icosahedral_graph()
495
+ >>> len(nx.minimum_edge_cut(G))
496
+ 5
497
+
498
+ You can use alternative flow algorithms for the underlying
499
+ maximum flow computation. In dense networks the algorithm
500
+ :meth:`shortest_augmenting_path` will usually perform better
501
+ than the default :meth:`edmonds_karp`, which is faster for
502
+ sparse networks with highly skewed degree distributions.
503
+ Alternative flow functions have to be explicitly imported
504
+ from the flow package.
505
+
506
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
507
+ >>> len(nx.minimum_edge_cut(G, flow_func=shortest_augmenting_path))
508
+ 5
509
+
510
+ If you specify a pair of nodes (source and target) as parameters,
511
+ this function returns the value of local edge connectivity.
512
+
513
+ >>> nx.edge_connectivity(G, 3, 7)
514
+ 5
515
+
516
+ If you need to perform several local computations among different
517
+ pairs of nodes on the same graph, it is recommended that you reuse
518
+ the data structures used in the maximum flow computations. See
519
+ :meth:`local_edge_connectivity` for details.
520
+
521
+ Notes
522
+ -----
523
+ This is a flow based implementation of minimum edge cut. For
524
+ undirected graphs the algorithm works by finding a 'small' dominating
525
+ set of nodes of G (see algorithm 7 in [1]_) and computing the maximum
526
+ flow between an arbitrary node in the dominating set and the rest of
527
+ nodes in it. This is an implementation of algorithm 6 in [1]_. For
528
+ directed graphs, the algorithm does n calls to the max flow function.
529
+ The function raises an error if the directed graph is not weakly
530
+ connected and returns an empty set if it is weakly connected.
531
+ It is an implementation of algorithm 8 in [1]_.
532
+
533
+ See also
534
+ --------
535
+ :meth:`minimum_st_edge_cut`
536
+ :meth:`minimum_node_cut`
537
+ :meth:`stoer_wagner`
538
+ :meth:`node_connectivity`
539
+ :meth:`edge_connectivity`
540
+ :meth:`maximum_flow`
541
+ :meth:`edmonds_karp`
542
+ :meth:`preflow_push`
543
+ :meth:`shortest_augmenting_path`
544
+
545
+ References
546
+ ----------
547
+ .. [1] Abdol-Hossein Esfahanian. Connectivity Algorithms.
548
+ http://www.cse.msu.edu/~cse835/Papers/Graph_connectivity_revised.pdf
549
+
550
+ """
551
+ if (s is not None and t is None) or (s is None and t is not None):
552
+ raise nx.NetworkXError("Both source and target must be specified.")
553
+
554
+ # reuse auxiliary digraph and residual network
555
+ H = build_auxiliary_edge_connectivity(G)
556
+ R = build_residual_network(H, "capacity")
557
+ kwargs = {"flow_func": flow_func, "residual": R, "auxiliary": H}
558
+
559
+ # Local minimum edge cut if s and t are not None
560
+ if s is not None and t is not None:
561
+ if s not in G:
562
+ raise nx.NetworkXError(f"node {s} not in graph")
563
+ if t not in G:
564
+ raise nx.NetworkXError(f"node {t} not in graph")
565
+ return minimum_st_edge_cut(H, s, t, **kwargs)
566
+
567
+ # Global minimum edge cut
568
+ # Analog to the algorithm for global edge connectivity
569
+ if G.is_directed():
570
+ # Based on algorithm 8 in [1]
571
+ if not nx.is_weakly_connected(G):
572
+ raise nx.NetworkXError("Input graph is not connected")
573
+
574
+ # Initial cutset is all edges of a node with minimum degree
575
+ node = min(G, key=G.degree)
576
+ min_cut = set(G.edges(node))
577
+ nodes = list(G)
578
+ n = len(nodes)
579
+ for i in range(n):
580
+ try:
581
+ this_cut = minimum_st_edge_cut(H, nodes[i], nodes[i + 1], **kwargs)
582
+ if len(this_cut) <= len(min_cut):
583
+ min_cut = this_cut
584
+ except IndexError: # Last node!
585
+ this_cut = minimum_st_edge_cut(H, nodes[i], nodes[0], **kwargs)
586
+ if len(this_cut) <= len(min_cut):
587
+ min_cut = this_cut
588
+
589
+ return min_cut
590
+
591
+ else: # undirected
592
+ # Based on algorithm 6 in [1]
593
+ if not nx.is_connected(G):
594
+ raise nx.NetworkXError("Input graph is not connected")
595
+
596
+ # Initial cutset is all edges of a node with minimum degree
597
+ node = min(G, key=G.degree)
598
+ min_cut = set(G.edges(node))
599
+ # A dominating set is \lambda-covering
600
+ # We need a dominating set with at least two nodes
601
+ for node in G:
602
+ D = nx.dominating_set(G, start_with=node)
603
+ v = D.pop()
604
+ if D:
605
+ break
606
+ else:
607
+ # in complete graphs the dominating set will always be of one node
608
+ # thus we return min_cut, which now contains the edges of a node
609
+ # with minimum degree
610
+ return min_cut
611
+ for w in D:
612
+ this_cut = minimum_st_edge_cut(H, v, w, **kwargs)
613
+ if len(this_cut) <= len(min_cut):
614
+ min_cut = this_cut
615
+
616
+ return min_cut
lib/python3.12/site-packages/networkx/algorithms/connectivity/disjoint_paths.py ADDED
@@ -0,0 +1,408 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Flow based node and edge disjoint paths."""
2
+
3
+ from itertools import filterfalse as _filterfalse
4
+
5
+ import networkx as nx
6
+
7
+ # Define the default maximum flow function to use for the underlying
8
+ # maximum flow computations
9
+ from networkx.algorithms.flow import (
10
+ edmonds_karp,
11
+ preflow_push,
12
+ shortest_augmenting_path,
13
+ )
14
+ from networkx.exception import NetworkXNoPath
15
+
16
+ # Functions to build auxiliary data structures.
17
+ from .utils import build_auxiliary_edge_connectivity, build_auxiliary_node_connectivity
18
+
19
+ __all__ = ["edge_disjoint_paths", "node_disjoint_paths"]
20
+ default_flow_func = edmonds_karp
21
+
22
+
23
+ @nx._dispatchable(
24
+ graphs={"G": 0, "auxiliary?": 5},
25
+ preserve_edge_attrs={"auxiliary": {"capacity": float("inf")}},
26
+ )
27
+ def edge_disjoint_paths(
28
+ G, s, t, flow_func=None, cutoff=None, auxiliary=None, residual=None
29
+ ):
30
+ """Returns the edges disjoint paths between source and target.
31
+
32
+ Edge disjoint paths are paths that do not share any edge. The
33
+ number of edge disjoint paths between source and target is equal
34
+ to their edge connectivity.
35
+
36
+ Parameters
37
+ ----------
38
+ G : NetworkX graph
39
+
40
+ s : node
41
+ Source node for the flow.
42
+
43
+ t : node
44
+ Sink node for the flow.
45
+
46
+ flow_func : function
47
+ A function for computing the maximum flow among a pair of nodes.
48
+ The function has to accept at least three parameters: a Digraph,
49
+ a source node, and a target node. And return a residual network
50
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
51
+ details). If flow_func is None, the default maximum flow function
52
+ (:meth:`edmonds_karp`) is used. The choice of the default function
53
+ may change from version to version and should not be relied on.
54
+ Default value: None.
55
+
56
+ cutoff : integer or None (default: None)
57
+ Maximum number of paths to yield. If specified, the maximum flow
58
+ algorithm will terminate when the flow value reaches or exceeds the
59
+ cutoff. This only works for flows that support the cutoff parameter
60
+ (most do) and is ignored otherwise.
61
+
62
+ auxiliary : NetworkX DiGraph
63
+ Auxiliary digraph to compute flow based edge connectivity. It has
64
+ to have a graph attribute called mapping with a dictionary mapping
65
+ node names in G and in the auxiliary digraph. If provided
66
+ it will be reused instead of recreated. Default value: None.
67
+
68
+ residual : NetworkX DiGraph
69
+ Residual network to compute maximum flow. If provided it will be
70
+ reused instead of recreated. Default value: None.
71
+
72
+ Returns
73
+ -------
74
+ paths : generator
75
+ A generator of edge independent paths.
76
+
77
+ Raises
78
+ ------
79
+ NetworkXNoPath
80
+ If there is no path between source and target.
81
+
82
+ NetworkXError
83
+ If source or target are not in the graph G.
84
+
85
+ See also
86
+ --------
87
+ :meth:`node_disjoint_paths`
88
+ :meth:`edge_connectivity`
89
+ :meth:`maximum_flow`
90
+ :meth:`edmonds_karp`
91
+ :meth:`preflow_push`
92
+ :meth:`shortest_augmenting_path`
93
+
94
+ Examples
95
+ --------
96
+ We use in this example the platonic icosahedral graph, which has node
97
+ edge connectivity 5, thus there are 5 edge disjoint paths between any
98
+ pair of nodes.
99
+
100
+ >>> G = nx.icosahedral_graph()
101
+ >>> len(list(nx.edge_disjoint_paths(G, 0, 6)))
102
+ 5
103
+
104
+
105
+ If you need to compute edge disjoint paths on several pairs of
106
+ nodes in the same graph, it is recommended that you reuse the
107
+ data structures that NetworkX uses in the computation: the
108
+ auxiliary digraph for edge connectivity, and the residual
109
+ network for the underlying maximum flow computation.
110
+
111
+ Example of how to compute edge disjoint paths among all pairs of
112
+ nodes of the platonic icosahedral graph reusing the data
113
+ structures.
114
+
115
+ >>> import itertools
116
+ >>> # You also have to explicitly import the function for
117
+ >>> # building the auxiliary digraph from the connectivity package
118
+ >>> from networkx.algorithms.connectivity import build_auxiliary_edge_connectivity
119
+ >>> H = build_auxiliary_edge_connectivity(G)
120
+ >>> # And the function for building the residual network from the
121
+ >>> # flow package
122
+ >>> from networkx.algorithms.flow import build_residual_network
123
+ >>> # Note that the auxiliary digraph has an edge attribute named capacity
124
+ >>> R = build_residual_network(H, "capacity")
125
+ >>> result = {n: {} for n in G}
126
+ >>> # Reuse the auxiliary digraph and the residual network by passing them
127
+ >>> # as arguments
128
+ >>> for u, v in itertools.combinations(G, 2):
129
+ ... k = len(list(nx.edge_disjoint_paths(G, u, v, auxiliary=H, residual=R)))
130
+ ... result[u][v] = k
131
+ >>> all(result[u][v] == 5 for u, v in itertools.combinations(G, 2))
132
+ True
133
+
134
+ You can also use alternative flow algorithms for computing edge disjoint
135
+ paths. For instance, in dense networks the algorithm
136
+ :meth:`shortest_augmenting_path` will usually perform better than
137
+ the default :meth:`edmonds_karp` which is faster for sparse
138
+ networks with highly skewed degree distributions. Alternative flow
139
+ functions have to be explicitly imported from the flow package.
140
+
141
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
142
+ >>> len(list(nx.edge_disjoint_paths(G, 0, 6, flow_func=shortest_augmenting_path)))
143
+ 5
144
+
145
+ Notes
146
+ -----
147
+ This is a flow based implementation of edge disjoint paths. We compute
148
+ the maximum flow between source and target on an auxiliary directed
149
+ network. The saturated edges in the residual network after running the
150
+ maximum flow algorithm correspond to edge disjoint paths between source
151
+ and target in the original network. This function handles both directed
152
+ and undirected graphs, and can use all flow algorithms from NetworkX flow
153
+ package.
154
+
155
+ """
156
+ if s not in G:
157
+ raise nx.NetworkXError(f"node {s} not in graph")
158
+ if t not in G:
159
+ raise nx.NetworkXError(f"node {t} not in graph")
160
+
161
+ if flow_func is None:
162
+ flow_func = default_flow_func
163
+
164
+ if auxiliary is None:
165
+ H = build_auxiliary_edge_connectivity(G)
166
+ else:
167
+ H = auxiliary
168
+
169
+ # Maximum possible edge disjoint paths
170
+ possible = min(H.out_degree(s), H.in_degree(t))
171
+ if not possible:
172
+ raise NetworkXNoPath
173
+
174
+ if cutoff is None:
175
+ cutoff = possible
176
+ else:
177
+ cutoff = min(cutoff, possible)
178
+
179
+ # Compute maximum flow between source and target. Flow functions in
180
+ # NetworkX return a residual network.
181
+ kwargs = {
182
+ "capacity": "capacity",
183
+ "residual": residual,
184
+ "cutoff": cutoff,
185
+ "value_only": True,
186
+ }
187
+ if flow_func is preflow_push:
188
+ del kwargs["cutoff"]
189
+ if flow_func is shortest_augmenting_path:
190
+ kwargs["two_phase"] = True
191
+ R = flow_func(H, s, t, **kwargs)
192
+
193
+ if R.graph["flow_value"] == 0:
194
+ raise NetworkXNoPath
195
+
196
+ # Saturated edges in the residual network form the edge disjoint paths
197
+ # between source and target
198
+ cutset = [
199
+ (u, v)
200
+ for u, v, d in R.edges(data=True)
201
+ if d["capacity"] == d["flow"] and d["flow"] > 0
202
+ ]
203
+ # This is equivalent of what flow.utils.build_flow_dict returns, but
204
+ # only for the nodes with saturated edges and without reporting 0 flows.
205
+ flow_dict = {n: {} for edge in cutset for n in edge}
206
+ for u, v in cutset:
207
+ flow_dict[u][v] = 1
208
+
209
+ # Rebuild the edge disjoint paths from the flow dictionary.
210
+ paths_found = 0
211
+ for v in list(flow_dict[s]):
212
+ if paths_found >= cutoff:
213
+ # preflow_push does not support cutoff: we have to
214
+ # keep track of the paths founds and stop at cutoff.
215
+ break
216
+ path = [s]
217
+ if v == t:
218
+ path.append(v)
219
+ yield path
220
+ continue
221
+ u = v
222
+ while u != t:
223
+ path.append(u)
224
+ try:
225
+ u, _ = flow_dict[u].popitem()
226
+ except KeyError:
227
+ break
228
+ else:
229
+ path.append(t)
230
+ yield path
231
+ paths_found += 1
232
+
233
+
234
+ @nx._dispatchable(
235
+ graphs={"G": 0, "auxiliary?": 5},
236
+ preserve_node_attrs={"auxiliary": {"id": None}},
237
+ preserve_graph_attrs={"auxiliary"},
238
+ )
239
+ def node_disjoint_paths(
240
+ G, s, t, flow_func=None, cutoff=None, auxiliary=None, residual=None
241
+ ):
242
+ r"""Computes node disjoint paths between source and target.
243
+
244
+ Node disjoint paths are paths that only share their first and last
245
+ nodes. The number of node independent paths between two nodes is
246
+ equal to their local node connectivity.
247
+
248
+ Parameters
249
+ ----------
250
+ G : NetworkX graph
251
+
252
+ s : node
253
+ Source node.
254
+
255
+ t : node
256
+ Target node.
257
+
258
+ flow_func : function
259
+ A function for computing the maximum flow among a pair of nodes.
260
+ The function has to accept at least three parameters: a Digraph,
261
+ a source node, and a target node. And return a residual network
262
+ that follows NetworkX conventions (see :meth:`maximum_flow` for
263
+ details). If flow_func is None, the default maximum flow function
264
+ (:meth:`edmonds_karp`) is used. See below for details. The choice
265
+ of the default function may change from version to version and
266
+ should not be relied on. Default value: None.
267
+
268
+ cutoff : integer or None (default: None)
269
+ Maximum number of paths to yield. If specified, the maximum flow
270
+ algorithm will terminate when the flow value reaches or exceeds the
271
+ cutoff. This only works for flows that support the cutoff parameter
272
+ (most do) and is ignored otherwise.
273
+
274
+ auxiliary : NetworkX DiGraph
275
+ Auxiliary digraph to compute flow based node connectivity. It has
276
+ to have a graph attribute called mapping with a dictionary mapping
277
+ node names in G and in the auxiliary digraph. If provided
278
+ it will be reused instead of recreated. Default value: None.
279
+
280
+ residual : NetworkX DiGraph
281
+ Residual network to compute maximum flow. If provided it will be
282
+ reused instead of recreated. Default value: None.
283
+
284
+ Returns
285
+ -------
286
+ paths : generator
287
+ Generator of node disjoint paths.
288
+
289
+ Raises
290
+ ------
291
+ NetworkXNoPath
292
+ If there is no path between source and target.
293
+
294
+ NetworkXError
295
+ If source or target are not in the graph G.
296
+
297
+ Examples
298
+ --------
299
+ We use in this example the platonic icosahedral graph, which has node
300
+ connectivity 5, thus there are 5 node disjoint paths between any pair
301
+ of non neighbor nodes.
302
+
303
+ >>> G = nx.icosahedral_graph()
304
+ >>> len(list(nx.node_disjoint_paths(G, 0, 6)))
305
+ 5
306
+
307
+ If you need to compute node disjoint paths between several pairs of
308
+ nodes in the same graph, it is recommended that you reuse the
309
+ data structures that NetworkX uses in the computation: the
310
+ auxiliary digraph for node connectivity and node cuts, and the
311
+ residual network for the underlying maximum flow computation.
312
+
313
+ Example of how to compute node disjoint paths reusing the data
314
+ structures:
315
+
316
+ >>> # You also have to explicitly import the function for
317
+ >>> # building the auxiliary digraph from the connectivity package
318
+ >>> from networkx.algorithms.connectivity import build_auxiliary_node_connectivity
319
+ >>> H = build_auxiliary_node_connectivity(G)
320
+ >>> # And the function for building the residual network from the
321
+ >>> # flow package
322
+ >>> from networkx.algorithms.flow import build_residual_network
323
+ >>> # Note that the auxiliary digraph has an edge attribute named capacity
324
+ >>> R = build_residual_network(H, "capacity")
325
+ >>> # Reuse the auxiliary digraph and the residual network by passing them
326
+ >>> # as arguments
327
+ >>> len(list(nx.node_disjoint_paths(G, 0, 6, auxiliary=H, residual=R)))
328
+ 5
329
+
330
+ You can also use alternative flow algorithms for computing node disjoint
331
+ paths. For instance, in dense networks the algorithm
332
+ :meth:`shortest_augmenting_path` will usually perform better than
333
+ the default :meth:`edmonds_karp` which is faster for sparse
334
+ networks with highly skewed degree distributions. Alternative flow
335
+ functions have to be explicitly imported from the flow package.
336
+
337
+ >>> from networkx.algorithms.flow import shortest_augmenting_path
338
+ >>> len(list(nx.node_disjoint_paths(G, 0, 6, flow_func=shortest_augmenting_path)))
339
+ 5
340
+
341
+ Notes
342
+ -----
343
+ This is a flow based implementation of node disjoint paths. We compute
344
+ the maximum flow between source and target on an auxiliary directed
345
+ network. The saturated edges in the residual network after running the
346
+ maximum flow algorithm correspond to node disjoint paths between source
347
+ and target in the original network. This function handles both directed
348
+ and undirected graphs, and can use all flow algorithms from NetworkX flow
349
+ package.
350
+
351
+ See also
352
+ --------
353
+ :meth:`edge_disjoint_paths`
354
+ :meth:`node_connectivity`
355
+ :meth:`maximum_flow`
356
+ :meth:`edmonds_karp`
357
+ :meth:`preflow_push`
358
+ :meth:`shortest_augmenting_path`
359
+
360
+ """
361
+ if s not in G:
362
+ raise nx.NetworkXError(f"node {s} not in graph")
363
+ if t not in G:
364
+ raise nx.NetworkXError(f"node {t} not in graph")
365
+
366
+ if auxiliary is None:
367
+ H = build_auxiliary_node_connectivity(G)
368
+ else:
369
+ H = auxiliary
370
+
371
+ mapping = H.graph.get("mapping", None)
372
+ if mapping is None:
373
+ raise nx.NetworkXError("Invalid auxiliary digraph.")
374
+
375
+ # Maximum possible edge disjoint paths
376
+ possible = min(H.out_degree(f"{mapping[s]}B"), H.in_degree(f"{mapping[t]}A"))
377
+ if not possible:
378
+ raise NetworkXNoPath
379
+
380
+ if cutoff is None:
381
+ cutoff = possible
382
+ else:
383
+ cutoff = min(cutoff, possible)
384
+
385
+ kwargs = {
386
+ "flow_func": flow_func,
387
+ "residual": residual,
388
+ "auxiliary": H,
389
+ "cutoff": cutoff,
390
+ }
391
+
392
+ # The edge disjoint paths in the auxiliary digraph correspond to the node
393
+ # disjoint paths in the original graph.
394
+ paths_edges = edge_disjoint_paths(H, f"{mapping[s]}B", f"{mapping[t]}A", **kwargs)
395
+ for path in paths_edges:
396
+ # Each node in the original graph maps to two nodes in auxiliary graph
397
+ yield list(_unique_everseen(H.nodes[node]["id"] for node in path))
398
+
399
+
400
+ def _unique_everseen(iterable):
401
+ # Adapted from https://docs.python.org/3/library/itertools.html examples
402
+ "List unique elements, preserving order. Remember all elements ever seen."
403
+ # unique_everseen('AAAABBBCCDAABBB') --> A B C D
404
+ seen = set()
405
+ seen_add = seen.add
406
+ for element in _filterfalse(seen.__contains__, iterable):
407
+ seen_add(element)
408
+ yield element