Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- lib/python3.12/site-packages/networkx/algorithms/__init__.py +134 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/__init__.py +5 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/connectivity.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/correlation.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/mixing.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/neighbor_degree.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/pairs.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/connectivity.py +122 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/correlation.py +302 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/mixing.py +255 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/neighbor_degree.py +160 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/pairs.py +127 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__init__.py +0 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/base_test.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_connectivity.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_correlation.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_mixing.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_neighbor_degree.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_pairs.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/base_test.py +81 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_connectivity.py +143 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_correlation.py +122 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_mixing.py +174 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_neighbor_degree.py +107 -0
- lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_pairs.py +87 -0
- lib/python3.12/site-packages/networkx/algorithms/asteroidal.py +164 -0
- lib/python3.12/site-packages/networkx/algorithms/boundary.py +168 -0
- lib/python3.12/site-packages/networkx/algorithms/bridges.py +205 -0
- lib/python3.12/site-packages/networkx/algorithms/broadcasting.py +164 -0
- lib/python3.12/site-packages/networkx/algorithms/chains.py +172 -0
- lib/python3.12/site-packages/networkx/algorithms/chordal.py +443 -0
- lib/python3.12/site-packages/networkx/algorithms/clique.py +818 -0
- lib/python3.12/site-packages/networkx/algorithms/cluster.py +732 -0
- lib/python3.12/site-packages/networkx/algorithms/communicability_alg.py +163 -0
- lib/python3.12/site-packages/networkx/algorithms/connectivity/__init__.py +11 -0
- lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/connectivity.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/cuts.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/disjoint_paths.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/edge_augmentation.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/edge_kcomponents.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/kcomponents.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/kcutsets.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/stoerwagner.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/algorithms/connectivity/connectivity.py +811 -0
- lib/python3.12/site-packages/networkx/algorithms/connectivity/cuts.py +616 -0
- 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
|