Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/__init__.py +132 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/__init__.py +24 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/__pycache__/clique.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/__pycache__/clustering_coefficient.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/__pycache__/dominating_set.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/__pycache__/kcomponents.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/__pycache__/traveling_salesman.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/clustering_coefficient.py +66 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/dominating_set.py +126 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/ramsey.py +52 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/__pycache__/__init__.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_approx_clust_coeff.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_dominating_set.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_kcomponents.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_matching.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/test_approx_clust_coeff.py +41 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/test_clique.py +113 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/test_distance_measures.py +60 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/test_maxcut.py +82 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/test_vertex_cover.py +68 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/asteroidal.py +170 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/bridges.py +205 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/chordal.py +440 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/cluster.py +605 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/community/centrality.py +171 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/community/louvain.py +373 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__init__.py +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/__init__.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/dag.py +1258 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/distance_regular.py +234 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__init__.py +11 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/__init__.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/edmondskarp.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/maxflow.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/edmondskarp.py +250 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/mincost.py +335 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/shortestaugmentingpath.py +304 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/test_mincost.py +476 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/graph_hashing.py +313 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/graphical.py +483 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/hierarchy.py +48 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/isolate.py +107 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/tests/__pycache__/test_pagerank.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/tests/test_hits.py +78 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/lowest_common_ancestors.py +268 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/minors/__pycache__/contraction.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/minors/tests/test_contraction.py +445 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/mis.py +77 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/moral.py +59 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/node_classification.py +218 -0
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/__init__.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from networkx.algorithms.assortativity import *
|
| 2 |
+
from networkx.algorithms.asteroidal import *
|
| 3 |
+
from networkx.algorithms.boundary import *
|
| 4 |
+
from networkx.algorithms.bridges import *
|
| 5 |
+
from networkx.algorithms.chains import *
|
| 6 |
+
from networkx.algorithms.centrality import *
|
| 7 |
+
from networkx.algorithms.chordal import *
|
| 8 |
+
from networkx.algorithms.cluster import *
|
| 9 |
+
from networkx.algorithms.clique import *
|
| 10 |
+
from networkx.algorithms.communicability_alg import *
|
| 11 |
+
from networkx.algorithms.components import *
|
| 12 |
+
from networkx.algorithms.coloring import *
|
| 13 |
+
from networkx.algorithms.core import *
|
| 14 |
+
from networkx.algorithms.covering import *
|
| 15 |
+
from networkx.algorithms.cycles import *
|
| 16 |
+
from networkx.algorithms.cuts import *
|
| 17 |
+
from networkx.algorithms.d_separation import *
|
| 18 |
+
from networkx.algorithms.dag import *
|
| 19 |
+
from networkx.algorithms.distance_measures import *
|
| 20 |
+
from networkx.algorithms.distance_regular import *
|
| 21 |
+
from networkx.algorithms.dominance import *
|
| 22 |
+
from networkx.algorithms.dominating import *
|
| 23 |
+
from networkx.algorithms.efficiency_measures import *
|
| 24 |
+
from networkx.algorithms.euler import *
|
| 25 |
+
from networkx.algorithms.graphical import *
|
| 26 |
+
from networkx.algorithms.hierarchy import *
|
| 27 |
+
from networkx.algorithms.hybrid import *
|
| 28 |
+
from networkx.algorithms.link_analysis import *
|
| 29 |
+
from networkx.algorithms.link_prediction import *
|
| 30 |
+
from networkx.algorithms.lowest_common_ancestors import *
|
| 31 |
+
from networkx.algorithms.isolate import *
|
| 32 |
+
from networkx.algorithms.matching import *
|
| 33 |
+
from networkx.algorithms.minors import *
|
| 34 |
+
from networkx.algorithms.mis import *
|
| 35 |
+
from networkx.algorithms.moral import *
|
| 36 |
+
from networkx.algorithms.non_randomness import *
|
| 37 |
+
from networkx.algorithms.operators import *
|
| 38 |
+
from networkx.algorithms.planarity import *
|
| 39 |
+
from networkx.algorithms.planar_drawing import *
|
| 40 |
+
from networkx.algorithms.reciprocity import *
|
| 41 |
+
from networkx.algorithms.regular import *
|
| 42 |
+
from networkx.algorithms.richclub import *
|
| 43 |
+
from networkx.algorithms.shortest_paths import *
|
| 44 |
+
from networkx.algorithms.similarity import *
|
| 45 |
+
from networkx.algorithms.graph_hashing import *
|
| 46 |
+
from networkx.algorithms.simple_paths import *
|
| 47 |
+
from networkx.algorithms.smallworld import *
|
| 48 |
+
from networkx.algorithms.smetric import *
|
| 49 |
+
from networkx.algorithms.structuralholes import *
|
| 50 |
+
from networkx.algorithms.sparsifiers import *
|
| 51 |
+
from networkx.algorithms.summarization import *
|
| 52 |
+
from networkx.algorithms.swap import *
|
| 53 |
+
from networkx.algorithms.time_dependent import *
|
| 54 |
+
from networkx.algorithms.traversal import *
|
| 55 |
+
from networkx.algorithms.triads import *
|
| 56 |
+
from networkx.algorithms.vitality import *
|
| 57 |
+
from networkx.algorithms.voronoi import *
|
| 58 |
+
from networkx.algorithms.walks import *
|
| 59 |
+
from networkx.algorithms.wiener import *
|
| 60 |
+
from networkx.algorithms.polynomials import *
|
| 61 |
+
|
| 62 |
+
# Make certain subpackages available to the user as direct imports from
|
| 63 |
+
# the `networkx` namespace.
|
| 64 |
+
from networkx.algorithms import approximation
|
| 65 |
+
from networkx.algorithms import assortativity
|
| 66 |
+
from networkx.algorithms import bipartite
|
| 67 |
+
from networkx.algorithms import node_classification
|
| 68 |
+
from networkx.algorithms import centrality
|
| 69 |
+
from networkx.algorithms import chordal
|
| 70 |
+
from networkx.algorithms import cluster
|
| 71 |
+
from networkx.algorithms import clique
|
| 72 |
+
from networkx.algorithms import components
|
| 73 |
+
from networkx.algorithms import connectivity
|
| 74 |
+
from networkx.algorithms import community
|
| 75 |
+
from networkx.algorithms import coloring
|
| 76 |
+
from networkx.algorithms import flow
|
| 77 |
+
from networkx.algorithms import isomorphism
|
| 78 |
+
from networkx.algorithms import link_analysis
|
| 79 |
+
from networkx.algorithms import lowest_common_ancestors
|
| 80 |
+
from networkx.algorithms import operators
|
| 81 |
+
from networkx.algorithms import shortest_paths
|
| 82 |
+
from networkx.algorithms import tournament
|
| 83 |
+
from networkx.algorithms import traversal
|
| 84 |
+
from networkx.algorithms import tree
|
| 85 |
+
|
| 86 |
+
# Make certain functions from some of the previous subpackages available
|
| 87 |
+
# to the user as direct imports from the `networkx` namespace.
|
| 88 |
+
from networkx.algorithms.bipartite import complete_bipartite_graph
|
| 89 |
+
from networkx.algorithms.bipartite import is_bipartite
|
| 90 |
+
from networkx.algorithms.bipartite import projected_graph
|
| 91 |
+
from networkx.algorithms.connectivity import all_pairs_node_connectivity
|
| 92 |
+
from networkx.algorithms.connectivity import all_node_cuts
|
| 93 |
+
from networkx.algorithms.connectivity import average_node_connectivity
|
| 94 |
+
from networkx.algorithms.connectivity import edge_connectivity
|
| 95 |
+
from networkx.algorithms.connectivity import edge_disjoint_paths
|
| 96 |
+
from networkx.algorithms.connectivity import k_components
|
| 97 |
+
from networkx.algorithms.connectivity import k_edge_components
|
| 98 |
+
from networkx.algorithms.connectivity import k_edge_subgraphs
|
| 99 |
+
from networkx.algorithms.connectivity import k_edge_augmentation
|
| 100 |
+
from networkx.algorithms.connectivity import is_k_edge_connected
|
| 101 |
+
from networkx.algorithms.connectivity import minimum_edge_cut
|
| 102 |
+
from networkx.algorithms.connectivity import minimum_node_cut
|
| 103 |
+
from networkx.algorithms.connectivity import node_connectivity
|
| 104 |
+
from networkx.algorithms.connectivity import node_disjoint_paths
|
| 105 |
+
from networkx.algorithms.connectivity import stoer_wagner
|
| 106 |
+
from networkx.algorithms.flow import capacity_scaling
|
| 107 |
+
from networkx.algorithms.flow import cost_of_flow
|
| 108 |
+
from networkx.algorithms.flow import gomory_hu_tree
|
| 109 |
+
from networkx.algorithms.flow import max_flow_min_cost
|
| 110 |
+
from networkx.algorithms.flow import maximum_flow
|
| 111 |
+
from networkx.algorithms.flow import maximum_flow_value
|
| 112 |
+
from networkx.algorithms.flow import min_cost_flow
|
| 113 |
+
from networkx.algorithms.flow import min_cost_flow_cost
|
| 114 |
+
from networkx.algorithms.flow import minimum_cut
|
| 115 |
+
from networkx.algorithms.flow import minimum_cut_value
|
| 116 |
+
from networkx.algorithms.flow import network_simplex
|
| 117 |
+
from networkx.algorithms.isomorphism import could_be_isomorphic
|
| 118 |
+
from networkx.algorithms.isomorphism import fast_could_be_isomorphic
|
| 119 |
+
from networkx.algorithms.isomorphism import faster_could_be_isomorphic
|
| 120 |
+
from networkx.algorithms.isomorphism import is_isomorphic
|
| 121 |
+
from networkx.algorithms.isomorphism.vf2pp import *
|
| 122 |
+
from networkx.algorithms.tree.branchings import maximum_branching
|
| 123 |
+
from networkx.algorithms.tree.branchings import maximum_spanning_arborescence
|
| 124 |
+
from networkx.algorithms.tree.branchings import minimum_branching
|
| 125 |
+
from networkx.algorithms.tree.branchings import minimum_spanning_arborescence
|
| 126 |
+
from networkx.algorithms.tree.branchings import ArborescenceIterator
|
| 127 |
+
from networkx.algorithms.tree.coding import *
|
| 128 |
+
from networkx.algorithms.tree.decomposition import *
|
| 129 |
+
from networkx.algorithms.tree.mst import *
|
| 130 |
+
from networkx.algorithms.tree.operations import *
|
| 131 |
+
from networkx.algorithms.tree.recognition import *
|
| 132 |
+
from networkx.algorithms.tournament import is_tournament
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/__init__.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Approximations of graph properties and Heuristic methods for optimization.
|
| 2 |
+
|
| 3 |
+
The functions in this class are not imported into the top-level ``networkx``
|
| 4 |
+
namespace so the easiest way to use them is with::
|
| 5 |
+
|
| 6 |
+
>>> from networkx.algorithms import approximation
|
| 7 |
+
|
| 8 |
+
Another option is to import the specific function with
|
| 9 |
+
``from networkx.algorithms.approximation import function_name``.
|
| 10 |
+
|
| 11 |
+
"""
|
| 12 |
+
from networkx.algorithms.approximation.clustering_coefficient import *
|
| 13 |
+
from networkx.algorithms.approximation.clique import *
|
| 14 |
+
from networkx.algorithms.approximation.connectivity import *
|
| 15 |
+
from networkx.algorithms.approximation.distance_measures import *
|
| 16 |
+
from networkx.algorithms.approximation.dominating_set import *
|
| 17 |
+
from networkx.algorithms.approximation.kcomponents import *
|
| 18 |
+
from networkx.algorithms.approximation.matching import *
|
| 19 |
+
from networkx.algorithms.approximation.ramsey import *
|
| 20 |
+
from networkx.algorithms.approximation.steinertree import *
|
| 21 |
+
from networkx.algorithms.approximation.traveling_salesman import *
|
| 22 |
+
from networkx.algorithms.approximation.treewidth import *
|
| 23 |
+
from networkx.algorithms.approximation.vertex_cover import *
|
| 24 |
+
from networkx.algorithms.approximation.maxcut import *
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/__pycache__/clique.cpython-311.pyc
ADDED
|
Binary file (9.8 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/__pycache__/clustering_coefficient.cpython-311.pyc
ADDED
|
Binary file (3.16 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/__pycache__/dominating_set.cpython-311.pyc
ADDED
|
Binary file (4.96 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/__pycache__/kcomponents.cpython-311.pyc
ADDED
|
Binary file (20.5 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/__pycache__/traveling_salesman.cpython-311.pyc
ADDED
|
Binary file (62.5 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/clustering_coefficient.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import networkx as nx
|
| 2 |
+
from networkx.utils import not_implemented_for, py_random_state
|
| 3 |
+
|
| 4 |
+
__all__ = ["average_clustering"]
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
@not_implemented_for("directed")
|
| 8 |
+
@py_random_state(2)
|
| 9 |
+
@nx._dispatch(name="approximate_average_clustering")
|
| 10 |
+
def average_clustering(G, trials=1000, seed=None):
|
| 11 |
+
r"""Estimates the average clustering coefficient of G.
|
| 12 |
+
|
| 13 |
+
The local clustering of each node in `G` is the fraction of triangles
|
| 14 |
+
that actually exist over all possible triangles in its neighborhood.
|
| 15 |
+
The average clustering coefficient of a graph `G` is the mean of
|
| 16 |
+
local clusterings.
|
| 17 |
+
|
| 18 |
+
This function finds an approximate average clustering coefficient
|
| 19 |
+
for G by repeating `n` times (defined in `trials`) the following
|
| 20 |
+
experiment: choose a node at random, choose two of its neighbors
|
| 21 |
+
at random, and check if they are connected. The approximate
|
| 22 |
+
coefficient is the fraction of triangles found over the number
|
| 23 |
+
of trials [1]_.
|
| 24 |
+
|
| 25 |
+
Parameters
|
| 26 |
+
----------
|
| 27 |
+
G : NetworkX graph
|
| 28 |
+
|
| 29 |
+
trials : integer
|
| 30 |
+
Number of trials to perform (default 1000).
|
| 31 |
+
|
| 32 |
+
seed : integer, random_state, or None (default)
|
| 33 |
+
Indicator of random number generation state.
|
| 34 |
+
See :ref:`Randomness<randomness>`.
|
| 35 |
+
|
| 36 |
+
Returns
|
| 37 |
+
-------
|
| 38 |
+
c : float
|
| 39 |
+
Approximated average clustering coefficient.
|
| 40 |
+
|
| 41 |
+
Examples
|
| 42 |
+
--------
|
| 43 |
+
>>> from networkx.algorithms import approximation
|
| 44 |
+
>>> G = nx.erdos_renyi_graph(10, 0.2, seed=10)
|
| 45 |
+
>>> approximation.average_clustering(G, trials=1000, seed=10)
|
| 46 |
+
0.214
|
| 47 |
+
|
| 48 |
+
References
|
| 49 |
+
----------
|
| 50 |
+
.. [1] Schank, Thomas, and Dorothea Wagner. Approximating clustering
|
| 51 |
+
coefficient and transitivity. Universität Karlsruhe, Fakultät für
|
| 52 |
+
Informatik, 2004.
|
| 53 |
+
https://doi.org/10.5445/IR/1000001239
|
| 54 |
+
|
| 55 |
+
"""
|
| 56 |
+
n = len(G)
|
| 57 |
+
triangles = 0
|
| 58 |
+
nodes = list(G)
|
| 59 |
+
for i in [int(seed.random() * n) for i in range(trials)]:
|
| 60 |
+
nbrs = list(G[nodes[i]])
|
| 61 |
+
if len(nbrs) < 2:
|
| 62 |
+
continue
|
| 63 |
+
u, v = seed.sample(nbrs, 2)
|
| 64 |
+
if u in G[v]:
|
| 65 |
+
triangles += 1
|
| 66 |
+
return triangles / trials
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/dominating_set.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Functions for finding node and edge dominating sets.
|
| 2 |
+
|
| 3 |
+
A `dominating set`_ for an undirected graph *G* with vertex set *V*
|
| 4 |
+
and edge set *E* is a subset *D* of *V* such that every vertex not in
|
| 5 |
+
*D* is adjacent to at least one member of *D*. An `edge dominating set`_
|
| 6 |
+
is a subset *F* of *E* such that every edge not in *F* is
|
| 7 |
+
incident to an endpoint of at least one edge in *F*.
|
| 8 |
+
|
| 9 |
+
.. _dominating set: https://en.wikipedia.org/wiki/Dominating_set
|
| 10 |
+
.. _edge dominating set: https://en.wikipedia.org/wiki/Edge_dominating_set
|
| 11 |
+
|
| 12 |
+
"""
|
| 13 |
+
import networkx as nx
|
| 14 |
+
|
| 15 |
+
from ...utils import not_implemented_for
|
| 16 |
+
from ..matching import maximal_matching
|
| 17 |
+
|
| 18 |
+
__all__ = ["min_weighted_dominating_set", "min_edge_dominating_set"]
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
# TODO Why doesn't this algorithm work for directed graphs?
|
| 22 |
+
@not_implemented_for("directed")
|
| 23 |
+
@nx._dispatch(node_attrs="weight")
|
| 24 |
+
def min_weighted_dominating_set(G, weight=None):
|
| 25 |
+
r"""Returns a dominating set that approximates the minimum weight node
|
| 26 |
+
dominating set.
|
| 27 |
+
|
| 28 |
+
Parameters
|
| 29 |
+
----------
|
| 30 |
+
G : NetworkX graph
|
| 31 |
+
Undirected graph.
|
| 32 |
+
|
| 33 |
+
weight : string
|
| 34 |
+
The node attribute storing the weight of an node. If provided,
|
| 35 |
+
the node attribute with this key must be a number for each
|
| 36 |
+
node. If not provided, each node is assumed to have weight one.
|
| 37 |
+
|
| 38 |
+
Returns
|
| 39 |
+
-------
|
| 40 |
+
min_weight_dominating_set : set
|
| 41 |
+
A set of nodes, the sum of whose weights is no more than `(\log
|
| 42 |
+
w(V)) w(V^*)`, where `w(V)` denotes the sum of the weights of
|
| 43 |
+
each node in the graph and `w(V^*)` denotes the sum of the
|
| 44 |
+
weights of each node in the minimum weight dominating set.
|
| 45 |
+
|
| 46 |
+
Notes
|
| 47 |
+
-----
|
| 48 |
+
This algorithm computes an approximate minimum weighted dominating
|
| 49 |
+
set for the graph `G`. The returned solution has weight `(\log
|
| 50 |
+
w(V)) w(V^*)`, where `w(V)` denotes the sum of the weights of each
|
| 51 |
+
node in the graph and `w(V^*)` denotes the sum of the weights of
|
| 52 |
+
each node in the minimum weight dominating set for the graph.
|
| 53 |
+
|
| 54 |
+
This implementation of the algorithm runs in $O(m)$ time, where $m$
|
| 55 |
+
is the number of edges in the graph.
|
| 56 |
+
|
| 57 |
+
References
|
| 58 |
+
----------
|
| 59 |
+
.. [1] Vazirani, Vijay V.
|
| 60 |
+
*Approximation Algorithms*.
|
| 61 |
+
Springer Science & Business Media, 2001.
|
| 62 |
+
|
| 63 |
+
"""
|
| 64 |
+
# The unique dominating set for the null graph is the empty set.
|
| 65 |
+
if len(G) == 0:
|
| 66 |
+
return set()
|
| 67 |
+
|
| 68 |
+
# This is the dominating set that will eventually be returned.
|
| 69 |
+
dom_set = set()
|
| 70 |
+
|
| 71 |
+
def _cost(node_and_neighborhood):
|
| 72 |
+
"""Returns the cost-effectiveness of greedily choosing the given
|
| 73 |
+
node.
|
| 74 |
+
|
| 75 |
+
`node_and_neighborhood` is a two-tuple comprising a node and its
|
| 76 |
+
closed neighborhood.
|
| 77 |
+
|
| 78 |
+
"""
|
| 79 |
+
v, neighborhood = node_and_neighborhood
|
| 80 |
+
return G.nodes[v].get(weight, 1) / len(neighborhood - dom_set)
|
| 81 |
+
|
| 82 |
+
# This is a set of all vertices not already covered by the
|
| 83 |
+
# dominating set.
|
| 84 |
+
vertices = set(G)
|
| 85 |
+
# This is a dictionary mapping each node to the closed neighborhood
|
| 86 |
+
# of that node.
|
| 87 |
+
neighborhoods = {v: {v} | set(G[v]) for v in G}
|
| 88 |
+
|
| 89 |
+
# Continue until all vertices are adjacent to some node in the
|
| 90 |
+
# dominating set.
|
| 91 |
+
while vertices:
|
| 92 |
+
# Find the most cost-effective node to add, along with its
|
| 93 |
+
# closed neighborhood.
|
| 94 |
+
dom_node, min_set = min(neighborhoods.items(), key=_cost)
|
| 95 |
+
# Add the node to the dominating set and reduce the remaining
|
| 96 |
+
# set of nodes to cover.
|
| 97 |
+
dom_set.add(dom_node)
|
| 98 |
+
del neighborhoods[dom_node]
|
| 99 |
+
vertices -= min_set
|
| 100 |
+
|
| 101 |
+
return dom_set
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
@nx._dispatch
|
| 105 |
+
def min_edge_dominating_set(G):
|
| 106 |
+
r"""Returns minimum cardinality edge dominating set.
|
| 107 |
+
|
| 108 |
+
Parameters
|
| 109 |
+
----------
|
| 110 |
+
G : NetworkX graph
|
| 111 |
+
Undirected graph
|
| 112 |
+
|
| 113 |
+
Returns
|
| 114 |
+
-------
|
| 115 |
+
min_edge_dominating_set : set
|
| 116 |
+
Returns a set of dominating edges whose size is no more than 2 * OPT.
|
| 117 |
+
|
| 118 |
+
Notes
|
| 119 |
+
-----
|
| 120 |
+
The algorithm computes an approximate solution to the edge dominating set
|
| 121 |
+
problem. The result is no more than 2 * OPT in terms of size of the set.
|
| 122 |
+
Runtime of the algorithm is $O(|E|)$.
|
| 123 |
+
"""
|
| 124 |
+
if not G:
|
| 125 |
+
raise ValueError("Expected non-empty NetworkX graph!")
|
| 126 |
+
return maximal_matching(G)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/ramsey.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Ramsey numbers.
|
| 3 |
+
"""
|
| 4 |
+
import networkx as nx
|
| 5 |
+
from networkx.utils import not_implemented_for
|
| 6 |
+
|
| 7 |
+
from ...utils import arbitrary_element
|
| 8 |
+
|
| 9 |
+
__all__ = ["ramsey_R2"]
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@not_implemented_for("directed")
|
| 13 |
+
@not_implemented_for("multigraph")
|
| 14 |
+
@nx._dispatch
|
| 15 |
+
def ramsey_R2(G):
|
| 16 |
+
r"""Compute the largest clique and largest independent set in `G`.
|
| 17 |
+
|
| 18 |
+
This can be used to estimate bounds for the 2-color
|
| 19 |
+
Ramsey number `R(2;s,t)` for `G`.
|
| 20 |
+
|
| 21 |
+
This is a recursive implementation which could run into trouble
|
| 22 |
+
for large recursions. Note that self-loop edges are ignored.
|
| 23 |
+
|
| 24 |
+
Parameters
|
| 25 |
+
----------
|
| 26 |
+
G : NetworkX graph
|
| 27 |
+
Undirected graph
|
| 28 |
+
|
| 29 |
+
Returns
|
| 30 |
+
-------
|
| 31 |
+
max_pair : (set, set) tuple
|
| 32 |
+
Maximum clique, Maximum independent set.
|
| 33 |
+
|
| 34 |
+
Raises
|
| 35 |
+
------
|
| 36 |
+
NetworkXNotImplemented
|
| 37 |
+
If the graph is directed or is a multigraph.
|
| 38 |
+
"""
|
| 39 |
+
if not G:
|
| 40 |
+
return set(), set()
|
| 41 |
+
|
| 42 |
+
node = arbitrary_element(G)
|
| 43 |
+
nbrs = (nbr for nbr in nx.all_neighbors(G, node) if nbr != node)
|
| 44 |
+
nnbrs = nx.non_neighbors(G, node)
|
| 45 |
+
c_1, i_1 = ramsey_R2(G.subgraph(nbrs).copy())
|
| 46 |
+
c_2, i_2 = ramsey_R2(G.subgraph(nnbrs).copy())
|
| 47 |
+
|
| 48 |
+
c_1.add(node)
|
| 49 |
+
i_2.add(node)
|
| 50 |
+
# Choose the larger of the two cliques and the larger of the two
|
| 51 |
+
# independent sets, according to cardinality.
|
| 52 |
+
return max(c_1, c_2, key=len), max(i_1, i_2, key=len)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (240 Bytes). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_approx_clust_coeff.cpython-311.pyc
ADDED
|
Binary file (2.77 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_dominating_set.cpython-311.pyc
ADDED
|
Binary file (4.47 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_kcomponents.cpython-311.pyc
ADDED
|
Binary file (20.7 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_matching.cpython-311.pyc
ADDED
|
Binary file (736 Bytes). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/test_approx_clust_coeff.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import networkx as nx
|
| 2 |
+
from networkx.algorithms.approximation import average_clustering
|
| 3 |
+
|
| 4 |
+
# This approximation has to be exact in regular graphs
|
| 5 |
+
# with no triangles or with all possible triangles.
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def test_petersen():
|
| 9 |
+
# Actual coefficient is 0
|
| 10 |
+
G = nx.petersen_graph()
|
| 11 |
+
assert average_clustering(G, trials=len(G) // 2) == nx.average_clustering(G)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def test_petersen_seed():
|
| 15 |
+
# Actual coefficient is 0
|
| 16 |
+
G = nx.petersen_graph()
|
| 17 |
+
assert average_clustering(G, trials=len(G) // 2, seed=1) == nx.average_clustering(G)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def test_tetrahedral():
|
| 21 |
+
# Actual coefficient is 1
|
| 22 |
+
G = nx.tetrahedral_graph()
|
| 23 |
+
assert average_clustering(G, trials=len(G) // 2) == nx.average_clustering(G)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def test_dodecahedral():
|
| 27 |
+
# Actual coefficient is 0
|
| 28 |
+
G = nx.dodecahedral_graph()
|
| 29 |
+
assert average_clustering(G, trials=len(G) // 2) == nx.average_clustering(G)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def test_empty():
|
| 33 |
+
G = nx.empty_graph(5)
|
| 34 |
+
assert average_clustering(G, trials=len(G) // 2) == 0
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def test_complete():
|
| 38 |
+
G = nx.complete_graph(5)
|
| 39 |
+
assert average_clustering(G, trials=len(G) // 2) == 1
|
| 40 |
+
G = nx.complete_graph(7)
|
| 41 |
+
assert average_clustering(G, trials=len(G) // 2) == 1
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/test_clique.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Unit tests for the :mod:`networkx.algorithms.approximation.clique` module."""
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
import networkx as nx
|
| 5 |
+
from networkx.algorithms.approximation import (
|
| 6 |
+
clique_removal,
|
| 7 |
+
large_clique_size,
|
| 8 |
+
max_clique,
|
| 9 |
+
maximum_independent_set,
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def is_independent_set(G, nodes):
|
| 14 |
+
"""Returns True if and only if `nodes` is a clique in `G`.
|
| 15 |
+
|
| 16 |
+
`G` is a NetworkX graph. `nodes` is an iterable of nodes in
|
| 17 |
+
`G`.
|
| 18 |
+
|
| 19 |
+
"""
|
| 20 |
+
return G.subgraph(nodes).number_of_edges() == 0
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def is_clique(G, nodes):
|
| 24 |
+
"""Returns True if and only if `nodes` is an independent set
|
| 25 |
+
in `G`.
|
| 26 |
+
|
| 27 |
+
`G` is an undirected simple graph. `nodes` is an iterable of
|
| 28 |
+
nodes in `G`.
|
| 29 |
+
|
| 30 |
+
"""
|
| 31 |
+
H = G.subgraph(nodes)
|
| 32 |
+
n = len(H)
|
| 33 |
+
return H.number_of_edges() == n * (n - 1) // 2
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class TestCliqueRemoval:
|
| 37 |
+
"""Unit tests for the
|
| 38 |
+
:func:`~networkx.algorithms.approximation.clique_removal` function.
|
| 39 |
+
|
| 40 |
+
"""
|
| 41 |
+
|
| 42 |
+
def test_trivial_graph(self):
|
| 43 |
+
G = nx.trivial_graph()
|
| 44 |
+
independent_set, cliques = clique_removal(G)
|
| 45 |
+
assert is_independent_set(G, independent_set)
|
| 46 |
+
assert all(is_clique(G, clique) for clique in cliques)
|
| 47 |
+
# In fact, we should only have 1-cliques, that is, singleton nodes.
|
| 48 |
+
assert all(len(clique) == 1 for clique in cliques)
|
| 49 |
+
|
| 50 |
+
def test_complete_graph(self):
|
| 51 |
+
G = nx.complete_graph(10)
|
| 52 |
+
independent_set, cliques = clique_removal(G)
|
| 53 |
+
assert is_independent_set(G, independent_set)
|
| 54 |
+
assert all(is_clique(G, clique) for clique in cliques)
|
| 55 |
+
|
| 56 |
+
def test_barbell_graph(self):
|
| 57 |
+
G = nx.barbell_graph(10, 5)
|
| 58 |
+
independent_set, cliques = clique_removal(G)
|
| 59 |
+
assert is_independent_set(G, independent_set)
|
| 60 |
+
assert all(is_clique(G, clique) for clique in cliques)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class TestMaxClique:
|
| 64 |
+
"""Unit tests for the :func:`networkx.algorithms.approximation.max_clique`
|
| 65 |
+
function.
|
| 66 |
+
|
| 67 |
+
"""
|
| 68 |
+
|
| 69 |
+
def test_null_graph(self):
|
| 70 |
+
G = nx.null_graph()
|
| 71 |
+
assert len(max_clique(G)) == 0
|
| 72 |
+
|
| 73 |
+
def test_complete_graph(self):
|
| 74 |
+
graph = nx.complete_graph(30)
|
| 75 |
+
# this should return the entire graph
|
| 76 |
+
mc = max_clique(graph)
|
| 77 |
+
assert 30 == len(mc)
|
| 78 |
+
|
| 79 |
+
def test_maximal_by_cardinality(self):
|
| 80 |
+
"""Tests that the maximal clique is computed according to maximum
|
| 81 |
+
cardinality of the sets.
|
| 82 |
+
|
| 83 |
+
For more information, see pull request #1531.
|
| 84 |
+
|
| 85 |
+
"""
|
| 86 |
+
G = nx.complete_graph(5)
|
| 87 |
+
G.add_edge(4, 5)
|
| 88 |
+
clique = max_clique(G)
|
| 89 |
+
assert len(clique) > 1
|
| 90 |
+
|
| 91 |
+
G = nx.lollipop_graph(30, 2)
|
| 92 |
+
clique = max_clique(G)
|
| 93 |
+
assert len(clique) > 2
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def test_large_clique_size():
|
| 97 |
+
G = nx.complete_graph(9)
|
| 98 |
+
nx.add_cycle(G, [9, 10, 11])
|
| 99 |
+
G.add_edge(8, 9)
|
| 100 |
+
G.add_edge(1, 12)
|
| 101 |
+
G.add_node(13)
|
| 102 |
+
|
| 103 |
+
assert large_clique_size(G) == 9
|
| 104 |
+
G.remove_node(5)
|
| 105 |
+
assert large_clique_size(G) == 8
|
| 106 |
+
G.remove_edge(2, 3)
|
| 107 |
+
assert large_clique_size(G) == 7
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def test_independent_set():
|
| 111 |
+
# smoke test
|
| 112 |
+
G = nx.Graph()
|
| 113 |
+
assert len(maximum_independent_set(G)) == 0
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/test_distance_measures.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Unit tests for the :mod:`networkx.algorithms.approximation.distance_measures` module.
|
| 2 |
+
"""
|
| 3 |
+
|
| 4 |
+
import pytest
|
| 5 |
+
|
| 6 |
+
import networkx as nx
|
| 7 |
+
from networkx.algorithms.approximation import diameter
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class TestDiameter:
|
| 11 |
+
"""Unit tests for the approximate diameter function
|
| 12 |
+
:func:`~networkx.algorithms.approximation.distance_measures.diameter`.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
def test_null_graph(self):
|
| 16 |
+
"""Test empty graph."""
|
| 17 |
+
G = nx.null_graph()
|
| 18 |
+
with pytest.raises(
|
| 19 |
+
nx.NetworkXError, match="Expected non-empty NetworkX graph!"
|
| 20 |
+
):
|
| 21 |
+
diameter(G)
|
| 22 |
+
|
| 23 |
+
def test_undirected_non_connected(self):
|
| 24 |
+
"""Test an undirected disconnected graph."""
|
| 25 |
+
graph = nx.path_graph(10)
|
| 26 |
+
graph.remove_edge(3, 4)
|
| 27 |
+
with pytest.raises(nx.NetworkXError, match="Graph not connected."):
|
| 28 |
+
diameter(graph)
|
| 29 |
+
|
| 30 |
+
def test_directed_non_strongly_connected(self):
|
| 31 |
+
"""Test a directed non strongly connected graph."""
|
| 32 |
+
graph = nx.path_graph(10, create_using=nx.DiGraph())
|
| 33 |
+
with pytest.raises(nx.NetworkXError, match="DiGraph not strongly connected."):
|
| 34 |
+
diameter(graph)
|
| 35 |
+
|
| 36 |
+
def test_complete_undirected_graph(self):
|
| 37 |
+
"""Test a complete undirected graph."""
|
| 38 |
+
graph = nx.complete_graph(10)
|
| 39 |
+
assert diameter(graph) == 1
|
| 40 |
+
|
| 41 |
+
def test_complete_directed_graph(self):
|
| 42 |
+
"""Test a complete directed graph."""
|
| 43 |
+
graph = nx.complete_graph(10, create_using=nx.DiGraph())
|
| 44 |
+
assert diameter(graph) == 1
|
| 45 |
+
|
| 46 |
+
def test_undirected_path_graph(self):
|
| 47 |
+
"""Test an undirected path graph with 10 nodes."""
|
| 48 |
+
graph = nx.path_graph(10)
|
| 49 |
+
assert diameter(graph) == 9
|
| 50 |
+
|
| 51 |
+
def test_directed_path_graph(self):
|
| 52 |
+
"""Test a directed path graph with 10 nodes."""
|
| 53 |
+
graph = nx.path_graph(10).to_directed()
|
| 54 |
+
assert diameter(graph) == 9
|
| 55 |
+
|
| 56 |
+
def test_single_node(self):
|
| 57 |
+
"""Test a graph which contains just a node."""
|
| 58 |
+
graph = nx.Graph()
|
| 59 |
+
graph.add_node(1)
|
| 60 |
+
assert diameter(graph) == 0
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/test_maxcut.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx.algorithms.approximation import maxcut
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def _is_valid_cut(G, set1, set2):
|
| 8 |
+
union = set1.union(set2)
|
| 9 |
+
assert union == set(G.nodes)
|
| 10 |
+
assert len(set1) + len(set2) == G.number_of_nodes()
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def _cut_is_locally_optimal(G, cut_size, set1):
|
| 14 |
+
# test if cut can be locally improved
|
| 15 |
+
for i, node in enumerate(set1):
|
| 16 |
+
cut_size_without_node = nx.algorithms.cut_size(
|
| 17 |
+
G, set1 - {node}, weight="weight"
|
| 18 |
+
)
|
| 19 |
+
assert cut_size_without_node <= cut_size
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def test_random_partitioning():
|
| 23 |
+
G = nx.complete_graph(5)
|
| 24 |
+
_, (set1, set2) = maxcut.randomized_partitioning(G, seed=5)
|
| 25 |
+
_is_valid_cut(G, set1, set2)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def test_random_partitioning_all_to_one():
|
| 29 |
+
G = nx.complete_graph(5)
|
| 30 |
+
_, (set1, set2) = maxcut.randomized_partitioning(G, p=1)
|
| 31 |
+
_is_valid_cut(G, set1, set2)
|
| 32 |
+
assert len(set1) == G.number_of_nodes()
|
| 33 |
+
assert len(set2) == 0
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def test_one_exchange_basic():
|
| 37 |
+
G = nx.complete_graph(5)
|
| 38 |
+
random.seed(5)
|
| 39 |
+
for u, v, w in G.edges(data=True):
|
| 40 |
+
w["weight"] = random.randrange(-100, 100, 1) / 10
|
| 41 |
+
|
| 42 |
+
initial_cut = set(random.sample(sorted(G.nodes()), k=5))
|
| 43 |
+
cut_size, (set1, set2) = maxcut.one_exchange(
|
| 44 |
+
G, initial_cut, weight="weight", seed=5
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
_is_valid_cut(G, set1, set2)
|
| 48 |
+
_cut_is_locally_optimal(G, cut_size, set1)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def test_one_exchange_optimal():
|
| 52 |
+
# Greedy one exchange should find the optimal solution for this graph (14)
|
| 53 |
+
G = nx.Graph()
|
| 54 |
+
G.add_edge(1, 2, weight=3)
|
| 55 |
+
G.add_edge(1, 3, weight=3)
|
| 56 |
+
G.add_edge(1, 4, weight=3)
|
| 57 |
+
G.add_edge(1, 5, weight=3)
|
| 58 |
+
G.add_edge(2, 3, weight=5)
|
| 59 |
+
|
| 60 |
+
cut_size, (set1, set2) = maxcut.one_exchange(G, weight="weight", seed=5)
|
| 61 |
+
|
| 62 |
+
_is_valid_cut(G, set1, set2)
|
| 63 |
+
_cut_is_locally_optimal(G, cut_size, set1)
|
| 64 |
+
# check global optimality
|
| 65 |
+
assert cut_size == 14
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def test_negative_weights():
|
| 69 |
+
G = nx.complete_graph(5)
|
| 70 |
+
random.seed(5)
|
| 71 |
+
for u, v, w in G.edges(data=True):
|
| 72 |
+
w["weight"] = -1 * random.random()
|
| 73 |
+
|
| 74 |
+
initial_cut = set(random.sample(sorted(G.nodes()), k=5))
|
| 75 |
+
cut_size, (set1, set2) = maxcut.one_exchange(G, initial_cut, weight="weight")
|
| 76 |
+
|
| 77 |
+
# make sure it is a valid cut
|
| 78 |
+
_is_valid_cut(G, set1, set2)
|
| 79 |
+
# check local optimality
|
| 80 |
+
_cut_is_locally_optimal(G, cut_size, set1)
|
| 81 |
+
# test that all nodes are in the same partition
|
| 82 |
+
assert len(set1) == len(G.nodes) or len(set2) == len(G.nodes)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/approximation/tests/test_vertex_cover.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import networkx as nx
|
| 2 |
+
from networkx.algorithms.approximation import min_weighted_vertex_cover
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def is_cover(G, node_cover):
|
| 6 |
+
return all({u, v} & node_cover for u, v in G.edges())
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class TestMWVC:
|
| 10 |
+
"""Unit tests for the approximate minimum weighted vertex cover
|
| 11 |
+
function,
|
| 12 |
+
:func:`~networkx.algorithms.approximation.vertex_cover.min_weighted_vertex_cover`.
|
| 13 |
+
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
def test_unweighted_directed(self):
|
| 17 |
+
# Create a star graph in which half the nodes are directed in
|
| 18 |
+
# and half are directed out.
|
| 19 |
+
G = nx.DiGraph()
|
| 20 |
+
G.add_edges_from((0, v) for v in range(1, 26))
|
| 21 |
+
G.add_edges_from((v, 0) for v in range(26, 51))
|
| 22 |
+
cover = min_weighted_vertex_cover(G)
|
| 23 |
+
assert 1 == len(cover)
|
| 24 |
+
assert is_cover(G, cover)
|
| 25 |
+
|
| 26 |
+
def test_unweighted_undirected(self):
|
| 27 |
+
# create a simple star graph
|
| 28 |
+
size = 50
|
| 29 |
+
sg = nx.star_graph(size)
|
| 30 |
+
cover = min_weighted_vertex_cover(sg)
|
| 31 |
+
assert 1 == len(cover)
|
| 32 |
+
assert is_cover(sg, cover)
|
| 33 |
+
|
| 34 |
+
def test_weighted(self):
|
| 35 |
+
wg = nx.Graph()
|
| 36 |
+
wg.add_node(0, weight=10)
|
| 37 |
+
wg.add_node(1, weight=1)
|
| 38 |
+
wg.add_node(2, weight=1)
|
| 39 |
+
wg.add_node(3, weight=1)
|
| 40 |
+
wg.add_node(4, weight=1)
|
| 41 |
+
|
| 42 |
+
wg.add_edge(0, 1)
|
| 43 |
+
wg.add_edge(0, 2)
|
| 44 |
+
wg.add_edge(0, 3)
|
| 45 |
+
wg.add_edge(0, 4)
|
| 46 |
+
|
| 47 |
+
wg.add_edge(1, 2)
|
| 48 |
+
wg.add_edge(2, 3)
|
| 49 |
+
wg.add_edge(3, 4)
|
| 50 |
+
wg.add_edge(4, 1)
|
| 51 |
+
|
| 52 |
+
cover = min_weighted_vertex_cover(wg, weight="weight")
|
| 53 |
+
csum = sum(wg.nodes[node]["weight"] for node in cover)
|
| 54 |
+
assert 4 == csum
|
| 55 |
+
assert is_cover(wg, cover)
|
| 56 |
+
|
| 57 |
+
def test_unweighted_self_loop(self):
|
| 58 |
+
slg = nx.Graph()
|
| 59 |
+
slg.add_node(0)
|
| 60 |
+
slg.add_node(1)
|
| 61 |
+
slg.add_node(2)
|
| 62 |
+
|
| 63 |
+
slg.add_edge(0, 1)
|
| 64 |
+
slg.add_edge(2, 2)
|
| 65 |
+
|
| 66 |
+
cover = min_weighted_vertex_cover(slg)
|
| 67 |
+
assert 2 == len(cover)
|
| 68 |
+
assert is_cover(slg, cover)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/asteroidal.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
import networkx as nx
|
| 14 |
+
from networkx.utils import not_implemented_for
|
| 15 |
+
|
| 16 |
+
__all__ = ["is_at_free", "find_asteroidal_triple"]
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@not_implemented_for("directed")
|
| 20 |
+
@not_implemented_for("multigraph")
|
| 21 |
+
@nx._dispatch
|
| 22 |
+
def find_asteroidal_triple(G):
|
| 23 |
+
r"""Find an asteroidal triple in the given graph.
|
| 24 |
+
|
| 25 |
+
An asteroidal triple is a triple of non-adjacent vertices such that
|
| 26 |
+
there exists a path between any two of them which avoids the closed
|
| 27 |
+
neighborhood of the third. It checks all independent triples of vertices
|
| 28 |
+
and whether they are an asteroidal triple or not. This is done with the
|
| 29 |
+
help of a data structure called a component structure.
|
| 30 |
+
A component structure encodes information about which vertices belongs to
|
| 31 |
+
the same connected component when the closed neighborhood of a given vertex
|
| 32 |
+
is removed from the graph. The algorithm used to check is the trivial
|
| 33 |
+
one, outlined in [1]_, which has a runtime of
|
| 34 |
+
:math:`O(|V||\overline{E} + |V||E|)`, where the second term is the
|
| 35 |
+
creation of the component structure.
|
| 36 |
+
|
| 37 |
+
Parameters
|
| 38 |
+
----------
|
| 39 |
+
G : NetworkX Graph
|
| 40 |
+
The graph to check whether is AT-free or not
|
| 41 |
+
|
| 42 |
+
Returns
|
| 43 |
+
-------
|
| 44 |
+
list or None
|
| 45 |
+
An asteroidal triple is returned as a list of nodes. If no asteroidal
|
| 46 |
+
triple exists, i.e. the graph is AT-free, then None is returned.
|
| 47 |
+
The returned value depends on the certificate parameter. The default
|
| 48 |
+
option is a bool which is True if the graph is AT-free, i.e. the
|
| 49 |
+
given graph contains no asteroidal triples, and False otherwise, i.e.
|
| 50 |
+
if the graph contains at least one asteroidal triple.
|
| 51 |
+
|
| 52 |
+
Notes
|
| 53 |
+
-----
|
| 54 |
+
The component structure and the algorithm is described in [1]_. The current
|
| 55 |
+
implementation implements the trivial algorithm for simple graphs.
|
| 56 |
+
|
| 57 |
+
References
|
| 58 |
+
----------
|
| 59 |
+
.. [1] Ekkehard Köhler,
|
| 60 |
+
"Recognizing Graphs without asteroidal triples",
|
| 61 |
+
Journal of Discrete Algorithms 2, pages 439-452, 2004.
|
| 62 |
+
https://www.sciencedirect.com/science/article/pii/S157086670400019X
|
| 63 |
+
"""
|
| 64 |
+
V = set(G.nodes)
|
| 65 |
+
|
| 66 |
+
if len(V) < 6:
|
| 67 |
+
# An asteroidal triple cannot exist in a graph with 5 or less vertices.
|
| 68 |
+
return None
|
| 69 |
+
|
| 70 |
+
component_structure = create_component_structure(G)
|
| 71 |
+
E_complement = set(nx.complement(G).edges)
|
| 72 |
+
|
| 73 |
+
for e in E_complement:
|
| 74 |
+
u = e[0]
|
| 75 |
+
v = e[1]
|
| 76 |
+
u_neighborhood = set(G[u]).union([u])
|
| 77 |
+
v_neighborhood = set(G[v]).union([v])
|
| 78 |
+
union_of_neighborhoods = u_neighborhood.union(v_neighborhood)
|
| 79 |
+
for w in V - union_of_neighborhoods:
|
| 80 |
+
# Check for each pair of vertices whether they belong to the
|
| 81 |
+
# same connected component when the closed neighborhood of the
|
| 82 |
+
# third is removed.
|
| 83 |
+
if (
|
| 84 |
+
component_structure[u][v] == component_structure[u][w]
|
| 85 |
+
and component_structure[v][u] == component_structure[v][w]
|
| 86 |
+
and component_structure[w][u] == component_structure[w][v]
|
| 87 |
+
):
|
| 88 |
+
return [u, v, w]
|
| 89 |
+
return None
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
@not_implemented_for("directed")
|
| 93 |
+
@not_implemented_for("multigraph")
|
| 94 |
+
@nx._dispatch
|
| 95 |
+
def is_at_free(G):
|
| 96 |
+
"""Check if a graph is AT-free.
|
| 97 |
+
|
| 98 |
+
The method uses the `find_asteroidal_triple` method to recognize
|
| 99 |
+
an AT-free graph. If no asteroidal triple is found the graph is
|
| 100 |
+
AT-free and True is returned. If at least one asteroidal triple is
|
| 101 |
+
found the graph is not AT-free and False is returned.
|
| 102 |
+
|
| 103 |
+
Parameters
|
| 104 |
+
----------
|
| 105 |
+
G : NetworkX Graph
|
| 106 |
+
The graph to check whether is AT-free or not.
|
| 107 |
+
|
| 108 |
+
Returns
|
| 109 |
+
-------
|
| 110 |
+
bool
|
| 111 |
+
True if G is AT-free and False otherwise.
|
| 112 |
+
|
| 113 |
+
Examples
|
| 114 |
+
--------
|
| 115 |
+
>>> G = nx.Graph([(0, 1), (0, 2), (1, 2), (1, 3), (1, 4), (4, 5)])
|
| 116 |
+
>>> nx.is_at_free(G)
|
| 117 |
+
True
|
| 118 |
+
|
| 119 |
+
>>> G = nx.cycle_graph(6)
|
| 120 |
+
>>> nx.is_at_free(G)
|
| 121 |
+
False
|
| 122 |
+
"""
|
| 123 |
+
return find_asteroidal_triple(G) is None
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
@not_implemented_for("directed")
|
| 127 |
+
@not_implemented_for("multigraph")
|
| 128 |
+
@nx._dispatch
|
| 129 |
+
def create_component_structure(G):
|
| 130 |
+
r"""Create component structure for G.
|
| 131 |
+
|
| 132 |
+
A *component structure* is an `nxn` array, denoted `c`, where `n` is
|
| 133 |
+
the number of vertices, where each row and column corresponds to a vertex.
|
| 134 |
+
|
| 135 |
+
.. math::
|
| 136 |
+
c_{uv} = \begin{cases} 0, if v \in N[u] \\
|
| 137 |
+
k, if v \in component k of G \setminus N[u] \end{cases}
|
| 138 |
+
|
| 139 |
+
Where `k` is an arbitrary label for each component. The structure is used
|
| 140 |
+
to simplify the detection of asteroidal triples.
|
| 141 |
+
|
| 142 |
+
Parameters
|
| 143 |
+
----------
|
| 144 |
+
G : NetworkX Graph
|
| 145 |
+
Undirected, simple graph.
|
| 146 |
+
|
| 147 |
+
Returns
|
| 148 |
+
-------
|
| 149 |
+
component_structure : dictionary
|
| 150 |
+
A dictionary of dictionaries, keyed by pairs of vertices.
|
| 151 |
+
|
| 152 |
+
"""
|
| 153 |
+
V = set(G.nodes)
|
| 154 |
+
component_structure = {}
|
| 155 |
+
for v in V:
|
| 156 |
+
label = 0
|
| 157 |
+
closed_neighborhood = set(G[v]).union({v})
|
| 158 |
+
row_dict = {}
|
| 159 |
+
for u in closed_neighborhood:
|
| 160 |
+
row_dict[u] = 0
|
| 161 |
+
|
| 162 |
+
G_reduced = G.subgraph(set(G.nodes) - closed_neighborhood)
|
| 163 |
+
for cc in nx.connected_components(G_reduced):
|
| 164 |
+
label += 1
|
| 165 |
+
for u in cc:
|
| 166 |
+
row_dict[u] = label
|
| 167 |
+
|
| 168 |
+
component_structure[v] = row_dict
|
| 169 |
+
|
| 170 |
+
return component_structure
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/bridges.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Bridge-finding algorithms."""
|
| 2 |
+
from itertools import chain
|
| 3 |
+
|
| 4 |
+
import networkx as nx
|
| 5 |
+
from networkx.utils import not_implemented_for
|
| 6 |
+
|
| 7 |
+
__all__ = ["bridges", "has_bridges", "local_bridges"]
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@not_implemented_for("directed")
|
| 11 |
+
@nx._dispatch
|
| 12 |
+
def bridges(G, root=None):
|
| 13 |
+
"""Generate all bridges in a graph.
|
| 14 |
+
|
| 15 |
+
A *bridge* in a graph is an edge whose removal causes the number of
|
| 16 |
+
connected components of the graph to increase. Equivalently, a bridge is an
|
| 17 |
+
edge that does not belong to any cycle. Bridges are also known as cut-edges,
|
| 18 |
+
isthmuses, or cut arcs.
|
| 19 |
+
|
| 20 |
+
Parameters
|
| 21 |
+
----------
|
| 22 |
+
G : undirected graph
|
| 23 |
+
|
| 24 |
+
root : node (optional)
|
| 25 |
+
A node in the graph `G`. If specified, only the bridges in the
|
| 26 |
+
connected component containing this node will be returned.
|
| 27 |
+
|
| 28 |
+
Yields
|
| 29 |
+
------
|
| 30 |
+
e : edge
|
| 31 |
+
An edge in the graph whose removal disconnects the graph (or
|
| 32 |
+
causes the number of connected components to increase).
|
| 33 |
+
|
| 34 |
+
Raises
|
| 35 |
+
------
|
| 36 |
+
NodeNotFound
|
| 37 |
+
If `root` is not in the graph `G`.
|
| 38 |
+
|
| 39 |
+
NetworkXNotImplemented
|
| 40 |
+
If `G` is a directed graph.
|
| 41 |
+
|
| 42 |
+
Examples
|
| 43 |
+
--------
|
| 44 |
+
The barbell graph with parameter zero has a single bridge:
|
| 45 |
+
|
| 46 |
+
>>> G = nx.barbell_graph(10, 0)
|
| 47 |
+
>>> list(nx.bridges(G))
|
| 48 |
+
[(9, 10)]
|
| 49 |
+
|
| 50 |
+
Notes
|
| 51 |
+
-----
|
| 52 |
+
This is an implementation of the algorithm described in [1]_. An edge is a
|
| 53 |
+
bridge if and only if it is not contained in any chain. Chains are found
|
| 54 |
+
using the :func:`networkx.chain_decomposition` function.
|
| 55 |
+
|
| 56 |
+
The algorithm described in [1]_ requires a simple graph. If the provided
|
| 57 |
+
graph is a multigraph, we convert it to a simple graph and verify that any
|
| 58 |
+
bridges discovered by the chain decomposition algorithm are not multi-edges.
|
| 59 |
+
|
| 60 |
+
Ignoring polylogarithmic factors, the worst-case time complexity is the
|
| 61 |
+
same as the :func:`networkx.chain_decomposition` function,
|
| 62 |
+
$O(m + n)$, where $n$ is the number of nodes in the graph and $m$ is
|
| 63 |
+
the number of edges.
|
| 64 |
+
|
| 65 |
+
References
|
| 66 |
+
----------
|
| 67 |
+
.. [1] https://en.wikipedia.org/wiki/Bridge_%28graph_theory%29#Bridge-Finding_with_Chain_Decompositions
|
| 68 |
+
"""
|
| 69 |
+
multigraph = G.is_multigraph()
|
| 70 |
+
H = nx.Graph(G) if multigraph else G
|
| 71 |
+
chains = nx.chain_decomposition(H, root=root)
|
| 72 |
+
chain_edges = set(chain.from_iterable(chains))
|
| 73 |
+
H_copy = H.copy()
|
| 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._dispatch
|
| 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._dispatch(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")
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/chordal.py
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
import sys
|
| 9 |
+
|
| 10 |
+
import networkx as nx
|
| 11 |
+
from networkx.algorithms.components import connected_components
|
| 12 |
+
from networkx.utils import arbitrary_element, not_implemented_for
|
| 13 |
+
|
| 14 |
+
__all__ = [
|
| 15 |
+
"is_chordal",
|
| 16 |
+
"find_induced_nodes",
|
| 17 |
+
"chordal_graph_cliques",
|
| 18 |
+
"chordal_graph_treewidth",
|
| 19 |
+
"NetworkXTreewidthBoundExceeded",
|
| 20 |
+
"complete_to_chordal_graph",
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class NetworkXTreewidthBoundExceeded(nx.NetworkXException):
|
| 25 |
+
"""Exception raised when a treewidth bound has been provided and it has
|
| 26 |
+
been exceeded"""
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
@not_implemented_for("directed")
|
| 30 |
+
@not_implemented_for("multigraph")
|
| 31 |
+
@nx._dispatch
|
| 32 |
+
def is_chordal(G):
|
| 33 |
+
"""Checks whether G is a chordal graph.
|
| 34 |
+
|
| 35 |
+
A graph is chordal if every cycle of length at least 4 has a chord
|
| 36 |
+
(an edge joining two nodes not adjacent in the cycle).
|
| 37 |
+
|
| 38 |
+
Parameters
|
| 39 |
+
----------
|
| 40 |
+
G : graph
|
| 41 |
+
A NetworkX graph.
|
| 42 |
+
|
| 43 |
+
Returns
|
| 44 |
+
-------
|
| 45 |
+
chordal : bool
|
| 46 |
+
True if G is a chordal graph and False otherwise.
|
| 47 |
+
|
| 48 |
+
Raises
|
| 49 |
+
------
|
| 50 |
+
NetworkXNotImplemented
|
| 51 |
+
The algorithm does not support DiGraph, MultiGraph and MultiDiGraph.
|
| 52 |
+
|
| 53 |
+
Examples
|
| 54 |
+
--------
|
| 55 |
+
>>> e = [
|
| 56 |
+
... (1, 2),
|
| 57 |
+
... (1, 3),
|
| 58 |
+
... (2, 3),
|
| 59 |
+
... (2, 4),
|
| 60 |
+
... (3, 4),
|
| 61 |
+
... (3, 5),
|
| 62 |
+
... (3, 6),
|
| 63 |
+
... (4, 5),
|
| 64 |
+
... (4, 6),
|
| 65 |
+
... (5, 6),
|
| 66 |
+
... ]
|
| 67 |
+
>>> G = nx.Graph(e)
|
| 68 |
+
>>> nx.is_chordal(G)
|
| 69 |
+
True
|
| 70 |
+
|
| 71 |
+
Notes
|
| 72 |
+
-----
|
| 73 |
+
The routine tries to go through every node following maximum cardinality
|
| 74 |
+
search. It returns False when it finds that the separator for any node
|
| 75 |
+
is not a clique. Based on the algorithms in [1]_.
|
| 76 |
+
|
| 77 |
+
Self loops are ignored.
|
| 78 |
+
|
| 79 |
+
References
|
| 80 |
+
----------
|
| 81 |
+
.. [1] R. E. Tarjan and M. Yannakakis, Simple linear-time algorithms
|
| 82 |
+
to test chordality of graphs, test acyclicity of hypergraphs, and
|
| 83 |
+
selectively reduce acyclic hypergraphs, SIAM J. Comput., 13 (1984),
|
| 84 |
+
pp. 566–579.
|
| 85 |
+
"""
|
| 86 |
+
if len(G.nodes) <= 3:
|
| 87 |
+
return True
|
| 88 |
+
return len(_find_chordality_breaker(G)) == 0
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
@nx._dispatch
|
| 92 |
+
def find_induced_nodes(G, s, t, treewidth_bound=sys.maxsize):
|
| 93 |
+
"""Returns the set of induced nodes in the path from s to t.
|
| 94 |
+
|
| 95 |
+
Parameters
|
| 96 |
+
----------
|
| 97 |
+
G : graph
|
| 98 |
+
A chordal NetworkX graph
|
| 99 |
+
s : node
|
| 100 |
+
Source node to look for induced nodes
|
| 101 |
+
t : node
|
| 102 |
+
Destination node to look for induced nodes
|
| 103 |
+
treewidth_bound: float
|
| 104 |
+
Maximum treewidth acceptable for the graph H. The search
|
| 105 |
+
for induced nodes will end as soon as the treewidth_bound is exceeded.
|
| 106 |
+
|
| 107 |
+
Returns
|
| 108 |
+
-------
|
| 109 |
+
induced_nodes : Set of nodes
|
| 110 |
+
The set of induced nodes in the path from s to t in G
|
| 111 |
+
|
| 112 |
+
Raises
|
| 113 |
+
------
|
| 114 |
+
NetworkXError
|
| 115 |
+
The algorithm does not support DiGraph, MultiGraph and MultiDiGraph.
|
| 116 |
+
If the input graph is an instance of one of these classes, a
|
| 117 |
+
:exc:`NetworkXError` is raised.
|
| 118 |
+
The algorithm can only be applied to chordal graphs. If the input
|
| 119 |
+
graph is found to be non-chordal, a :exc:`NetworkXError` is raised.
|
| 120 |
+
|
| 121 |
+
Examples
|
| 122 |
+
--------
|
| 123 |
+
>>> G = nx.Graph()
|
| 124 |
+
>>> G = nx.generators.classic.path_graph(10)
|
| 125 |
+
>>> induced_nodes = nx.find_induced_nodes(G, 1, 9, 2)
|
| 126 |
+
>>> sorted(induced_nodes)
|
| 127 |
+
[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
| 128 |
+
|
| 129 |
+
Notes
|
| 130 |
+
-----
|
| 131 |
+
G must be a chordal graph and (s,t) an edge that is not in G.
|
| 132 |
+
|
| 133 |
+
If a treewidth_bound is provided, the search for induced nodes will end
|
| 134 |
+
as soon as the treewidth_bound is exceeded.
|
| 135 |
+
|
| 136 |
+
The algorithm is inspired by Algorithm 4 in [1]_.
|
| 137 |
+
A formal definition of induced node can also be found on that reference.
|
| 138 |
+
|
| 139 |
+
Self Loops are ignored
|
| 140 |
+
|
| 141 |
+
References
|
| 142 |
+
----------
|
| 143 |
+
.. [1] Learning Bounded Treewidth Bayesian Networks.
|
| 144 |
+
Gal Elidan, Stephen Gould; JMLR, 9(Dec):2699--2731, 2008.
|
| 145 |
+
http://jmlr.csail.mit.edu/papers/volume9/elidan08a/elidan08a.pdf
|
| 146 |
+
"""
|
| 147 |
+
if not is_chordal(G):
|
| 148 |
+
raise nx.NetworkXError("Input graph is not chordal.")
|
| 149 |
+
|
| 150 |
+
H = nx.Graph(G)
|
| 151 |
+
H.add_edge(s, t)
|
| 152 |
+
induced_nodes = set()
|
| 153 |
+
triplet = _find_chordality_breaker(H, s, treewidth_bound)
|
| 154 |
+
while triplet:
|
| 155 |
+
(u, v, w) = triplet
|
| 156 |
+
induced_nodes.update(triplet)
|
| 157 |
+
for n in triplet:
|
| 158 |
+
if n != s:
|
| 159 |
+
H.add_edge(s, n)
|
| 160 |
+
triplet = _find_chordality_breaker(H, s, treewidth_bound)
|
| 161 |
+
if induced_nodes:
|
| 162 |
+
# Add t and the second node in the induced path from s to t.
|
| 163 |
+
induced_nodes.add(t)
|
| 164 |
+
for u in G[s]:
|
| 165 |
+
if len(induced_nodes & set(G[u])) == 2:
|
| 166 |
+
induced_nodes.add(u)
|
| 167 |
+
break
|
| 168 |
+
return induced_nodes
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
@nx._dispatch
|
| 172 |
+
def chordal_graph_cliques(G):
|
| 173 |
+
"""Returns all maximal cliques of a chordal graph.
|
| 174 |
+
|
| 175 |
+
The algorithm breaks the graph in connected components and performs a
|
| 176 |
+
maximum cardinality search in each component to get the cliques.
|
| 177 |
+
|
| 178 |
+
Parameters
|
| 179 |
+
----------
|
| 180 |
+
G : graph
|
| 181 |
+
A NetworkX graph
|
| 182 |
+
|
| 183 |
+
Yields
|
| 184 |
+
------
|
| 185 |
+
frozenset of nodes
|
| 186 |
+
Maximal cliques, each of which is a frozenset of
|
| 187 |
+
nodes in `G`. The order of cliques is arbitrary.
|
| 188 |
+
|
| 189 |
+
Raises
|
| 190 |
+
------
|
| 191 |
+
NetworkXError
|
| 192 |
+
The algorithm does not support DiGraph, MultiGraph and MultiDiGraph.
|
| 193 |
+
The algorithm can only be applied to chordal graphs. If the input
|
| 194 |
+
graph is found to be non-chordal, a :exc:`NetworkXError` is raised.
|
| 195 |
+
|
| 196 |
+
Examples
|
| 197 |
+
--------
|
| 198 |
+
>>> e = [
|
| 199 |
+
... (1, 2),
|
| 200 |
+
... (1, 3),
|
| 201 |
+
... (2, 3),
|
| 202 |
+
... (2, 4),
|
| 203 |
+
... (3, 4),
|
| 204 |
+
... (3, 5),
|
| 205 |
+
... (3, 6),
|
| 206 |
+
... (4, 5),
|
| 207 |
+
... (4, 6),
|
| 208 |
+
... (5, 6),
|
| 209 |
+
... (7, 8),
|
| 210 |
+
... ]
|
| 211 |
+
>>> G = nx.Graph(e)
|
| 212 |
+
>>> G.add_node(9)
|
| 213 |
+
>>> cliques = [c for c in chordal_graph_cliques(G)]
|
| 214 |
+
>>> cliques[0]
|
| 215 |
+
frozenset({1, 2, 3})
|
| 216 |
+
"""
|
| 217 |
+
for C in (G.subgraph(c).copy() for c in connected_components(G)):
|
| 218 |
+
if C.number_of_nodes() == 1:
|
| 219 |
+
if nx.number_of_selfloops(C) > 0:
|
| 220 |
+
raise nx.NetworkXError("Input graph is not chordal.")
|
| 221 |
+
yield frozenset(C.nodes())
|
| 222 |
+
else:
|
| 223 |
+
unnumbered = set(C.nodes())
|
| 224 |
+
v = arbitrary_element(C)
|
| 225 |
+
unnumbered.remove(v)
|
| 226 |
+
numbered = {v}
|
| 227 |
+
clique_wanna_be = {v}
|
| 228 |
+
while unnumbered:
|
| 229 |
+
v = _max_cardinality_node(C, unnumbered, numbered)
|
| 230 |
+
unnumbered.remove(v)
|
| 231 |
+
numbered.add(v)
|
| 232 |
+
new_clique_wanna_be = set(C.neighbors(v)) & numbered
|
| 233 |
+
sg = C.subgraph(clique_wanna_be)
|
| 234 |
+
if _is_complete_graph(sg):
|
| 235 |
+
new_clique_wanna_be.add(v)
|
| 236 |
+
if not new_clique_wanna_be >= clique_wanna_be:
|
| 237 |
+
yield frozenset(clique_wanna_be)
|
| 238 |
+
clique_wanna_be = new_clique_wanna_be
|
| 239 |
+
else:
|
| 240 |
+
raise nx.NetworkXError("Input graph is not chordal.")
|
| 241 |
+
yield frozenset(clique_wanna_be)
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
@nx._dispatch
|
| 245 |
+
def chordal_graph_treewidth(G):
|
| 246 |
+
"""Returns the treewidth of the chordal graph G.
|
| 247 |
+
|
| 248 |
+
Parameters
|
| 249 |
+
----------
|
| 250 |
+
G : graph
|
| 251 |
+
A NetworkX graph
|
| 252 |
+
|
| 253 |
+
Returns
|
| 254 |
+
-------
|
| 255 |
+
treewidth : int
|
| 256 |
+
The size of the largest clique in the graph minus one.
|
| 257 |
+
|
| 258 |
+
Raises
|
| 259 |
+
------
|
| 260 |
+
NetworkXError
|
| 261 |
+
The algorithm does not support DiGraph, MultiGraph and MultiDiGraph.
|
| 262 |
+
The algorithm can only be applied to chordal graphs. If the input
|
| 263 |
+
graph is found to be non-chordal, a :exc:`NetworkXError` is raised.
|
| 264 |
+
|
| 265 |
+
Examples
|
| 266 |
+
--------
|
| 267 |
+
>>> e = [
|
| 268 |
+
... (1, 2),
|
| 269 |
+
... (1, 3),
|
| 270 |
+
... (2, 3),
|
| 271 |
+
... (2, 4),
|
| 272 |
+
... (3, 4),
|
| 273 |
+
... (3, 5),
|
| 274 |
+
... (3, 6),
|
| 275 |
+
... (4, 5),
|
| 276 |
+
... (4, 6),
|
| 277 |
+
... (5, 6),
|
| 278 |
+
... (7, 8),
|
| 279 |
+
... ]
|
| 280 |
+
>>> G = nx.Graph(e)
|
| 281 |
+
>>> G.add_node(9)
|
| 282 |
+
>>> nx.chordal_graph_treewidth(G)
|
| 283 |
+
3
|
| 284 |
+
|
| 285 |
+
References
|
| 286 |
+
----------
|
| 287 |
+
.. [1] https://en.wikipedia.org/wiki/Tree_decomposition#Treewidth
|
| 288 |
+
"""
|
| 289 |
+
if not is_chordal(G):
|
| 290 |
+
raise nx.NetworkXError("Input graph is not chordal.")
|
| 291 |
+
|
| 292 |
+
max_clique = -1
|
| 293 |
+
for clique in nx.chordal_graph_cliques(G):
|
| 294 |
+
max_clique = max(max_clique, len(clique))
|
| 295 |
+
return max_clique - 1
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
def _is_complete_graph(G):
|
| 299 |
+
"""Returns True if G is a complete graph."""
|
| 300 |
+
if nx.number_of_selfloops(G) > 0:
|
| 301 |
+
raise nx.NetworkXError("Self loop found in _is_complete_graph()")
|
| 302 |
+
n = G.number_of_nodes()
|
| 303 |
+
if n < 2:
|
| 304 |
+
return True
|
| 305 |
+
e = G.number_of_edges()
|
| 306 |
+
max_edges = (n * (n - 1)) / 2
|
| 307 |
+
return e == max_edges
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
def _find_missing_edge(G):
|
| 311 |
+
"""Given a non-complete graph G, returns a missing edge."""
|
| 312 |
+
nodes = set(G)
|
| 313 |
+
for u in G:
|
| 314 |
+
missing = nodes - set(list(G[u].keys()) + [u])
|
| 315 |
+
if missing:
|
| 316 |
+
return (u, missing.pop())
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def _max_cardinality_node(G, choices, wanna_connect):
|
| 320 |
+
"""Returns a the node in choices that has more connections in G
|
| 321 |
+
to nodes in wanna_connect.
|
| 322 |
+
"""
|
| 323 |
+
max_number = -1
|
| 324 |
+
for x in choices:
|
| 325 |
+
number = len([y for y in G[x] if y in wanna_connect])
|
| 326 |
+
if number > max_number:
|
| 327 |
+
max_number = number
|
| 328 |
+
max_cardinality_node = x
|
| 329 |
+
return max_cardinality_node
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
def _find_chordality_breaker(G, s=None, treewidth_bound=sys.maxsize):
|
| 333 |
+
"""Given a graph G, starts a max cardinality search
|
| 334 |
+
(starting from s if s is given and from an arbitrary node otherwise)
|
| 335 |
+
trying to find a non-chordal cycle.
|
| 336 |
+
|
| 337 |
+
If it does find one, it returns (u,v,w) where u,v,w are the three
|
| 338 |
+
nodes that together with s are involved in the cycle.
|
| 339 |
+
|
| 340 |
+
It ignores any self loops.
|
| 341 |
+
"""
|
| 342 |
+
unnumbered = set(G)
|
| 343 |
+
if s is None:
|
| 344 |
+
s = arbitrary_element(G)
|
| 345 |
+
unnumbered.remove(s)
|
| 346 |
+
numbered = {s}
|
| 347 |
+
current_treewidth = -1
|
| 348 |
+
while unnumbered: # and current_treewidth <= treewidth_bound:
|
| 349 |
+
v = _max_cardinality_node(G, unnumbered, numbered)
|
| 350 |
+
unnumbered.remove(v)
|
| 351 |
+
numbered.add(v)
|
| 352 |
+
clique_wanna_be = set(G[v]) & numbered
|
| 353 |
+
sg = G.subgraph(clique_wanna_be)
|
| 354 |
+
if _is_complete_graph(sg):
|
| 355 |
+
# The graph seems to be chordal by now. We update the treewidth
|
| 356 |
+
current_treewidth = max(current_treewidth, len(clique_wanna_be))
|
| 357 |
+
if current_treewidth > treewidth_bound:
|
| 358 |
+
raise nx.NetworkXTreewidthBoundExceeded(
|
| 359 |
+
f"treewidth_bound exceeded: {current_treewidth}"
|
| 360 |
+
)
|
| 361 |
+
else:
|
| 362 |
+
# sg is not a clique,
|
| 363 |
+
# look for an edge that is not included in sg
|
| 364 |
+
(u, w) = _find_missing_edge(sg)
|
| 365 |
+
return (u, v, w)
|
| 366 |
+
return ()
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
@not_implemented_for("directed")
|
| 370 |
+
@nx._dispatch
|
| 371 |
+
def complete_to_chordal_graph(G):
|
| 372 |
+
"""Return a copy of G completed to a chordal graph
|
| 373 |
+
|
| 374 |
+
Adds edges to a copy of G to create a chordal graph. A graph G=(V,E) is
|
| 375 |
+
called chordal if for each cycle with length bigger than 3, there exist
|
| 376 |
+
two non-adjacent nodes connected by an edge (called a chord).
|
| 377 |
+
|
| 378 |
+
Parameters
|
| 379 |
+
----------
|
| 380 |
+
G : NetworkX graph
|
| 381 |
+
Undirected graph
|
| 382 |
+
|
| 383 |
+
Returns
|
| 384 |
+
-------
|
| 385 |
+
H : NetworkX graph
|
| 386 |
+
The chordal enhancement of G
|
| 387 |
+
alpha : Dictionary
|
| 388 |
+
The elimination ordering of nodes of G
|
| 389 |
+
|
| 390 |
+
Notes
|
| 391 |
+
-----
|
| 392 |
+
There are different approaches to calculate the chordal
|
| 393 |
+
enhancement of a graph. The algorithm used here is called
|
| 394 |
+
MCS-M and gives at least minimal (local) triangulation of graph. Note
|
| 395 |
+
that this triangulation is not necessarily a global minimum.
|
| 396 |
+
|
| 397 |
+
https://en.wikipedia.org/wiki/Chordal_graph
|
| 398 |
+
|
| 399 |
+
References
|
| 400 |
+
----------
|
| 401 |
+
.. [1] Berry, Anne & Blair, Jean & Heggernes, Pinar & Peyton, Barry. (2004)
|
| 402 |
+
Maximum Cardinality Search for Computing Minimal Triangulations of
|
| 403 |
+
Graphs. Algorithmica. 39. 287-298. 10.1007/s00453-004-1084-3.
|
| 404 |
+
|
| 405 |
+
Examples
|
| 406 |
+
--------
|
| 407 |
+
>>> from networkx.algorithms.chordal import complete_to_chordal_graph
|
| 408 |
+
>>> G = nx.wheel_graph(10)
|
| 409 |
+
>>> H, alpha = complete_to_chordal_graph(G)
|
| 410 |
+
"""
|
| 411 |
+
H = G.copy()
|
| 412 |
+
alpha = {node: 0 for node in H}
|
| 413 |
+
if nx.is_chordal(H):
|
| 414 |
+
return H, alpha
|
| 415 |
+
chords = set()
|
| 416 |
+
weight = {node: 0 for node in H.nodes()}
|
| 417 |
+
unnumbered_nodes = list(H.nodes())
|
| 418 |
+
for i in range(len(H.nodes()), 0, -1):
|
| 419 |
+
# get the node in unnumbered_nodes with the maximum weight
|
| 420 |
+
z = max(unnumbered_nodes, key=lambda node: weight[node])
|
| 421 |
+
unnumbered_nodes.remove(z)
|
| 422 |
+
alpha[z] = i
|
| 423 |
+
update_nodes = []
|
| 424 |
+
for y in unnumbered_nodes:
|
| 425 |
+
if G.has_edge(y, z):
|
| 426 |
+
update_nodes.append(y)
|
| 427 |
+
else:
|
| 428 |
+
# y_weight will be bigger than node weights between y and z
|
| 429 |
+
y_weight = weight[y]
|
| 430 |
+
lower_nodes = [
|
| 431 |
+
node for node in unnumbered_nodes if weight[node] < y_weight
|
| 432 |
+
]
|
| 433 |
+
if nx.has_path(H.subgraph(lower_nodes + [z, y]), y, z):
|
| 434 |
+
update_nodes.append(y)
|
| 435 |
+
chords.add((z, y))
|
| 436 |
+
# during calculation of paths the weights should not be updated
|
| 437 |
+
for node in update_nodes:
|
| 438 |
+
weight[node] += 1
|
| 439 |
+
H.add_edges_from(chords)
|
| 440 |
+
return H, alpha
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/cluster.py
ADDED
|
@@ -0,0 +1,605 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
"average_clustering",
|
| 12 |
+
"clustering",
|
| 13 |
+
"transitivity",
|
| 14 |
+
"square_clustering",
|
| 15 |
+
"generalized_degree",
|
| 16 |
+
]
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@not_implemented_for("directed")
|
| 20 |
+
@nx._dispatch
|
| 21 |
+
def triangles(G, nodes=None):
|
| 22 |
+
"""Compute the number of triangles.
|
| 23 |
+
|
| 24 |
+
Finds the number of triangles that include a node as one vertex.
|
| 25 |
+
|
| 26 |
+
Parameters
|
| 27 |
+
----------
|
| 28 |
+
G : graph
|
| 29 |
+
A networkx graph
|
| 30 |
+
|
| 31 |
+
nodes : node, iterable of nodes, or None (default=None)
|
| 32 |
+
If a singleton node, return the number of triangles for that node.
|
| 33 |
+
If an iterable, compute the number of triangles for each of those nodes.
|
| 34 |
+
If `None` (the default) compute the number of triangles for all nodes in `G`.
|
| 35 |
+
|
| 36 |
+
Returns
|
| 37 |
+
-------
|
| 38 |
+
out : dict or int
|
| 39 |
+
If `nodes` is a container of nodes, returns number of triangles keyed by node (dict).
|
| 40 |
+
If `nodes` is a specific node, returns number of triangles for the node (int).
|
| 41 |
+
|
| 42 |
+
Examples
|
| 43 |
+
--------
|
| 44 |
+
>>> G = nx.complete_graph(5)
|
| 45 |
+
>>> print(nx.triangles(G, 0))
|
| 46 |
+
6
|
| 47 |
+
>>> print(nx.triangles(G))
|
| 48 |
+
{0: 6, 1: 6, 2: 6, 3: 6, 4: 6}
|
| 49 |
+
>>> print(list(nx.triangles(G, [0, 1]).values()))
|
| 50 |
+
[6, 6]
|
| 51 |
+
|
| 52 |
+
Notes
|
| 53 |
+
-----
|
| 54 |
+
Self loops are ignored.
|
| 55 |
+
|
| 56 |
+
"""
|
| 57 |
+
if nodes is not None:
|
| 58 |
+
# If `nodes` represents a single node, return only its number of triangles
|
| 59 |
+
if nodes in G:
|
| 60 |
+
return next(_triangles_and_degree_iter(G, nodes))[2] // 2
|
| 61 |
+
|
| 62 |
+
# if `nodes` is a container of nodes, then return a
|
| 63 |
+
# dictionary mapping node to number of triangles.
|
| 64 |
+
return {v: t // 2 for v, d, t, _ in _triangles_and_degree_iter(G, nodes)}
|
| 65 |
+
|
| 66 |
+
# if nodes is None, then compute triangles for the complete graph
|
| 67 |
+
|
| 68 |
+
# dict used to avoid visiting the same nodes twice
|
| 69 |
+
# this allows calculating/counting each triangle only once
|
| 70 |
+
later_neighbors = {}
|
| 71 |
+
|
| 72 |
+
# iterate over the nodes in a graph
|
| 73 |
+
for node, neighbors in G.adjacency():
|
| 74 |
+
later_neighbors[node] = {
|
| 75 |
+
n for n in neighbors if n not in later_neighbors and n != node
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
# instantiate Counter for each node to include isolated nodes
|
| 79 |
+
# add 1 to the count if a nodes neighbor's neighbor is also a neighbor
|
| 80 |
+
triangle_counts = Counter(dict.fromkeys(G, 0))
|
| 81 |
+
for node1, neighbors in later_neighbors.items():
|
| 82 |
+
for node2 in neighbors:
|
| 83 |
+
third_nodes = neighbors & later_neighbors[node2]
|
| 84 |
+
m = len(third_nodes)
|
| 85 |
+
triangle_counts[node1] += m
|
| 86 |
+
triangle_counts[node2] += m
|
| 87 |
+
triangle_counts.update(third_nodes)
|
| 88 |
+
|
| 89 |
+
return dict(triangle_counts)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
@not_implemented_for("multigraph")
|
| 93 |
+
def _triangles_and_degree_iter(G, nodes=None):
|
| 94 |
+
"""Return an iterator of (node, degree, triangles, generalized degree).
|
| 95 |
+
|
| 96 |
+
This double counts triangles so you may want to divide by 2.
|
| 97 |
+
See degree(), triangles() and generalized_degree() for definitions
|
| 98 |
+
and details.
|
| 99 |
+
|
| 100 |
+
"""
|
| 101 |
+
if nodes is None:
|
| 102 |
+
nodes_nbrs = G.adj.items()
|
| 103 |
+
else:
|
| 104 |
+
nodes_nbrs = ((n, G[n]) for n in G.nbunch_iter(nodes))
|
| 105 |
+
|
| 106 |
+
for v, v_nbrs in nodes_nbrs:
|
| 107 |
+
vs = set(v_nbrs) - {v}
|
| 108 |
+
gen_degree = Counter(len(vs & (set(G[w]) - {w})) for w in vs)
|
| 109 |
+
ntriangles = sum(k * val for k, val in gen_degree.items())
|
| 110 |
+
yield (v, len(vs), ntriangles, gen_degree)
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
@not_implemented_for("multigraph")
|
| 114 |
+
def _weighted_triangles_and_degree_iter(G, nodes=None, weight="weight"):
|
| 115 |
+
"""Return an iterator of (node, degree, weighted_triangles).
|
| 116 |
+
|
| 117 |
+
Used for weighted clustering.
|
| 118 |
+
Note: this returns the geometric average weight of edges in the triangle.
|
| 119 |
+
Also, each triangle is counted twice (each direction).
|
| 120 |
+
So you may want to divide by 2.
|
| 121 |
+
|
| 122 |
+
"""
|
| 123 |
+
import numpy as np
|
| 124 |
+
|
| 125 |
+
if weight is None or G.number_of_edges() == 0:
|
| 126 |
+
max_weight = 1
|
| 127 |
+
else:
|
| 128 |
+
max_weight = max(d.get(weight, 1) for u, v, d in G.edges(data=True))
|
| 129 |
+
if nodes is None:
|
| 130 |
+
nodes_nbrs = G.adj.items()
|
| 131 |
+
else:
|
| 132 |
+
nodes_nbrs = ((n, G[n]) for n in G.nbunch_iter(nodes))
|
| 133 |
+
|
| 134 |
+
def wt(u, v):
|
| 135 |
+
return G[u][v].get(weight, 1) / max_weight
|
| 136 |
+
|
| 137 |
+
for i, nbrs in nodes_nbrs:
|
| 138 |
+
inbrs = set(nbrs) - {i}
|
| 139 |
+
weighted_triangles = 0
|
| 140 |
+
seen = set()
|
| 141 |
+
for j in inbrs:
|
| 142 |
+
seen.add(j)
|
| 143 |
+
# This avoids counting twice -- we double at the end.
|
| 144 |
+
jnbrs = set(G[j]) - seen
|
| 145 |
+
# Only compute the edge weight once, before the inner inner
|
| 146 |
+
# loop.
|
| 147 |
+
wij = wt(i, j)
|
| 148 |
+
weighted_triangles += sum(
|
| 149 |
+
np.cbrt([(wij * wt(j, k) * wt(k, i)) for k in inbrs & jnbrs])
|
| 150 |
+
)
|
| 151 |
+
yield (i, len(inbrs), 2 * weighted_triangles)
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
@not_implemented_for("multigraph")
|
| 155 |
+
def _directed_triangles_and_degree_iter(G, nodes=None):
|
| 156 |
+
"""Return an iterator of
|
| 157 |
+
(node, total_degree, reciprocal_degree, directed_triangles).
|
| 158 |
+
|
| 159 |
+
Used for directed clustering.
|
| 160 |
+
Note that unlike `_triangles_and_degree_iter()`, this function counts
|
| 161 |
+
directed triangles so does not count triangles twice.
|
| 162 |
+
|
| 163 |
+
"""
|
| 164 |
+
nodes_nbrs = ((n, G._pred[n], G._succ[n]) for n in G.nbunch_iter(nodes))
|
| 165 |
+
|
| 166 |
+
for i, preds, succs in nodes_nbrs:
|
| 167 |
+
ipreds = set(preds) - {i}
|
| 168 |
+
isuccs = set(succs) - {i}
|
| 169 |
+
|
| 170 |
+
directed_triangles = 0
|
| 171 |
+
for j in chain(ipreds, isuccs):
|
| 172 |
+
jpreds = set(G._pred[j]) - {j}
|
| 173 |
+
jsuccs = set(G._succ[j]) - {j}
|
| 174 |
+
directed_triangles += sum(
|
| 175 |
+
1
|
| 176 |
+
for k in chain(
|
| 177 |
+
(ipreds & jpreds),
|
| 178 |
+
(ipreds & jsuccs),
|
| 179 |
+
(isuccs & jpreds),
|
| 180 |
+
(isuccs & jsuccs),
|
| 181 |
+
)
|
| 182 |
+
)
|
| 183 |
+
dtotal = len(ipreds) + len(isuccs)
|
| 184 |
+
dbidirectional = len(ipreds & isuccs)
|
| 185 |
+
yield (i, dtotal, dbidirectional, directed_triangles)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
@not_implemented_for("multigraph")
|
| 189 |
+
def _directed_weighted_triangles_and_degree_iter(G, nodes=None, weight="weight"):
|
| 190 |
+
"""Return an iterator of
|
| 191 |
+
(node, total_degree, reciprocal_degree, directed_weighted_triangles).
|
| 192 |
+
|
| 193 |
+
Used for directed weighted clustering.
|
| 194 |
+
Note that unlike `_weighted_triangles_and_degree_iter()`, this function counts
|
| 195 |
+
directed triangles so does not count triangles twice.
|
| 196 |
+
|
| 197 |
+
"""
|
| 198 |
+
import numpy as np
|
| 199 |
+
|
| 200 |
+
if weight is None or G.number_of_edges() == 0:
|
| 201 |
+
max_weight = 1
|
| 202 |
+
else:
|
| 203 |
+
max_weight = max(d.get(weight, 1) for u, v, d in G.edges(data=True))
|
| 204 |
+
|
| 205 |
+
nodes_nbrs = ((n, G._pred[n], G._succ[n]) for n in G.nbunch_iter(nodes))
|
| 206 |
+
|
| 207 |
+
def wt(u, v):
|
| 208 |
+
return G[u][v].get(weight, 1) / max_weight
|
| 209 |
+
|
| 210 |
+
for i, preds, succs in nodes_nbrs:
|
| 211 |
+
ipreds = set(preds) - {i}
|
| 212 |
+
isuccs = set(succs) - {i}
|
| 213 |
+
|
| 214 |
+
directed_triangles = 0
|
| 215 |
+
for j in ipreds:
|
| 216 |
+
jpreds = set(G._pred[j]) - {j}
|
| 217 |
+
jsuccs = set(G._succ[j]) - {j}
|
| 218 |
+
directed_triangles += sum(
|
| 219 |
+
np.cbrt([(wt(j, i) * wt(k, i) * wt(k, j)) for k in ipreds & jpreds])
|
| 220 |
+
)
|
| 221 |
+
directed_triangles += sum(
|
| 222 |
+
np.cbrt([(wt(j, i) * wt(k, i) * wt(j, k)) for k in ipreds & jsuccs])
|
| 223 |
+
)
|
| 224 |
+
directed_triangles += sum(
|
| 225 |
+
np.cbrt([(wt(j, i) * wt(i, k) * wt(k, j)) for k in isuccs & jpreds])
|
| 226 |
+
)
|
| 227 |
+
directed_triangles += sum(
|
| 228 |
+
np.cbrt([(wt(j, i) * wt(i, k) * wt(j, k)) for k in isuccs & jsuccs])
|
| 229 |
+
)
|
| 230 |
+
|
| 231 |
+
for j in isuccs:
|
| 232 |
+
jpreds = set(G._pred[j]) - {j}
|
| 233 |
+
jsuccs = set(G._succ[j]) - {j}
|
| 234 |
+
directed_triangles += sum(
|
| 235 |
+
np.cbrt([(wt(i, j) * wt(k, i) * wt(k, j)) for k in ipreds & jpreds])
|
| 236 |
+
)
|
| 237 |
+
directed_triangles += sum(
|
| 238 |
+
np.cbrt([(wt(i, j) * wt(k, i) * wt(j, k)) for k in ipreds & jsuccs])
|
| 239 |
+
)
|
| 240 |
+
directed_triangles += sum(
|
| 241 |
+
np.cbrt([(wt(i, j) * wt(i, k) * wt(k, j)) for k in isuccs & jpreds])
|
| 242 |
+
)
|
| 243 |
+
directed_triangles += sum(
|
| 244 |
+
np.cbrt([(wt(i, j) * wt(i, k) * wt(j, k)) for k in isuccs & jsuccs])
|
| 245 |
+
)
|
| 246 |
+
|
| 247 |
+
dtotal = len(ipreds) + len(isuccs)
|
| 248 |
+
dbidirectional = len(ipreds & isuccs)
|
| 249 |
+
yield (i, dtotal, dbidirectional, directed_triangles)
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
@nx._dispatch(edge_attrs="weight")
|
| 253 |
+
def average_clustering(G, nodes=None, weight=None, count_zeros=True):
|
| 254 |
+
r"""Compute the average clustering coefficient for the graph G.
|
| 255 |
+
|
| 256 |
+
The clustering coefficient for the graph is the average,
|
| 257 |
+
|
| 258 |
+
.. math::
|
| 259 |
+
|
| 260 |
+
C = \frac{1}{n}\sum_{v \in G} c_v,
|
| 261 |
+
|
| 262 |
+
where :math:`n` is the number of nodes in `G`.
|
| 263 |
+
|
| 264 |
+
Parameters
|
| 265 |
+
----------
|
| 266 |
+
G : graph
|
| 267 |
+
|
| 268 |
+
nodes : container of nodes, optional (default=all nodes in G)
|
| 269 |
+
Compute average clustering for nodes in this container.
|
| 270 |
+
|
| 271 |
+
weight : string or None, optional (default=None)
|
| 272 |
+
The edge attribute that holds the numerical value used as a weight.
|
| 273 |
+
If None, then each edge has weight 1.
|
| 274 |
+
|
| 275 |
+
count_zeros : bool
|
| 276 |
+
If False include only the nodes with nonzero clustering in the average.
|
| 277 |
+
|
| 278 |
+
Returns
|
| 279 |
+
-------
|
| 280 |
+
avg : float
|
| 281 |
+
Average clustering
|
| 282 |
+
|
| 283 |
+
Examples
|
| 284 |
+
--------
|
| 285 |
+
>>> G = nx.complete_graph(5)
|
| 286 |
+
>>> print(nx.average_clustering(G))
|
| 287 |
+
1.0
|
| 288 |
+
|
| 289 |
+
Notes
|
| 290 |
+
-----
|
| 291 |
+
This is a space saving routine; it might be faster
|
| 292 |
+
to use the clustering function to get a list and then take the average.
|
| 293 |
+
|
| 294 |
+
Self loops are ignored.
|
| 295 |
+
|
| 296 |
+
References
|
| 297 |
+
----------
|
| 298 |
+
.. [1] Generalizations of the clustering coefficient to weighted
|
| 299 |
+
complex networks by J. Saramäki, M. Kivelä, J.-P. Onnela,
|
| 300 |
+
K. Kaski, and J. Kertész, Physical Review E, 75 027105 (2007).
|
| 301 |
+
http://jponnela.com/web_documents/a9.pdf
|
| 302 |
+
.. [2] Marcus Kaiser, Mean clustering coefficients: the role of isolated
|
| 303 |
+
nodes and leafs on clustering measures for small-world networks.
|
| 304 |
+
https://arxiv.org/abs/0802.2512
|
| 305 |
+
"""
|
| 306 |
+
c = clustering(G, nodes, weight=weight).values()
|
| 307 |
+
if not count_zeros:
|
| 308 |
+
c = [v for v in c if abs(v) > 0]
|
| 309 |
+
return sum(c) / len(c)
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
@nx._dispatch(edge_attrs="weight")
|
| 313 |
+
def clustering(G, nodes=None, weight=None):
|
| 314 |
+
r"""Compute the clustering coefficient for nodes.
|
| 315 |
+
|
| 316 |
+
For unweighted graphs, the clustering of a node :math:`u`
|
| 317 |
+
is the fraction of possible triangles through that node that exist,
|
| 318 |
+
|
| 319 |
+
.. math::
|
| 320 |
+
|
| 321 |
+
c_u = \frac{2 T(u)}{deg(u)(deg(u)-1)},
|
| 322 |
+
|
| 323 |
+
where :math:`T(u)` is the number of triangles through node :math:`u` and
|
| 324 |
+
:math:`deg(u)` is the degree of :math:`u`.
|
| 325 |
+
|
| 326 |
+
For weighted graphs, there are several ways to define clustering [1]_.
|
| 327 |
+
the one used here is defined
|
| 328 |
+
as the geometric average of the subgraph edge weights [2]_,
|
| 329 |
+
|
| 330 |
+
.. math::
|
| 331 |
+
|
| 332 |
+
c_u = \frac{1}{deg(u)(deg(u)-1))}
|
| 333 |
+
\sum_{vw} (\hat{w}_{uv} \hat{w}_{uw} \hat{w}_{vw})^{1/3}.
|
| 334 |
+
|
| 335 |
+
The edge weights :math:`\hat{w}_{uv}` are normalized by the maximum weight
|
| 336 |
+
in the network :math:`\hat{w}_{uv} = w_{uv}/\max(w)`.
|
| 337 |
+
|
| 338 |
+
The value of :math:`c_u` is assigned to 0 if :math:`deg(u) < 2`.
|
| 339 |
+
|
| 340 |
+
Additionally, this weighted definition has been generalized to support negative edge weights [3]_.
|
| 341 |
+
|
| 342 |
+
For directed graphs, the clustering is similarly defined as the fraction
|
| 343 |
+
of all possible directed triangles or geometric average of the subgraph
|
| 344 |
+
edge weights for unweighted and weighted directed graph respectively [4]_.
|
| 345 |
+
|
| 346 |
+
.. math::
|
| 347 |
+
|
| 348 |
+
c_u = \frac{T(u)}{2(deg^{tot}(u)(deg^{tot}(u)-1) - 2deg^{\leftrightarrow}(u))},
|
| 349 |
+
|
| 350 |
+
where :math:`T(u)` is the number of directed triangles through node
|
| 351 |
+
:math:`u`, :math:`deg^{tot}(u)` is the sum of in degree and out degree of
|
| 352 |
+
:math:`u` and :math:`deg^{\leftrightarrow}(u)` is the reciprocal degree of
|
| 353 |
+
:math:`u`.
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
Parameters
|
| 357 |
+
----------
|
| 358 |
+
G : graph
|
| 359 |
+
|
| 360 |
+
nodes : node, iterable of nodes, or None (default=None)
|
| 361 |
+
If a singleton node, return the number of triangles for that node.
|
| 362 |
+
If an iterable, compute the number of triangles for each of those nodes.
|
| 363 |
+
If `None` (the default) compute the number of triangles for all nodes in `G`.
|
| 364 |
+
|
| 365 |
+
weight : string or None, optional (default=None)
|
| 366 |
+
The edge attribute that holds the numerical value used as a weight.
|
| 367 |
+
If None, then each edge has weight 1.
|
| 368 |
+
|
| 369 |
+
Returns
|
| 370 |
+
-------
|
| 371 |
+
out : float, or dictionary
|
| 372 |
+
Clustering coefficient at specified nodes
|
| 373 |
+
|
| 374 |
+
Examples
|
| 375 |
+
--------
|
| 376 |
+
>>> G = nx.complete_graph(5)
|
| 377 |
+
>>> print(nx.clustering(G, 0))
|
| 378 |
+
1.0
|
| 379 |
+
>>> print(nx.clustering(G))
|
| 380 |
+
{0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0}
|
| 381 |
+
|
| 382 |
+
Notes
|
| 383 |
+
-----
|
| 384 |
+
Self loops are ignored.
|
| 385 |
+
|
| 386 |
+
References
|
| 387 |
+
----------
|
| 388 |
+
.. [1] Generalizations of the clustering coefficient to weighted
|
| 389 |
+
complex networks by J. Saramäki, M. Kivelä, J.-P. Onnela,
|
| 390 |
+
K. Kaski, and J. Kertész, Physical Review E, 75 027105 (2007).
|
| 391 |
+
http://jponnela.com/web_documents/a9.pdf
|
| 392 |
+
.. [2] Intensity and coherence of motifs in weighted complex
|
| 393 |
+
networks by J. P. Onnela, J. Saramäki, J. Kertész, and K. Kaski,
|
| 394 |
+
Physical Review E, 71(6), 065103 (2005).
|
| 395 |
+
.. [3] Generalization of Clustering Coefficients to Signed Correlation Networks
|
| 396 |
+
by G. Costantini and M. Perugini, PloS one, 9(2), e88669 (2014).
|
| 397 |
+
.. [4] Clustering in complex directed networks by G. Fagiolo,
|
| 398 |
+
Physical Review E, 76(2), 026107 (2007).
|
| 399 |
+
"""
|
| 400 |
+
if G.is_directed():
|
| 401 |
+
if weight is not None:
|
| 402 |
+
td_iter = _directed_weighted_triangles_and_degree_iter(G, nodes, weight)
|
| 403 |
+
clusterc = {
|
| 404 |
+
v: 0 if t == 0 else t / ((dt * (dt - 1) - 2 * db) * 2)
|
| 405 |
+
for v, dt, db, t in td_iter
|
| 406 |
+
}
|
| 407 |
+
else:
|
| 408 |
+
td_iter = _directed_triangles_and_degree_iter(G, nodes)
|
| 409 |
+
clusterc = {
|
| 410 |
+
v: 0 if t == 0 else t / ((dt * (dt - 1) - 2 * db) * 2)
|
| 411 |
+
for v, dt, db, t in td_iter
|
| 412 |
+
}
|
| 413 |
+
else:
|
| 414 |
+
# The formula 2*T/(d*(d-1)) from docs is t/(d*(d-1)) here b/c t==2*T
|
| 415 |
+
if weight is not None:
|
| 416 |
+
td_iter = _weighted_triangles_and_degree_iter(G, nodes, weight)
|
| 417 |
+
clusterc = {v: 0 if t == 0 else t / (d * (d - 1)) for v, d, t in td_iter}
|
| 418 |
+
else:
|
| 419 |
+
td_iter = _triangles_and_degree_iter(G, nodes)
|
| 420 |
+
clusterc = {v: 0 if t == 0 else t / (d * (d - 1)) for v, d, t, _ in td_iter}
|
| 421 |
+
if nodes in G:
|
| 422 |
+
# Return the value of the sole entry in the dictionary.
|
| 423 |
+
return clusterc[nodes]
|
| 424 |
+
return clusterc
|
| 425 |
+
|
| 426 |
+
|
| 427 |
+
@nx._dispatch
|
| 428 |
+
def transitivity(G):
|
| 429 |
+
r"""Compute graph transitivity, the fraction of all possible triangles
|
| 430 |
+
present in G.
|
| 431 |
+
|
| 432 |
+
Possible triangles are identified by the number of "triads"
|
| 433 |
+
(two edges with a shared vertex).
|
| 434 |
+
|
| 435 |
+
The transitivity is
|
| 436 |
+
|
| 437 |
+
.. math::
|
| 438 |
+
|
| 439 |
+
T = 3\frac{\#triangles}{\#triads}.
|
| 440 |
+
|
| 441 |
+
Parameters
|
| 442 |
+
----------
|
| 443 |
+
G : graph
|
| 444 |
+
|
| 445 |
+
Returns
|
| 446 |
+
-------
|
| 447 |
+
out : float
|
| 448 |
+
Transitivity
|
| 449 |
+
|
| 450 |
+
Examples
|
| 451 |
+
--------
|
| 452 |
+
>>> G = nx.complete_graph(5)
|
| 453 |
+
>>> print(nx.transitivity(G))
|
| 454 |
+
1.0
|
| 455 |
+
"""
|
| 456 |
+
triangles_contri = [
|
| 457 |
+
(t, d * (d - 1)) for v, d, t, _ in _triangles_and_degree_iter(G)
|
| 458 |
+
]
|
| 459 |
+
# If the graph is empty
|
| 460 |
+
if len(triangles_contri) == 0:
|
| 461 |
+
return 0
|
| 462 |
+
triangles, contri = map(sum, zip(*triangles_contri))
|
| 463 |
+
return 0 if triangles == 0 else triangles / contri
|
| 464 |
+
|
| 465 |
+
|
| 466 |
+
@nx._dispatch
|
| 467 |
+
def square_clustering(G, nodes=None):
|
| 468 |
+
r"""Compute the squares clustering coefficient for nodes.
|
| 469 |
+
|
| 470 |
+
For each node return the fraction of possible squares that exist at
|
| 471 |
+
the node [1]_
|
| 472 |
+
|
| 473 |
+
.. math::
|
| 474 |
+
C_4(v) = \frac{ \sum_{u=1}^{k_v}
|
| 475 |
+
\sum_{w=u+1}^{k_v} q_v(u,w) }{ \sum_{u=1}^{k_v}
|
| 476 |
+
\sum_{w=u+1}^{k_v} [a_v(u,w) + q_v(u,w)]},
|
| 477 |
+
|
| 478 |
+
where :math:`q_v(u,w)` are the number of common neighbors of :math:`u` and
|
| 479 |
+
:math:`w` other than :math:`v` (ie squares), and :math:`a_v(u,w) = (k_u -
|
| 480 |
+
(1+q_v(u,w)+\theta_{uv})) + (k_w - (1+q_v(u,w)+\theta_{uw}))`, where
|
| 481 |
+
:math:`\theta_{uw} = 1` if :math:`u` and :math:`w` are connected and 0
|
| 482 |
+
otherwise. [2]_
|
| 483 |
+
|
| 484 |
+
Parameters
|
| 485 |
+
----------
|
| 486 |
+
G : graph
|
| 487 |
+
|
| 488 |
+
nodes : container of nodes, optional (default=all nodes in G)
|
| 489 |
+
Compute clustering for nodes in this container.
|
| 490 |
+
|
| 491 |
+
Returns
|
| 492 |
+
-------
|
| 493 |
+
c4 : dictionary
|
| 494 |
+
A dictionary keyed by node with the square clustering coefficient value.
|
| 495 |
+
|
| 496 |
+
Examples
|
| 497 |
+
--------
|
| 498 |
+
>>> G = nx.complete_graph(5)
|
| 499 |
+
>>> print(nx.square_clustering(G, 0))
|
| 500 |
+
1.0
|
| 501 |
+
>>> print(nx.square_clustering(G))
|
| 502 |
+
{0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0}
|
| 503 |
+
|
| 504 |
+
Notes
|
| 505 |
+
-----
|
| 506 |
+
While :math:`C_3(v)` (triangle clustering) gives the probability that
|
| 507 |
+
two neighbors of node v are connected with each other, :math:`C_4(v)` is
|
| 508 |
+
the probability that two neighbors of node v share a common
|
| 509 |
+
neighbor different from v. This algorithm can be applied to both
|
| 510 |
+
bipartite and unipartite networks.
|
| 511 |
+
|
| 512 |
+
References
|
| 513 |
+
----------
|
| 514 |
+
.. [1] Pedro G. Lind, Marta C. González, and Hans J. Herrmann. 2005
|
| 515 |
+
Cycles and clustering in bipartite networks.
|
| 516 |
+
Physical Review E (72) 056127.
|
| 517 |
+
.. [2] Zhang, Peng et al. Clustering Coefficient and Community Structure of
|
| 518 |
+
Bipartite Networks. Physica A: Statistical Mechanics and its Applications 387.27 (2008): 6869–6875.
|
| 519 |
+
https://arxiv.org/abs/0710.0117v1
|
| 520 |
+
"""
|
| 521 |
+
if nodes is None:
|
| 522 |
+
node_iter = G
|
| 523 |
+
else:
|
| 524 |
+
node_iter = G.nbunch_iter(nodes)
|
| 525 |
+
clustering = {}
|
| 526 |
+
for v in node_iter:
|
| 527 |
+
clustering[v] = 0
|
| 528 |
+
potential = 0
|
| 529 |
+
for u, w in combinations(G[v], 2):
|
| 530 |
+
squares = len((set(G[u]) & set(G[w])) - {v})
|
| 531 |
+
clustering[v] += squares
|
| 532 |
+
degm = squares + 1
|
| 533 |
+
if w in G[u]:
|
| 534 |
+
degm += 1
|
| 535 |
+
potential += (len(G[u]) - degm) + (len(G[w]) - degm) + squares
|
| 536 |
+
if potential > 0:
|
| 537 |
+
clustering[v] /= potential
|
| 538 |
+
if nodes in G:
|
| 539 |
+
# Return the value of the sole entry in the dictionary.
|
| 540 |
+
return clustering[nodes]
|
| 541 |
+
return clustering
|
| 542 |
+
|
| 543 |
+
|
| 544 |
+
@not_implemented_for("directed")
|
| 545 |
+
@nx._dispatch
|
| 546 |
+
def generalized_degree(G, nodes=None):
|
| 547 |
+
r"""Compute the generalized degree for nodes.
|
| 548 |
+
|
| 549 |
+
For each node, the generalized degree shows how many edges of given
|
| 550 |
+
triangle multiplicity the node is connected to. The triangle multiplicity
|
| 551 |
+
of an edge is the number of triangles an edge participates in. The
|
| 552 |
+
generalized degree of node :math:`i` can be written as a vector
|
| 553 |
+
:math:`\mathbf{k}_i=(k_i^{(0)}, \dotsc, k_i^{(N-2)})` where
|
| 554 |
+
:math:`k_i^{(j)}` is the number of edges attached to node :math:`i` that
|
| 555 |
+
participate in :math:`j` triangles.
|
| 556 |
+
|
| 557 |
+
Parameters
|
| 558 |
+
----------
|
| 559 |
+
G : graph
|
| 560 |
+
|
| 561 |
+
nodes : container of nodes, optional (default=all nodes in G)
|
| 562 |
+
Compute the generalized degree for nodes in this container.
|
| 563 |
+
|
| 564 |
+
Returns
|
| 565 |
+
-------
|
| 566 |
+
out : Counter, or dictionary of Counters
|
| 567 |
+
Generalized degree of specified nodes. The Counter is keyed by edge
|
| 568 |
+
triangle multiplicity.
|
| 569 |
+
|
| 570 |
+
Examples
|
| 571 |
+
--------
|
| 572 |
+
>>> G = nx.complete_graph(5)
|
| 573 |
+
>>> print(nx.generalized_degree(G, 0))
|
| 574 |
+
Counter({3: 4})
|
| 575 |
+
>>> print(nx.generalized_degree(G))
|
| 576 |
+
{0: Counter({3: 4}), 1: Counter({3: 4}), 2: Counter({3: 4}), 3: Counter({3: 4}), 4: Counter({3: 4})}
|
| 577 |
+
|
| 578 |
+
To recover the number of triangles attached to a node:
|
| 579 |
+
|
| 580 |
+
>>> k1 = nx.generalized_degree(G, 0)
|
| 581 |
+
>>> sum([k * v for k, v in k1.items()]) / 2 == nx.triangles(G, 0)
|
| 582 |
+
True
|
| 583 |
+
|
| 584 |
+
Notes
|
| 585 |
+
-----
|
| 586 |
+
In a network of N nodes, the highest triangle multiplicity an edge can have
|
| 587 |
+
is N-2.
|
| 588 |
+
|
| 589 |
+
The return value does not include a `zero` entry if no edges of a
|
| 590 |
+
particular triangle multiplicity are present.
|
| 591 |
+
|
| 592 |
+
The number of triangles node :math:`i` is attached to can be recovered from
|
| 593 |
+
the generalized degree :math:`\mathbf{k}_i=(k_i^{(0)}, \dotsc,
|
| 594 |
+
k_i^{(N-2)})` by :math:`(k_i^{(1)}+2k_i^{(2)}+\dotsc +(N-2)k_i^{(N-2)})/2`.
|
| 595 |
+
|
| 596 |
+
References
|
| 597 |
+
----------
|
| 598 |
+
.. [1] Networks with arbitrary edge multiplicities by V. Zlatić,
|
| 599 |
+
D. Garlaschelli and G. Caldarelli, EPL (Europhysics Letters),
|
| 600 |
+
Volume 97, Number 2 (2012).
|
| 601 |
+
https://iopscience.iop.org/article/10.1209/0295-5075/97/28005
|
| 602 |
+
"""
|
| 603 |
+
if nodes in G:
|
| 604 |
+
return next(_triangles_and_degree_iter(G, nodes))[3]
|
| 605 |
+
return {v: gd for v, d, t, gd in _triangles_and_degree_iter(G, nodes)}
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/community/centrality.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Functions for computing communities based on centrality notions."""
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
|
| 5 |
+
__all__ = ["girvan_newman"]
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@nx._dispatch(preserve_edge_attrs="most_valuable_edge")
|
| 9 |
+
def girvan_newman(G, most_valuable_edge=None):
|
| 10 |
+
"""Finds communities in a graph using the Girvan–Newman method.
|
| 11 |
+
|
| 12 |
+
Parameters
|
| 13 |
+
----------
|
| 14 |
+
G : NetworkX graph
|
| 15 |
+
|
| 16 |
+
most_valuable_edge : function
|
| 17 |
+
Function that takes a graph as input and outputs an edge. The
|
| 18 |
+
edge returned by this function will be recomputed and removed at
|
| 19 |
+
each iteration of the algorithm.
|
| 20 |
+
|
| 21 |
+
If not specified, the edge with the highest
|
| 22 |
+
:func:`networkx.edge_betweenness_centrality` will be used.
|
| 23 |
+
|
| 24 |
+
Returns
|
| 25 |
+
-------
|
| 26 |
+
iterator
|
| 27 |
+
Iterator over tuples of sets of nodes in `G`. Each set of node
|
| 28 |
+
is a community, each tuple is a sequence of communities at a
|
| 29 |
+
particular level of the algorithm.
|
| 30 |
+
|
| 31 |
+
Examples
|
| 32 |
+
--------
|
| 33 |
+
To get the first pair of communities::
|
| 34 |
+
|
| 35 |
+
>>> G = nx.path_graph(10)
|
| 36 |
+
>>> comp = nx.community.girvan_newman(G)
|
| 37 |
+
>>> tuple(sorted(c) for c in next(comp))
|
| 38 |
+
([0, 1, 2, 3, 4], [5, 6, 7, 8, 9])
|
| 39 |
+
|
| 40 |
+
To get only the first *k* tuples of communities, use
|
| 41 |
+
:func:`itertools.islice`::
|
| 42 |
+
|
| 43 |
+
>>> import itertools
|
| 44 |
+
>>> G = nx.path_graph(8)
|
| 45 |
+
>>> k = 2
|
| 46 |
+
>>> comp = nx.community.girvan_newman(G)
|
| 47 |
+
>>> for communities in itertools.islice(comp, k):
|
| 48 |
+
... print(tuple(sorted(c) for c in communities))
|
| 49 |
+
...
|
| 50 |
+
([0, 1, 2, 3], [4, 5, 6, 7])
|
| 51 |
+
([0, 1], [2, 3], [4, 5, 6, 7])
|
| 52 |
+
|
| 53 |
+
To stop getting tuples of communities once the number of communities
|
| 54 |
+
is greater than *k*, use :func:`itertools.takewhile`::
|
| 55 |
+
|
| 56 |
+
>>> import itertools
|
| 57 |
+
>>> G = nx.path_graph(8)
|
| 58 |
+
>>> k = 4
|
| 59 |
+
>>> comp = nx.community.girvan_newman(G)
|
| 60 |
+
>>> limited = itertools.takewhile(lambda c: len(c) <= k, comp)
|
| 61 |
+
>>> for communities in limited:
|
| 62 |
+
... print(tuple(sorted(c) for c in communities))
|
| 63 |
+
...
|
| 64 |
+
([0, 1, 2, 3], [4, 5, 6, 7])
|
| 65 |
+
([0, 1], [2, 3], [4, 5, 6, 7])
|
| 66 |
+
([0, 1], [2, 3], [4, 5], [6, 7])
|
| 67 |
+
|
| 68 |
+
To just choose an edge to remove based on the weight::
|
| 69 |
+
|
| 70 |
+
>>> from operator import itemgetter
|
| 71 |
+
>>> G = nx.path_graph(10)
|
| 72 |
+
>>> edges = G.edges()
|
| 73 |
+
>>> nx.set_edge_attributes(G, {(u, v): v for u, v in edges}, "weight")
|
| 74 |
+
>>> def heaviest(G):
|
| 75 |
+
... u, v, w = max(G.edges(data="weight"), key=itemgetter(2))
|
| 76 |
+
... return (u, v)
|
| 77 |
+
...
|
| 78 |
+
>>> comp = nx.community.girvan_newman(G, most_valuable_edge=heaviest)
|
| 79 |
+
>>> tuple(sorted(c) for c in next(comp))
|
| 80 |
+
([0, 1, 2, 3, 4, 5, 6, 7, 8], [9])
|
| 81 |
+
|
| 82 |
+
To utilize edge weights when choosing an edge with, for example, the
|
| 83 |
+
highest betweenness centrality::
|
| 84 |
+
|
| 85 |
+
>>> from networkx import edge_betweenness_centrality as betweenness
|
| 86 |
+
>>> def most_central_edge(G):
|
| 87 |
+
... centrality = betweenness(G, weight="weight")
|
| 88 |
+
... return max(centrality, key=centrality.get)
|
| 89 |
+
...
|
| 90 |
+
>>> G = nx.path_graph(10)
|
| 91 |
+
>>> comp = nx.community.girvan_newman(G, most_valuable_edge=most_central_edge)
|
| 92 |
+
>>> tuple(sorted(c) for c in next(comp))
|
| 93 |
+
([0, 1, 2, 3, 4], [5, 6, 7, 8, 9])
|
| 94 |
+
|
| 95 |
+
To specify a different ranking algorithm for edges, use the
|
| 96 |
+
`most_valuable_edge` keyword argument::
|
| 97 |
+
|
| 98 |
+
>>> from networkx import edge_betweenness_centrality
|
| 99 |
+
>>> from random import random
|
| 100 |
+
>>> def most_central_edge(G):
|
| 101 |
+
... centrality = edge_betweenness_centrality(G)
|
| 102 |
+
... max_cent = max(centrality.values())
|
| 103 |
+
... # Scale the centrality values so they are between 0 and 1,
|
| 104 |
+
... # and add some random noise.
|
| 105 |
+
... centrality = {e: c / max_cent for e, c in centrality.items()}
|
| 106 |
+
... # Add some random noise.
|
| 107 |
+
... centrality = {e: c + random() for e, c in centrality.items()}
|
| 108 |
+
... return max(centrality, key=centrality.get)
|
| 109 |
+
...
|
| 110 |
+
>>> G = nx.path_graph(10)
|
| 111 |
+
>>> comp = nx.community.girvan_newman(G, most_valuable_edge=most_central_edge)
|
| 112 |
+
|
| 113 |
+
Notes
|
| 114 |
+
-----
|
| 115 |
+
The Girvan–Newman algorithm detects communities by progressively
|
| 116 |
+
removing edges from the original graph. The algorithm removes the
|
| 117 |
+
"most valuable" edge, traditionally the edge with the highest
|
| 118 |
+
betweenness centrality, at each step. As the graph breaks down into
|
| 119 |
+
pieces, the tightly knit community structure is exposed and the
|
| 120 |
+
result can be depicted as a dendrogram.
|
| 121 |
+
|
| 122 |
+
"""
|
| 123 |
+
# If the graph is already empty, simply return its connected
|
| 124 |
+
# components.
|
| 125 |
+
if G.number_of_edges() == 0:
|
| 126 |
+
yield tuple(nx.connected_components(G))
|
| 127 |
+
return
|
| 128 |
+
# If no function is provided for computing the most valuable edge,
|
| 129 |
+
# use the edge betweenness centrality.
|
| 130 |
+
if most_valuable_edge is None:
|
| 131 |
+
|
| 132 |
+
def most_valuable_edge(G):
|
| 133 |
+
"""Returns the edge with the highest betweenness centrality
|
| 134 |
+
in the graph `G`.
|
| 135 |
+
|
| 136 |
+
"""
|
| 137 |
+
# We have guaranteed that the graph is non-empty, so this
|
| 138 |
+
# dictionary will never be empty.
|
| 139 |
+
betweenness = nx.edge_betweenness_centrality(G)
|
| 140 |
+
return max(betweenness, key=betweenness.get)
|
| 141 |
+
|
| 142 |
+
# The copy of G here must include the edge weight data.
|
| 143 |
+
g = G.copy().to_undirected()
|
| 144 |
+
# Self-loops must be removed because their removal has no effect on
|
| 145 |
+
# the connected components of the graph.
|
| 146 |
+
g.remove_edges_from(nx.selfloop_edges(g))
|
| 147 |
+
while g.number_of_edges() > 0:
|
| 148 |
+
yield _without_most_central_edges(g, most_valuable_edge)
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def _without_most_central_edges(G, most_valuable_edge):
|
| 152 |
+
"""Returns the connected components of the graph that results from
|
| 153 |
+
repeatedly removing the most "valuable" edge in the graph.
|
| 154 |
+
|
| 155 |
+
`G` must be a non-empty graph. This function modifies the graph `G`
|
| 156 |
+
in-place; that is, it removes edges on the graph `G`.
|
| 157 |
+
|
| 158 |
+
`most_valuable_edge` is a function that takes the graph `G` as input
|
| 159 |
+
(or a subgraph with one or more edges of `G` removed) and returns an
|
| 160 |
+
edge. That edge will be removed and this process will be repeated
|
| 161 |
+
until the number of connected components in the graph increases.
|
| 162 |
+
|
| 163 |
+
"""
|
| 164 |
+
original_num_components = nx.number_connected_components(G)
|
| 165 |
+
num_new_components = original_num_components
|
| 166 |
+
while num_new_components <= original_num_components:
|
| 167 |
+
edge = most_valuable_edge(G)
|
| 168 |
+
G.remove_edge(*edge)
|
| 169 |
+
new_components = tuple(nx.connected_components(G))
|
| 170 |
+
num_new_components = len(new_components)
|
| 171 |
+
return new_components
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/community/louvain.py
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Function for detecting communities based on Louvain Community Detection
|
| 2 |
+
Algorithm"""
|
| 3 |
+
|
| 4 |
+
from collections import defaultdict, deque
|
| 5 |
+
|
| 6 |
+
import networkx as nx
|
| 7 |
+
from networkx.algorithms.community import modularity
|
| 8 |
+
from networkx.utils import py_random_state
|
| 9 |
+
|
| 10 |
+
__all__ = ["louvain_communities", "louvain_partitions"]
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@py_random_state("seed")
|
| 14 |
+
@nx._dispatch(edge_attrs="weight")
|
| 15 |
+
def louvain_communities(
|
| 16 |
+
G, weight="weight", resolution=1, threshold=0.0000001, seed=None
|
| 17 |
+
):
|
| 18 |
+
r"""Find the best partition of a graph using the Louvain Community Detection
|
| 19 |
+
Algorithm.
|
| 20 |
+
|
| 21 |
+
Louvain Community Detection Algorithm is a simple method to extract the community
|
| 22 |
+
structure of a network. This is a heuristic method based on modularity optimization. [1]_
|
| 23 |
+
|
| 24 |
+
The algorithm works in 2 steps. On the first step it assigns every node to be
|
| 25 |
+
in its own community and then for each node it tries to find the maximum positive
|
| 26 |
+
modularity gain by moving each node to all of its neighbor communities. If no positive
|
| 27 |
+
gain is achieved the node remains in its original community.
|
| 28 |
+
|
| 29 |
+
The modularity gain obtained by moving an isolated node $i$ into a community $C$ can
|
| 30 |
+
easily be calculated by the following formula (combining [1]_ [2]_ and some algebra):
|
| 31 |
+
|
| 32 |
+
.. math::
|
| 33 |
+
\Delta Q = \frac{k_{i,in}}{2m} - \gamma\frac{ \Sigma_{tot} \cdot k_i}{2m^2}
|
| 34 |
+
|
| 35 |
+
where $m$ is the size of the graph, $k_{i,in}$ is the sum of the weights of the links
|
| 36 |
+
from $i$ to nodes in $C$, $k_i$ is the sum of the weights of the links incident to node $i$,
|
| 37 |
+
$\Sigma_{tot}$ is the sum of the weights of the links incident to nodes in $C$ and $\gamma$
|
| 38 |
+
is the resolution parameter.
|
| 39 |
+
|
| 40 |
+
For the directed case the modularity gain can be computed using this formula according to [3]_
|
| 41 |
+
|
| 42 |
+
.. math::
|
| 43 |
+
\Delta Q = \frac{k_{i,in}}{m}
|
| 44 |
+
- \gamma\frac{k_i^{out} \cdot\Sigma_{tot}^{in} + k_i^{in} \cdot \Sigma_{tot}^{out}}{m^2}
|
| 45 |
+
|
| 46 |
+
where $k_i^{out}$, $k_i^{in}$ are the outer and inner weighted degrees of node $i$ and
|
| 47 |
+
$\Sigma_{tot}^{in}$, $\Sigma_{tot}^{out}$ are the sum of in-going and out-going links incident
|
| 48 |
+
to nodes in $C$.
|
| 49 |
+
|
| 50 |
+
The first phase continues until no individual move can improve the modularity.
|
| 51 |
+
|
| 52 |
+
The second phase consists in building a new network whose nodes are now the communities
|
| 53 |
+
found in the first phase. To do so, the weights of the links between the new nodes are given by
|
| 54 |
+
the sum of the weight of the links between nodes in the corresponding two communities. Once this
|
| 55 |
+
phase is complete it is possible to reapply the first phase creating bigger communities with
|
| 56 |
+
increased modularity.
|
| 57 |
+
|
| 58 |
+
The above two phases are executed until no modularity gain is achieved (or is less than
|
| 59 |
+
the `threshold`).
|
| 60 |
+
|
| 61 |
+
Be careful with self-loops in the input graph. These are treated as
|
| 62 |
+
previously reduced communities -- as if the process had been started
|
| 63 |
+
in the middle of the algorithm. Large self-loop edge weights thus
|
| 64 |
+
represent strong communities and in practice may be hard to add
|
| 65 |
+
other nodes to. If your input graph edge weights for self-loops
|
| 66 |
+
do not represent already reduced communities you may want to remove
|
| 67 |
+
the self-loops before inputting that graph.
|
| 68 |
+
|
| 69 |
+
Parameters
|
| 70 |
+
----------
|
| 71 |
+
G : NetworkX graph
|
| 72 |
+
weight : string or None, optional (default="weight")
|
| 73 |
+
The name of an edge attribute that holds the numerical value
|
| 74 |
+
used as a weight. If None then each edge has weight 1.
|
| 75 |
+
resolution : float, optional (default=1)
|
| 76 |
+
If resolution is less than 1, the algorithm favors larger communities.
|
| 77 |
+
Greater than 1 favors smaller communities
|
| 78 |
+
threshold : float, optional (default=0.0000001)
|
| 79 |
+
Modularity gain threshold for each level. If the gain of modularity
|
| 80 |
+
between 2 levels of the algorithm is less than the given threshold
|
| 81 |
+
then the algorithm stops and returns the resulting communities.
|
| 82 |
+
seed : integer, random_state, or None (default)
|
| 83 |
+
Indicator of random number generation state.
|
| 84 |
+
See :ref:`Randomness<randomness>`.
|
| 85 |
+
|
| 86 |
+
Returns
|
| 87 |
+
-------
|
| 88 |
+
list
|
| 89 |
+
A list of sets (partition of `G`). Each set represents one community and contains
|
| 90 |
+
all the nodes that constitute it.
|
| 91 |
+
|
| 92 |
+
Examples
|
| 93 |
+
--------
|
| 94 |
+
>>> import networkx as nx
|
| 95 |
+
>>> G = nx.petersen_graph()
|
| 96 |
+
>>> nx.community.louvain_communities(G, seed=123)
|
| 97 |
+
[{0, 4, 5, 7, 9}, {1, 2, 3, 6, 8}]
|
| 98 |
+
|
| 99 |
+
Notes
|
| 100 |
+
-----
|
| 101 |
+
The order in which the nodes are considered can affect the final output. In the algorithm
|
| 102 |
+
the ordering happens using a random shuffle.
|
| 103 |
+
|
| 104 |
+
References
|
| 105 |
+
----------
|
| 106 |
+
.. [1] Blondel, V.D. et al. Fast unfolding of communities in
|
| 107 |
+
large networks. J. Stat. Mech 10008, 1-12(2008). https://doi.org/10.1088/1742-5468/2008/10/P10008
|
| 108 |
+
.. [2] Traag, V.A., Waltman, L. & van Eck, N.J. From Louvain to Leiden: guaranteeing
|
| 109 |
+
well-connected communities. Sci Rep 9, 5233 (2019). https://doi.org/10.1038/s41598-019-41695-z
|
| 110 |
+
.. [3] Nicolas Dugué, Anthony Perez. Directed Louvain : maximizing modularity in directed networks.
|
| 111 |
+
[Research Report] Université d’Orléans. 2015. hal-01231784. https://hal.archives-ouvertes.fr/hal-01231784
|
| 112 |
+
|
| 113 |
+
See Also
|
| 114 |
+
--------
|
| 115 |
+
louvain_partitions
|
| 116 |
+
"""
|
| 117 |
+
|
| 118 |
+
d = louvain_partitions(G, weight, resolution, threshold, seed)
|
| 119 |
+
q = deque(d, maxlen=1)
|
| 120 |
+
return q.pop()
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
@py_random_state("seed")
|
| 124 |
+
@nx._dispatch(edge_attrs="weight")
|
| 125 |
+
def louvain_partitions(
|
| 126 |
+
G, weight="weight", resolution=1, threshold=0.0000001, seed=None
|
| 127 |
+
):
|
| 128 |
+
"""Yields partitions for each level of the Louvain Community Detection Algorithm
|
| 129 |
+
|
| 130 |
+
Louvain Community Detection Algorithm is a simple method to extract the community
|
| 131 |
+
structure of a network. This is a heuristic method based on modularity optimization. [1]_
|
| 132 |
+
|
| 133 |
+
The partitions at each level (step of the algorithm) form a dendrogram of communities.
|
| 134 |
+
A dendrogram is a diagram representing a tree and each level represents
|
| 135 |
+
a partition of the G graph. The top level contains the smallest communities
|
| 136 |
+
and as you traverse to the bottom of the tree the communities get bigger
|
| 137 |
+
and the overall modularity increases making the partition better.
|
| 138 |
+
|
| 139 |
+
Each level is generated by executing the two phases of the Louvain Community
|
| 140 |
+
Detection Algorithm.
|
| 141 |
+
|
| 142 |
+
Be careful with self-loops in the input graph. These are treated as
|
| 143 |
+
previously reduced communities -- as if the process had been started
|
| 144 |
+
in the middle of the algorithm. Large self-loop edge weights thus
|
| 145 |
+
represent strong communities and in practice may be hard to add
|
| 146 |
+
other nodes to. If your input graph edge weights for self-loops
|
| 147 |
+
do not represent already reduced communities you may want to remove
|
| 148 |
+
the self-loops before inputting that graph.
|
| 149 |
+
|
| 150 |
+
Parameters
|
| 151 |
+
----------
|
| 152 |
+
G : NetworkX graph
|
| 153 |
+
weight : string or None, optional (default="weight")
|
| 154 |
+
The name of an edge attribute that holds the numerical value
|
| 155 |
+
used as a weight. If None then each edge has weight 1.
|
| 156 |
+
resolution : float, optional (default=1)
|
| 157 |
+
If resolution is less than 1, the algorithm favors larger communities.
|
| 158 |
+
Greater than 1 favors smaller communities
|
| 159 |
+
threshold : float, optional (default=0.0000001)
|
| 160 |
+
Modularity gain threshold for each level. If the gain of modularity
|
| 161 |
+
between 2 levels of the algorithm is less than the given threshold
|
| 162 |
+
then the algorithm stops and returns the resulting communities.
|
| 163 |
+
seed : integer, random_state, or None (default)
|
| 164 |
+
Indicator of random number generation state.
|
| 165 |
+
See :ref:`Randomness<randomness>`.
|
| 166 |
+
|
| 167 |
+
Yields
|
| 168 |
+
------
|
| 169 |
+
list
|
| 170 |
+
A list of sets (partition of `G`). Each set represents one community and contains
|
| 171 |
+
all the nodes that constitute it.
|
| 172 |
+
|
| 173 |
+
References
|
| 174 |
+
----------
|
| 175 |
+
.. [1] Blondel, V.D. et al. Fast unfolding of communities in
|
| 176 |
+
large networks. J. Stat. Mech 10008, 1-12(2008)
|
| 177 |
+
|
| 178 |
+
See Also
|
| 179 |
+
--------
|
| 180 |
+
louvain_communities
|
| 181 |
+
"""
|
| 182 |
+
|
| 183 |
+
partition = [{u} for u in G.nodes()]
|
| 184 |
+
if nx.is_empty(G):
|
| 185 |
+
yield partition
|
| 186 |
+
return
|
| 187 |
+
mod = modularity(G, partition, resolution=resolution, weight=weight)
|
| 188 |
+
is_directed = G.is_directed()
|
| 189 |
+
if G.is_multigraph():
|
| 190 |
+
graph = _convert_multigraph(G, weight, is_directed)
|
| 191 |
+
else:
|
| 192 |
+
graph = G.__class__()
|
| 193 |
+
graph.add_nodes_from(G)
|
| 194 |
+
graph.add_weighted_edges_from(G.edges(data=weight, default=1))
|
| 195 |
+
|
| 196 |
+
m = graph.size(weight="weight")
|
| 197 |
+
partition, inner_partition, improvement = _one_level(
|
| 198 |
+
graph, m, partition, resolution, is_directed, seed
|
| 199 |
+
)
|
| 200 |
+
improvement = True
|
| 201 |
+
while improvement:
|
| 202 |
+
# gh-5901 protect the sets in the yielded list from further manipulation here
|
| 203 |
+
yield [s.copy() for s in partition]
|
| 204 |
+
new_mod = modularity(
|
| 205 |
+
graph, inner_partition, resolution=resolution, weight="weight"
|
| 206 |
+
)
|
| 207 |
+
if new_mod - mod <= threshold:
|
| 208 |
+
return
|
| 209 |
+
mod = new_mod
|
| 210 |
+
graph = _gen_graph(graph, inner_partition)
|
| 211 |
+
partition, inner_partition, improvement = _one_level(
|
| 212 |
+
graph, m, partition, resolution, is_directed, seed
|
| 213 |
+
)
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def _one_level(G, m, partition, resolution=1, is_directed=False, seed=None):
|
| 217 |
+
"""Calculate one level of the Louvain partitions tree
|
| 218 |
+
|
| 219 |
+
Parameters
|
| 220 |
+
----------
|
| 221 |
+
G : NetworkX Graph/DiGraph
|
| 222 |
+
The graph from which to detect communities
|
| 223 |
+
m : number
|
| 224 |
+
The size of the graph `G`.
|
| 225 |
+
partition : list of sets of nodes
|
| 226 |
+
A valid partition of the graph `G`
|
| 227 |
+
resolution : positive number
|
| 228 |
+
The resolution parameter for computing the modularity of a partition
|
| 229 |
+
is_directed : bool
|
| 230 |
+
True if `G` is a directed graph.
|
| 231 |
+
seed : integer, random_state, or None (default)
|
| 232 |
+
Indicator of random number generation state.
|
| 233 |
+
See :ref:`Randomness<randomness>`.
|
| 234 |
+
|
| 235 |
+
"""
|
| 236 |
+
node2com = {u: i for i, u in enumerate(G.nodes())}
|
| 237 |
+
inner_partition = [{u} for u in G.nodes()]
|
| 238 |
+
if is_directed:
|
| 239 |
+
in_degrees = dict(G.in_degree(weight="weight"))
|
| 240 |
+
out_degrees = dict(G.out_degree(weight="weight"))
|
| 241 |
+
Stot_in = list(in_degrees.values())
|
| 242 |
+
Stot_out = list(out_degrees.values())
|
| 243 |
+
# Calculate weights for both in and out neighbours without considering self-loops
|
| 244 |
+
nbrs = {}
|
| 245 |
+
for u in G:
|
| 246 |
+
nbrs[u] = defaultdict(float)
|
| 247 |
+
for _, n, wt in G.out_edges(u, data="weight"):
|
| 248 |
+
if u != n:
|
| 249 |
+
nbrs[u][n] += wt
|
| 250 |
+
for n, _, wt in G.in_edges(u, data="weight"):
|
| 251 |
+
if u != n:
|
| 252 |
+
nbrs[u][n] += wt
|
| 253 |
+
else:
|
| 254 |
+
degrees = dict(G.degree(weight="weight"))
|
| 255 |
+
Stot = list(degrees.values())
|
| 256 |
+
nbrs = {u: {v: data["weight"] for v, data in G[u].items() if v != u} for u in G}
|
| 257 |
+
rand_nodes = list(G.nodes)
|
| 258 |
+
seed.shuffle(rand_nodes)
|
| 259 |
+
nb_moves = 1
|
| 260 |
+
improvement = False
|
| 261 |
+
while nb_moves > 0:
|
| 262 |
+
nb_moves = 0
|
| 263 |
+
for u in rand_nodes:
|
| 264 |
+
best_mod = 0
|
| 265 |
+
best_com = node2com[u]
|
| 266 |
+
weights2com = _neighbor_weights(nbrs[u], node2com)
|
| 267 |
+
if is_directed:
|
| 268 |
+
in_degree = in_degrees[u]
|
| 269 |
+
out_degree = out_degrees[u]
|
| 270 |
+
Stot_in[best_com] -= in_degree
|
| 271 |
+
Stot_out[best_com] -= out_degree
|
| 272 |
+
remove_cost = (
|
| 273 |
+
-weights2com[best_com] / m
|
| 274 |
+
+ resolution
|
| 275 |
+
* (out_degree * Stot_in[best_com] + in_degree * Stot_out[best_com])
|
| 276 |
+
/ m**2
|
| 277 |
+
)
|
| 278 |
+
else:
|
| 279 |
+
degree = degrees[u]
|
| 280 |
+
Stot[best_com] -= degree
|
| 281 |
+
remove_cost = -weights2com[best_com] / m + resolution * (
|
| 282 |
+
Stot[best_com] * degree
|
| 283 |
+
) / (2 * m**2)
|
| 284 |
+
for nbr_com, wt in weights2com.items():
|
| 285 |
+
if is_directed:
|
| 286 |
+
gain = (
|
| 287 |
+
remove_cost
|
| 288 |
+
+ wt / m
|
| 289 |
+
- resolution
|
| 290 |
+
* (
|
| 291 |
+
out_degree * Stot_in[nbr_com]
|
| 292 |
+
+ in_degree * Stot_out[nbr_com]
|
| 293 |
+
)
|
| 294 |
+
/ m**2
|
| 295 |
+
)
|
| 296 |
+
else:
|
| 297 |
+
gain = (
|
| 298 |
+
remove_cost
|
| 299 |
+
+ wt / m
|
| 300 |
+
- resolution * (Stot[nbr_com] * degree) / (2 * m**2)
|
| 301 |
+
)
|
| 302 |
+
if gain > best_mod:
|
| 303 |
+
best_mod = gain
|
| 304 |
+
best_com = nbr_com
|
| 305 |
+
if is_directed:
|
| 306 |
+
Stot_in[best_com] += in_degree
|
| 307 |
+
Stot_out[best_com] += out_degree
|
| 308 |
+
else:
|
| 309 |
+
Stot[best_com] += degree
|
| 310 |
+
if best_com != node2com[u]:
|
| 311 |
+
com = G.nodes[u].get("nodes", {u})
|
| 312 |
+
partition[node2com[u]].difference_update(com)
|
| 313 |
+
inner_partition[node2com[u]].remove(u)
|
| 314 |
+
partition[best_com].update(com)
|
| 315 |
+
inner_partition[best_com].add(u)
|
| 316 |
+
improvement = True
|
| 317 |
+
nb_moves += 1
|
| 318 |
+
node2com[u] = best_com
|
| 319 |
+
partition = list(filter(len, partition))
|
| 320 |
+
inner_partition = list(filter(len, inner_partition))
|
| 321 |
+
return partition, inner_partition, improvement
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
def _neighbor_weights(nbrs, node2com):
|
| 325 |
+
"""Calculate weights between node and its neighbor communities.
|
| 326 |
+
|
| 327 |
+
Parameters
|
| 328 |
+
----------
|
| 329 |
+
nbrs : dictionary
|
| 330 |
+
Dictionary with nodes' neighbours as keys and their edge weight as value.
|
| 331 |
+
node2com : dictionary
|
| 332 |
+
Dictionary with all graph's nodes as keys and their community index as value.
|
| 333 |
+
|
| 334 |
+
"""
|
| 335 |
+
weights = defaultdict(float)
|
| 336 |
+
for nbr, wt in nbrs.items():
|
| 337 |
+
weights[node2com[nbr]] += wt
|
| 338 |
+
return weights
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
def _gen_graph(G, partition):
|
| 342 |
+
"""Generate a new graph based on the partitions of a given graph"""
|
| 343 |
+
H = G.__class__()
|
| 344 |
+
node2com = {}
|
| 345 |
+
for i, part in enumerate(partition):
|
| 346 |
+
nodes = set()
|
| 347 |
+
for node in part:
|
| 348 |
+
node2com[node] = i
|
| 349 |
+
nodes.update(G.nodes[node].get("nodes", {node}))
|
| 350 |
+
H.add_node(i, nodes=nodes)
|
| 351 |
+
|
| 352 |
+
for node1, node2, wt in G.edges(data=True):
|
| 353 |
+
wt = wt["weight"]
|
| 354 |
+
com1 = node2com[node1]
|
| 355 |
+
com2 = node2com[node2]
|
| 356 |
+
temp = H.get_edge_data(com1, com2, {"weight": 0})["weight"]
|
| 357 |
+
H.add_edge(com1, com2, weight=wt + temp)
|
| 358 |
+
return H
|
| 359 |
+
|
| 360 |
+
|
| 361 |
+
def _convert_multigraph(G, weight, is_directed):
|
| 362 |
+
"""Convert a Multigraph to normal Graph"""
|
| 363 |
+
if is_directed:
|
| 364 |
+
H = nx.DiGraph()
|
| 365 |
+
else:
|
| 366 |
+
H = nx.Graph()
|
| 367 |
+
H.add_nodes_from(G)
|
| 368 |
+
for u, v, wt in G.edges(data=weight, default=1):
|
| 369 |
+
if H.has_edge(u, v):
|
| 370 |
+
H[u][v]["weight"] += wt
|
| 371 |
+
else:
|
| 372 |
+
H.add_edge(u, v, weight=wt)
|
| 373 |
+
return H
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__init__.py
ADDED
|
File without changes
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (236 Bytes). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/dag.py
ADDED
|
@@ -0,0 +1,1258 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Algorithms for directed acyclic graphs (DAGs).
|
| 2 |
+
|
| 3 |
+
Note that most of these functions are only guaranteed to work for DAGs.
|
| 4 |
+
In general, these functions do not check for acyclic-ness, so it is up
|
| 5 |
+
to the user to check for that.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import heapq
|
| 9 |
+
from collections import deque
|
| 10 |
+
from functools import partial
|
| 11 |
+
from itertools import chain, combinations, product, starmap
|
| 12 |
+
from math import gcd
|
| 13 |
+
|
| 14 |
+
import networkx as nx
|
| 15 |
+
from networkx.utils import arbitrary_element, not_implemented_for, pairwise
|
| 16 |
+
|
| 17 |
+
__all__ = [
|
| 18 |
+
"descendants",
|
| 19 |
+
"ancestors",
|
| 20 |
+
"topological_sort",
|
| 21 |
+
"lexicographical_topological_sort",
|
| 22 |
+
"all_topological_sorts",
|
| 23 |
+
"topological_generations",
|
| 24 |
+
"is_directed_acyclic_graph",
|
| 25 |
+
"is_aperiodic",
|
| 26 |
+
"transitive_closure",
|
| 27 |
+
"transitive_closure_dag",
|
| 28 |
+
"transitive_reduction",
|
| 29 |
+
"antichains",
|
| 30 |
+
"dag_longest_path",
|
| 31 |
+
"dag_longest_path_length",
|
| 32 |
+
"dag_to_branching",
|
| 33 |
+
"compute_v_structures",
|
| 34 |
+
]
|
| 35 |
+
|
| 36 |
+
chaini = chain.from_iterable
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
@nx._dispatch
|
| 40 |
+
def descendants(G, source):
|
| 41 |
+
"""Returns all nodes reachable from `source` in `G`.
|
| 42 |
+
|
| 43 |
+
Parameters
|
| 44 |
+
----------
|
| 45 |
+
G : NetworkX Graph
|
| 46 |
+
source : node in `G`
|
| 47 |
+
|
| 48 |
+
Returns
|
| 49 |
+
-------
|
| 50 |
+
set()
|
| 51 |
+
The descendants of `source` in `G`
|
| 52 |
+
|
| 53 |
+
Raises
|
| 54 |
+
------
|
| 55 |
+
NetworkXError
|
| 56 |
+
If node `source` is not in `G`.
|
| 57 |
+
|
| 58 |
+
Examples
|
| 59 |
+
--------
|
| 60 |
+
>>> DG = nx.path_graph(5, create_using=nx.DiGraph)
|
| 61 |
+
>>> sorted(nx.descendants(DG, 2))
|
| 62 |
+
[3, 4]
|
| 63 |
+
|
| 64 |
+
The `source` node is not a descendant of itself, but can be included manually:
|
| 65 |
+
|
| 66 |
+
>>> sorted(nx.descendants(DG, 2) | {2})
|
| 67 |
+
[2, 3, 4]
|
| 68 |
+
|
| 69 |
+
See also
|
| 70 |
+
--------
|
| 71 |
+
ancestors
|
| 72 |
+
"""
|
| 73 |
+
return {child for parent, child in nx.bfs_edges(G, source)}
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
@nx._dispatch
|
| 77 |
+
def ancestors(G, source):
|
| 78 |
+
"""Returns all nodes having a path to `source` in `G`.
|
| 79 |
+
|
| 80 |
+
Parameters
|
| 81 |
+
----------
|
| 82 |
+
G : NetworkX Graph
|
| 83 |
+
source : node in `G`
|
| 84 |
+
|
| 85 |
+
Returns
|
| 86 |
+
-------
|
| 87 |
+
set()
|
| 88 |
+
The ancestors of `source` in `G`
|
| 89 |
+
|
| 90 |
+
Raises
|
| 91 |
+
------
|
| 92 |
+
NetworkXError
|
| 93 |
+
If node `source` is not in `G`.
|
| 94 |
+
|
| 95 |
+
Examples
|
| 96 |
+
--------
|
| 97 |
+
>>> DG = nx.path_graph(5, create_using=nx.DiGraph)
|
| 98 |
+
>>> sorted(nx.ancestors(DG, 2))
|
| 99 |
+
[0, 1]
|
| 100 |
+
|
| 101 |
+
The `source` node is not an ancestor of itself, but can be included manually:
|
| 102 |
+
|
| 103 |
+
>>> sorted(nx.ancestors(DG, 2) | {2})
|
| 104 |
+
[0, 1, 2]
|
| 105 |
+
|
| 106 |
+
See also
|
| 107 |
+
--------
|
| 108 |
+
descendants
|
| 109 |
+
"""
|
| 110 |
+
return {child for parent, child in nx.bfs_edges(G, source, reverse=True)}
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
@nx._dispatch
|
| 114 |
+
def has_cycle(G):
|
| 115 |
+
"""Decides whether the directed graph has a cycle."""
|
| 116 |
+
try:
|
| 117 |
+
# Feed the entire iterator into a zero-length deque.
|
| 118 |
+
deque(topological_sort(G), maxlen=0)
|
| 119 |
+
except nx.NetworkXUnfeasible:
|
| 120 |
+
return True
|
| 121 |
+
else:
|
| 122 |
+
return False
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
@nx._dispatch
|
| 126 |
+
def is_directed_acyclic_graph(G):
|
| 127 |
+
"""Returns True if the graph `G` is a directed acyclic graph (DAG) or
|
| 128 |
+
False if not.
|
| 129 |
+
|
| 130 |
+
Parameters
|
| 131 |
+
----------
|
| 132 |
+
G : NetworkX graph
|
| 133 |
+
|
| 134 |
+
Returns
|
| 135 |
+
-------
|
| 136 |
+
bool
|
| 137 |
+
True if `G` is a DAG, False otherwise
|
| 138 |
+
|
| 139 |
+
Examples
|
| 140 |
+
--------
|
| 141 |
+
Undirected graph::
|
| 142 |
+
|
| 143 |
+
>>> G = nx.Graph([(1, 2), (2, 3)])
|
| 144 |
+
>>> nx.is_directed_acyclic_graph(G)
|
| 145 |
+
False
|
| 146 |
+
|
| 147 |
+
Directed graph with cycle::
|
| 148 |
+
|
| 149 |
+
>>> G = nx.DiGraph([(1, 2), (2, 3), (3, 1)])
|
| 150 |
+
>>> nx.is_directed_acyclic_graph(G)
|
| 151 |
+
False
|
| 152 |
+
|
| 153 |
+
Directed acyclic graph::
|
| 154 |
+
|
| 155 |
+
>>> G = nx.DiGraph([(1, 2), (2, 3)])
|
| 156 |
+
>>> nx.is_directed_acyclic_graph(G)
|
| 157 |
+
True
|
| 158 |
+
|
| 159 |
+
See also
|
| 160 |
+
--------
|
| 161 |
+
topological_sort
|
| 162 |
+
"""
|
| 163 |
+
return G.is_directed() and not has_cycle(G)
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
@nx._dispatch
|
| 167 |
+
def topological_generations(G):
|
| 168 |
+
"""Stratifies a DAG into generations.
|
| 169 |
+
|
| 170 |
+
A topological generation is node collection in which ancestors of a node in each
|
| 171 |
+
generation are guaranteed to be in a previous generation, and any descendants of
|
| 172 |
+
a node are guaranteed to be in a following generation. Nodes are guaranteed to
|
| 173 |
+
be in the earliest possible generation that they can belong to.
|
| 174 |
+
|
| 175 |
+
Parameters
|
| 176 |
+
----------
|
| 177 |
+
G : NetworkX digraph
|
| 178 |
+
A directed acyclic graph (DAG)
|
| 179 |
+
|
| 180 |
+
Yields
|
| 181 |
+
------
|
| 182 |
+
sets of nodes
|
| 183 |
+
Yields sets of nodes representing each generation.
|
| 184 |
+
|
| 185 |
+
Raises
|
| 186 |
+
------
|
| 187 |
+
NetworkXError
|
| 188 |
+
Generations are defined for directed graphs only. If the graph
|
| 189 |
+
`G` is undirected, a :exc:`NetworkXError` is raised.
|
| 190 |
+
|
| 191 |
+
NetworkXUnfeasible
|
| 192 |
+
If `G` is not a directed acyclic graph (DAG) no topological generations
|
| 193 |
+
exist and a :exc:`NetworkXUnfeasible` exception is raised. This can also
|
| 194 |
+
be raised if `G` is changed while the returned iterator is being processed
|
| 195 |
+
|
| 196 |
+
RuntimeError
|
| 197 |
+
If `G` is changed while the returned iterator is being processed.
|
| 198 |
+
|
| 199 |
+
Examples
|
| 200 |
+
--------
|
| 201 |
+
>>> DG = nx.DiGraph([(2, 1), (3, 1)])
|
| 202 |
+
>>> [sorted(generation) for generation in nx.topological_generations(DG)]
|
| 203 |
+
[[2, 3], [1]]
|
| 204 |
+
|
| 205 |
+
Notes
|
| 206 |
+
-----
|
| 207 |
+
The generation in which a node resides can also be determined by taking the
|
| 208 |
+
max-path-distance from the node to the farthest leaf node. That value can
|
| 209 |
+
be obtained with this function using `enumerate(topological_generations(G))`.
|
| 210 |
+
|
| 211 |
+
See also
|
| 212 |
+
--------
|
| 213 |
+
topological_sort
|
| 214 |
+
"""
|
| 215 |
+
if not G.is_directed():
|
| 216 |
+
raise nx.NetworkXError("Topological sort not defined on undirected graphs.")
|
| 217 |
+
|
| 218 |
+
multigraph = G.is_multigraph()
|
| 219 |
+
indegree_map = {v: d for v, d in G.in_degree() if d > 0}
|
| 220 |
+
zero_indegree = [v for v, d in G.in_degree() if d == 0]
|
| 221 |
+
|
| 222 |
+
while zero_indegree:
|
| 223 |
+
this_generation = zero_indegree
|
| 224 |
+
zero_indegree = []
|
| 225 |
+
for node in this_generation:
|
| 226 |
+
if node not in G:
|
| 227 |
+
raise RuntimeError("Graph changed during iteration")
|
| 228 |
+
for child in G.neighbors(node):
|
| 229 |
+
try:
|
| 230 |
+
indegree_map[child] -= len(G[node][child]) if multigraph else 1
|
| 231 |
+
except KeyError as err:
|
| 232 |
+
raise RuntimeError("Graph changed during iteration") from err
|
| 233 |
+
if indegree_map[child] == 0:
|
| 234 |
+
zero_indegree.append(child)
|
| 235 |
+
del indegree_map[child]
|
| 236 |
+
yield this_generation
|
| 237 |
+
|
| 238 |
+
if indegree_map:
|
| 239 |
+
raise nx.NetworkXUnfeasible(
|
| 240 |
+
"Graph contains a cycle or graph changed during iteration"
|
| 241 |
+
)
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
@nx._dispatch
|
| 245 |
+
def topological_sort(G):
|
| 246 |
+
"""Returns a generator of nodes in topologically sorted order.
|
| 247 |
+
|
| 248 |
+
A topological sort is a nonunique permutation of the nodes of a
|
| 249 |
+
directed graph such that an edge from u to v implies that u
|
| 250 |
+
appears before v in the topological sort order. This ordering is
|
| 251 |
+
valid only if the graph has no directed cycles.
|
| 252 |
+
|
| 253 |
+
Parameters
|
| 254 |
+
----------
|
| 255 |
+
G : NetworkX digraph
|
| 256 |
+
A directed acyclic graph (DAG)
|
| 257 |
+
|
| 258 |
+
Yields
|
| 259 |
+
------
|
| 260 |
+
nodes
|
| 261 |
+
Yields the nodes in topological sorted order.
|
| 262 |
+
|
| 263 |
+
Raises
|
| 264 |
+
------
|
| 265 |
+
NetworkXError
|
| 266 |
+
Topological sort is defined for directed graphs only. If the graph `G`
|
| 267 |
+
is undirected, a :exc:`NetworkXError` is raised.
|
| 268 |
+
|
| 269 |
+
NetworkXUnfeasible
|
| 270 |
+
If `G` is not a directed acyclic graph (DAG) no topological sort exists
|
| 271 |
+
and a :exc:`NetworkXUnfeasible` exception is raised. This can also be
|
| 272 |
+
raised if `G` is changed while the returned iterator is being processed
|
| 273 |
+
|
| 274 |
+
RuntimeError
|
| 275 |
+
If `G` is changed while the returned iterator is being processed.
|
| 276 |
+
|
| 277 |
+
Examples
|
| 278 |
+
--------
|
| 279 |
+
To get the reverse order of the topological sort:
|
| 280 |
+
|
| 281 |
+
>>> DG = nx.DiGraph([(1, 2), (2, 3)])
|
| 282 |
+
>>> list(reversed(list(nx.topological_sort(DG))))
|
| 283 |
+
[3, 2, 1]
|
| 284 |
+
|
| 285 |
+
If your DiGraph naturally has the edges representing tasks/inputs
|
| 286 |
+
and nodes representing people/processes that initiate tasks, then
|
| 287 |
+
topological_sort is not quite what you need. You will have to change
|
| 288 |
+
the tasks to nodes with dependence reflected by edges. The result is
|
| 289 |
+
a kind of topological sort of the edges. This can be done
|
| 290 |
+
with :func:`networkx.line_graph` as follows:
|
| 291 |
+
|
| 292 |
+
>>> list(nx.topological_sort(nx.line_graph(DG)))
|
| 293 |
+
[(1, 2), (2, 3)]
|
| 294 |
+
|
| 295 |
+
Notes
|
| 296 |
+
-----
|
| 297 |
+
This algorithm is based on a description and proof in
|
| 298 |
+
"Introduction to Algorithms: A Creative Approach" [1]_ .
|
| 299 |
+
|
| 300 |
+
See also
|
| 301 |
+
--------
|
| 302 |
+
is_directed_acyclic_graph, lexicographical_topological_sort
|
| 303 |
+
|
| 304 |
+
References
|
| 305 |
+
----------
|
| 306 |
+
.. [1] Manber, U. (1989).
|
| 307 |
+
*Introduction to Algorithms - A Creative Approach.* Addison-Wesley.
|
| 308 |
+
"""
|
| 309 |
+
for generation in nx.topological_generations(G):
|
| 310 |
+
yield from generation
|
| 311 |
+
|
| 312 |
+
|
| 313 |
+
@nx._dispatch
|
| 314 |
+
def lexicographical_topological_sort(G, key=None):
|
| 315 |
+
"""Generate the nodes in the unique lexicographical topological sort order.
|
| 316 |
+
|
| 317 |
+
Generates a unique ordering of nodes by first sorting topologically (for which there are often
|
| 318 |
+
multiple valid orderings) and then additionally by sorting lexicographically.
|
| 319 |
+
|
| 320 |
+
A topological sort arranges the nodes of a directed graph so that the
|
| 321 |
+
upstream node of each directed edge precedes the downstream node.
|
| 322 |
+
It is always possible to find a solution for directed graphs that have no cycles.
|
| 323 |
+
There may be more than one valid solution.
|
| 324 |
+
|
| 325 |
+
Lexicographical sorting is just sorting alphabetically. It is used here to break ties in the
|
| 326 |
+
topological sort and to determine a single, unique ordering. This can be useful in comparing
|
| 327 |
+
sort results.
|
| 328 |
+
|
| 329 |
+
The lexicographical order can be customized by providing a function to the `key=` parameter.
|
| 330 |
+
The definition of the key function is the same as used in python's built-in `sort()`.
|
| 331 |
+
The function takes a single argument and returns a key to use for sorting purposes.
|
| 332 |
+
|
| 333 |
+
Lexicographical sorting can fail if the node names are un-sortable. See the example below.
|
| 334 |
+
The solution is to provide a function to the `key=` argument that returns sortable keys.
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
Parameters
|
| 338 |
+
----------
|
| 339 |
+
G : NetworkX digraph
|
| 340 |
+
A directed acyclic graph (DAG)
|
| 341 |
+
|
| 342 |
+
key : function, optional
|
| 343 |
+
A function of one argument that converts a node name to a comparison key.
|
| 344 |
+
It defines and resolves ambiguities in the sort order. Defaults to the identity function.
|
| 345 |
+
|
| 346 |
+
Yields
|
| 347 |
+
------
|
| 348 |
+
nodes
|
| 349 |
+
Yields the nodes of G in lexicographical topological sort order.
|
| 350 |
+
|
| 351 |
+
Raises
|
| 352 |
+
------
|
| 353 |
+
NetworkXError
|
| 354 |
+
Topological sort is defined for directed graphs only. If the graph `G`
|
| 355 |
+
is undirected, a :exc:`NetworkXError` is raised.
|
| 356 |
+
|
| 357 |
+
NetworkXUnfeasible
|
| 358 |
+
If `G` is not a directed acyclic graph (DAG) no topological sort exists
|
| 359 |
+
and a :exc:`NetworkXUnfeasible` exception is raised. This can also be
|
| 360 |
+
raised if `G` is changed while the returned iterator is being processed
|
| 361 |
+
|
| 362 |
+
RuntimeError
|
| 363 |
+
If `G` is changed while the returned iterator is being processed.
|
| 364 |
+
|
| 365 |
+
TypeError
|
| 366 |
+
Results from un-sortable node names.
|
| 367 |
+
Consider using `key=` parameter to resolve ambiguities in the sort order.
|
| 368 |
+
|
| 369 |
+
Examples
|
| 370 |
+
--------
|
| 371 |
+
>>> DG = nx.DiGraph([(2, 1), (2, 5), (1, 3), (1, 4), (5, 4)])
|
| 372 |
+
>>> list(nx.lexicographical_topological_sort(DG))
|
| 373 |
+
[2, 1, 3, 5, 4]
|
| 374 |
+
>>> list(nx.lexicographical_topological_sort(DG, key=lambda x: -x))
|
| 375 |
+
[2, 5, 1, 4, 3]
|
| 376 |
+
|
| 377 |
+
The sort will fail for any graph with integer and string nodes. Comparison of integer to strings
|
| 378 |
+
is not defined in python. Is 3 greater or less than 'red'?
|
| 379 |
+
|
| 380 |
+
>>> DG = nx.DiGraph([(1, 'red'), (3, 'red'), (1, 'green'), (2, 'blue')])
|
| 381 |
+
>>> list(nx.lexicographical_topological_sort(DG))
|
| 382 |
+
Traceback (most recent call last):
|
| 383 |
+
...
|
| 384 |
+
TypeError: '<' not supported between instances of 'str' and 'int'
|
| 385 |
+
...
|
| 386 |
+
|
| 387 |
+
Incomparable nodes can be resolved using a `key` function. This example function
|
| 388 |
+
allows comparison of integers and strings by returning a tuple where the first
|
| 389 |
+
element is True for `str`, False otherwise. The second element is the node name.
|
| 390 |
+
This groups the strings and integers separately so they can be compared only among themselves.
|
| 391 |
+
|
| 392 |
+
>>> key = lambda node: (isinstance(node, str), node)
|
| 393 |
+
>>> list(nx.lexicographical_topological_sort(DG, key=key))
|
| 394 |
+
[1, 2, 3, 'blue', 'green', 'red']
|
| 395 |
+
|
| 396 |
+
Notes
|
| 397 |
+
-----
|
| 398 |
+
This algorithm is based on a description and proof in
|
| 399 |
+
"Introduction to Algorithms: A Creative Approach" [1]_ .
|
| 400 |
+
|
| 401 |
+
See also
|
| 402 |
+
--------
|
| 403 |
+
topological_sort
|
| 404 |
+
|
| 405 |
+
References
|
| 406 |
+
----------
|
| 407 |
+
.. [1] Manber, U. (1989).
|
| 408 |
+
*Introduction to Algorithms - A Creative Approach.* Addison-Wesley.
|
| 409 |
+
"""
|
| 410 |
+
if not G.is_directed():
|
| 411 |
+
msg = "Topological sort not defined on undirected graphs."
|
| 412 |
+
raise nx.NetworkXError(msg)
|
| 413 |
+
|
| 414 |
+
if key is None:
|
| 415 |
+
|
| 416 |
+
def key(node):
|
| 417 |
+
return node
|
| 418 |
+
|
| 419 |
+
nodeid_map = {n: i for i, n in enumerate(G)}
|
| 420 |
+
|
| 421 |
+
def create_tuple(node):
|
| 422 |
+
return key(node), nodeid_map[node], node
|
| 423 |
+
|
| 424 |
+
indegree_map = {v: d for v, d in G.in_degree() if d > 0}
|
| 425 |
+
# These nodes have zero indegree and ready to be returned.
|
| 426 |
+
zero_indegree = [create_tuple(v) for v, d in G.in_degree() if d == 0]
|
| 427 |
+
heapq.heapify(zero_indegree)
|
| 428 |
+
|
| 429 |
+
while zero_indegree:
|
| 430 |
+
_, _, node = heapq.heappop(zero_indegree)
|
| 431 |
+
|
| 432 |
+
if node not in G:
|
| 433 |
+
raise RuntimeError("Graph changed during iteration")
|
| 434 |
+
for _, child in G.edges(node):
|
| 435 |
+
try:
|
| 436 |
+
indegree_map[child] -= 1
|
| 437 |
+
except KeyError as err:
|
| 438 |
+
raise RuntimeError("Graph changed during iteration") from err
|
| 439 |
+
if indegree_map[child] == 0:
|
| 440 |
+
try:
|
| 441 |
+
heapq.heappush(zero_indegree, create_tuple(child))
|
| 442 |
+
except TypeError as err:
|
| 443 |
+
raise TypeError(
|
| 444 |
+
f"{err}\nConsider using `key=` parameter to resolve ambiguities in the sort order."
|
| 445 |
+
)
|
| 446 |
+
del indegree_map[child]
|
| 447 |
+
|
| 448 |
+
yield node
|
| 449 |
+
|
| 450 |
+
if indegree_map:
|
| 451 |
+
msg = "Graph contains a cycle or graph changed during iteration"
|
| 452 |
+
raise nx.NetworkXUnfeasible(msg)
|
| 453 |
+
|
| 454 |
+
|
| 455 |
+
@not_implemented_for("undirected")
|
| 456 |
+
@nx._dispatch
|
| 457 |
+
def all_topological_sorts(G):
|
| 458 |
+
"""Returns a generator of _all_ topological sorts of the directed graph G.
|
| 459 |
+
|
| 460 |
+
A topological sort is a nonunique permutation of the nodes such that an
|
| 461 |
+
edge from u to v implies that u appears before v in the topological sort
|
| 462 |
+
order.
|
| 463 |
+
|
| 464 |
+
Parameters
|
| 465 |
+
----------
|
| 466 |
+
G : NetworkX DiGraph
|
| 467 |
+
A directed graph
|
| 468 |
+
|
| 469 |
+
Yields
|
| 470 |
+
------
|
| 471 |
+
topological_sort_order : list
|
| 472 |
+
a list of nodes in `G`, representing one of the topological sort orders
|
| 473 |
+
|
| 474 |
+
Raises
|
| 475 |
+
------
|
| 476 |
+
NetworkXNotImplemented
|
| 477 |
+
If `G` is not directed
|
| 478 |
+
NetworkXUnfeasible
|
| 479 |
+
If `G` is not acyclic
|
| 480 |
+
|
| 481 |
+
Examples
|
| 482 |
+
--------
|
| 483 |
+
To enumerate all topological sorts of directed graph:
|
| 484 |
+
|
| 485 |
+
>>> DG = nx.DiGraph([(1, 2), (2, 3), (2, 4)])
|
| 486 |
+
>>> list(nx.all_topological_sorts(DG))
|
| 487 |
+
[[1, 2, 4, 3], [1, 2, 3, 4]]
|
| 488 |
+
|
| 489 |
+
Notes
|
| 490 |
+
-----
|
| 491 |
+
Implements an iterative version of the algorithm given in [1].
|
| 492 |
+
|
| 493 |
+
References
|
| 494 |
+
----------
|
| 495 |
+
.. [1] Knuth, Donald E., Szwarcfiter, Jayme L. (1974).
|
| 496 |
+
"A Structured Program to Generate All Topological Sorting Arrangements"
|
| 497 |
+
Information Processing Letters, Volume 2, Issue 6, 1974, Pages 153-157,
|
| 498 |
+
ISSN 0020-0190,
|
| 499 |
+
https://doi.org/10.1016/0020-0190(74)90001-5.
|
| 500 |
+
Elsevier (North-Holland), Amsterdam
|
| 501 |
+
"""
|
| 502 |
+
if not G.is_directed():
|
| 503 |
+
raise nx.NetworkXError("Topological sort not defined on undirected graphs.")
|
| 504 |
+
|
| 505 |
+
# the names of count and D are chosen to match the global variables in [1]
|
| 506 |
+
# number of edges originating in a vertex v
|
| 507 |
+
count = dict(G.in_degree())
|
| 508 |
+
# vertices with indegree 0
|
| 509 |
+
D = deque([v for v, d in G.in_degree() if d == 0])
|
| 510 |
+
# stack of first value chosen at a position k in the topological sort
|
| 511 |
+
bases = []
|
| 512 |
+
current_sort = []
|
| 513 |
+
|
| 514 |
+
# do-while construct
|
| 515 |
+
while True:
|
| 516 |
+
assert all(count[v] == 0 for v in D)
|
| 517 |
+
|
| 518 |
+
if len(current_sort) == len(G):
|
| 519 |
+
yield list(current_sort)
|
| 520 |
+
|
| 521 |
+
# clean-up stack
|
| 522 |
+
while len(current_sort) > 0:
|
| 523 |
+
assert len(bases) == len(current_sort)
|
| 524 |
+
q = current_sort.pop()
|
| 525 |
+
|
| 526 |
+
# "restores" all edges (q, x)
|
| 527 |
+
# NOTE: it is important to iterate over edges instead
|
| 528 |
+
# of successors, so count is updated correctly in multigraphs
|
| 529 |
+
for _, j in G.out_edges(q):
|
| 530 |
+
count[j] += 1
|
| 531 |
+
assert count[j] >= 0
|
| 532 |
+
# remove entries from D
|
| 533 |
+
while len(D) > 0 and count[D[-1]] > 0:
|
| 534 |
+
D.pop()
|
| 535 |
+
|
| 536 |
+
# corresponds to a circular shift of the values in D
|
| 537 |
+
# if the first value chosen (the base) is in the first
|
| 538 |
+
# position of D again, we are done and need to consider the
|
| 539 |
+
# previous condition
|
| 540 |
+
D.appendleft(q)
|
| 541 |
+
if D[-1] == bases[-1]:
|
| 542 |
+
# all possible values have been chosen at current position
|
| 543 |
+
# remove corresponding marker
|
| 544 |
+
bases.pop()
|
| 545 |
+
else:
|
| 546 |
+
# there are still elements that have not been fixed
|
| 547 |
+
# at the current position in the topological sort
|
| 548 |
+
# stop removing elements, escape inner loop
|
| 549 |
+
break
|
| 550 |
+
|
| 551 |
+
else:
|
| 552 |
+
if len(D) == 0:
|
| 553 |
+
raise nx.NetworkXUnfeasible("Graph contains a cycle.")
|
| 554 |
+
|
| 555 |
+
# choose next node
|
| 556 |
+
q = D.pop()
|
| 557 |
+
# "erase" all edges (q, x)
|
| 558 |
+
# NOTE: it is important to iterate over edges instead
|
| 559 |
+
# of successors, so count is updated correctly in multigraphs
|
| 560 |
+
for _, j in G.out_edges(q):
|
| 561 |
+
count[j] -= 1
|
| 562 |
+
assert count[j] >= 0
|
| 563 |
+
if count[j] == 0:
|
| 564 |
+
D.append(j)
|
| 565 |
+
current_sort.append(q)
|
| 566 |
+
|
| 567 |
+
# base for current position might _not_ be fixed yet
|
| 568 |
+
if len(bases) < len(current_sort):
|
| 569 |
+
bases.append(q)
|
| 570 |
+
|
| 571 |
+
if len(bases) == 0:
|
| 572 |
+
break
|
| 573 |
+
|
| 574 |
+
|
| 575 |
+
@nx._dispatch
|
| 576 |
+
def is_aperiodic(G):
|
| 577 |
+
"""Returns True if `G` is aperiodic.
|
| 578 |
+
|
| 579 |
+
A directed graph is aperiodic if there is no integer k > 1 that
|
| 580 |
+
divides the length of every cycle in the graph.
|
| 581 |
+
|
| 582 |
+
Parameters
|
| 583 |
+
----------
|
| 584 |
+
G : NetworkX DiGraph
|
| 585 |
+
A directed graph
|
| 586 |
+
|
| 587 |
+
Returns
|
| 588 |
+
-------
|
| 589 |
+
bool
|
| 590 |
+
True if the graph is aperiodic False otherwise
|
| 591 |
+
|
| 592 |
+
Raises
|
| 593 |
+
------
|
| 594 |
+
NetworkXError
|
| 595 |
+
If `G` is not directed
|
| 596 |
+
|
| 597 |
+
Examples
|
| 598 |
+
--------
|
| 599 |
+
A graph consisting of one cycle, the length of which is 2. Therefore ``k = 2``
|
| 600 |
+
divides the length of every cycle in the graph and thus the graph
|
| 601 |
+
is *not aperiodic*::
|
| 602 |
+
|
| 603 |
+
>>> DG = nx.DiGraph([(1, 2), (2, 1)])
|
| 604 |
+
>>> nx.is_aperiodic(DG)
|
| 605 |
+
False
|
| 606 |
+
|
| 607 |
+
A graph consisting of two cycles: one of length 2 and the other of length 3.
|
| 608 |
+
The cycle lengths are coprime, so there is no single value of k where ``k > 1``
|
| 609 |
+
that divides each cycle length and therefore the graph is *aperiodic*::
|
| 610 |
+
|
| 611 |
+
>>> DG = nx.DiGraph([(1, 2), (2, 3), (3, 1), (1, 4), (4, 1)])
|
| 612 |
+
>>> nx.is_aperiodic(DG)
|
| 613 |
+
True
|
| 614 |
+
|
| 615 |
+
A graph consisting of two cycles: one of length 2 and the other of length 4.
|
| 616 |
+
The lengths of the cycles share a common factor ``k = 2``, and therefore
|
| 617 |
+
the graph is *not aperiodic*::
|
| 618 |
+
|
| 619 |
+
>>> DG = nx.DiGraph([(1, 2), (2, 1), (3, 4), (4, 5), (5, 6), (6, 3)])
|
| 620 |
+
>>> nx.is_aperiodic(DG)
|
| 621 |
+
False
|
| 622 |
+
|
| 623 |
+
An acyclic graph, therefore the graph is *not aperiodic*::
|
| 624 |
+
|
| 625 |
+
>>> DG = nx.DiGraph([(1, 2), (2, 3)])
|
| 626 |
+
>>> nx.is_aperiodic(DG)
|
| 627 |
+
False
|
| 628 |
+
|
| 629 |
+
Notes
|
| 630 |
+
-----
|
| 631 |
+
This uses the method outlined in [1]_, which runs in $O(m)$ time
|
| 632 |
+
given $m$ edges in `G`. Note that a graph is not aperiodic if it is
|
| 633 |
+
acyclic as every integer trivial divides length 0 cycles.
|
| 634 |
+
|
| 635 |
+
References
|
| 636 |
+
----------
|
| 637 |
+
.. [1] Jarvis, J. P.; Shier, D. R. (1996),
|
| 638 |
+
"Graph-theoretic analysis of finite Markov chains,"
|
| 639 |
+
in Shier, D. R.; Wallenius, K. T., Applied Mathematical Modeling:
|
| 640 |
+
A Multidisciplinary Approach, CRC Press.
|
| 641 |
+
"""
|
| 642 |
+
if not G.is_directed():
|
| 643 |
+
raise nx.NetworkXError("is_aperiodic not defined for undirected graphs")
|
| 644 |
+
|
| 645 |
+
s = arbitrary_element(G)
|
| 646 |
+
levels = {s: 0}
|
| 647 |
+
this_level = [s]
|
| 648 |
+
g = 0
|
| 649 |
+
lev = 1
|
| 650 |
+
while this_level:
|
| 651 |
+
next_level = []
|
| 652 |
+
for u in this_level:
|
| 653 |
+
for v in G[u]:
|
| 654 |
+
if v in levels: # Non-Tree Edge
|
| 655 |
+
g = gcd(g, levels[u] - levels[v] + 1)
|
| 656 |
+
else: # Tree Edge
|
| 657 |
+
next_level.append(v)
|
| 658 |
+
levels[v] = lev
|
| 659 |
+
this_level = next_level
|
| 660 |
+
lev += 1
|
| 661 |
+
if len(levels) == len(G): # All nodes in tree
|
| 662 |
+
return g == 1
|
| 663 |
+
else:
|
| 664 |
+
return g == 1 and nx.is_aperiodic(G.subgraph(set(G) - set(levels)))
|
| 665 |
+
|
| 666 |
+
|
| 667 |
+
@nx._dispatch(preserve_all_attrs=True)
|
| 668 |
+
def transitive_closure(G, reflexive=False):
|
| 669 |
+
"""Returns transitive closure of a graph
|
| 670 |
+
|
| 671 |
+
The transitive closure of G = (V,E) is a graph G+ = (V,E+) such that
|
| 672 |
+
for all v, w in V there is an edge (v, w) in E+ if and only if there
|
| 673 |
+
is a path from v to w in G.
|
| 674 |
+
|
| 675 |
+
Handling of paths from v to v has some flexibility within this definition.
|
| 676 |
+
A reflexive transitive closure creates a self-loop for the path
|
| 677 |
+
from v to v of length 0. The usual transitive closure creates a
|
| 678 |
+
self-loop only if a cycle exists (a path from v to v with length > 0).
|
| 679 |
+
We also allow an option for no self-loops.
|
| 680 |
+
|
| 681 |
+
Parameters
|
| 682 |
+
----------
|
| 683 |
+
G : NetworkX Graph
|
| 684 |
+
A directed/undirected graph/multigraph.
|
| 685 |
+
reflexive : Bool or None, optional (default: False)
|
| 686 |
+
Determines when cycles create self-loops in the Transitive Closure.
|
| 687 |
+
If True, trivial cycles (length 0) create self-loops. The result
|
| 688 |
+
is a reflexive transitive closure of G.
|
| 689 |
+
If False (the default) non-trivial cycles create self-loops.
|
| 690 |
+
If None, self-loops are not created.
|
| 691 |
+
|
| 692 |
+
Returns
|
| 693 |
+
-------
|
| 694 |
+
NetworkX graph
|
| 695 |
+
The transitive closure of `G`
|
| 696 |
+
|
| 697 |
+
Raises
|
| 698 |
+
------
|
| 699 |
+
NetworkXError
|
| 700 |
+
If `reflexive` not in `{None, True, False}`
|
| 701 |
+
|
| 702 |
+
Examples
|
| 703 |
+
--------
|
| 704 |
+
The treatment of trivial (i.e. length 0) cycles is controlled by the
|
| 705 |
+
`reflexive` parameter.
|
| 706 |
+
|
| 707 |
+
Trivial (i.e. length 0) cycles do not create self-loops when
|
| 708 |
+
``reflexive=False`` (the default)::
|
| 709 |
+
|
| 710 |
+
>>> DG = nx.DiGraph([(1, 2), (2, 3)])
|
| 711 |
+
>>> TC = nx.transitive_closure(DG, reflexive=False)
|
| 712 |
+
>>> TC.edges()
|
| 713 |
+
OutEdgeView([(1, 2), (1, 3), (2, 3)])
|
| 714 |
+
|
| 715 |
+
However, nontrivial (i.e. length greater than 0) cycles create self-loops
|
| 716 |
+
when ``reflexive=False`` (the default)::
|
| 717 |
+
|
| 718 |
+
>>> DG = nx.DiGraph([(1, 2), (2, 3), (3, 1)])
|
| 719 |
+
>>> TC = nx.transitive_closure(DG, reflexive=False)
|
| 720 |
+
>>> TC.edges()
|
| 721 |
+
OutEdgeView([(1, 2), (1, 3), (1, 1), (2, 3), (2, 1), (2, 2), (3, 1), (3, 2), (3, 3)])
|
| 722 |
+
|
| 723 |
+
Trivial cycles (length 0) create self-loops when ``reflexive=True``::
|
| 724 |
+
|
| 725 |
+
>>> DG = nx.DiGraph([(1, 2), (2, 3)])
|
| 726 |
+
>>> TC = nx.transitive_closure(DG, reflexive=True)
|
| 727 |
+
>>> TC.edges()
|
| 728 |
+
OutEdgeView([(1, 2), (1, 1), (1, 3), (2, 3), (2, 2), (3, 3)])
|
| 729 |
+
|
| 730 |
+
And the third option is not to create self-loops at all when ``reflexive=None``::
|
| 731 |
+
|
| 732 |
+
>>> DG = nx.DiGraph([(1, 2), (2, 3), (3, 1)])
|
| 733 |
+
>>> TC = nx.transitive_closure(DG, reflexive=None)
|
| 734 |
+
>>> TC.edges()
|
| 735 |
+
OutEdgeView([(1, 2), (1, 3), (2, 3), (2, 1), (3, 1), (3, 2)])
|
| 736 |
+
|
| 737 |
+
References
|
| 738 |
+
----------
|
| 739 |
+
.. [1] https://www.ics.uci.edu/~eppstein/PADS/PartialOrder.py
|
| 740 |
+
"""
|
| 741 |
+
TC = G.copy()
|
| 742 |
+
|
| 743 |
+
if reflexive not in {None, True, False}:
|
| 744 |
+
raise nx.NetworkXError("Incorrect value for the parameter `reflexive`")
|
| 745 |
+
|
| 746 |
+
for v in G:
|
| 747 |
+
if reflexive is None:
|
| 748 |
+
TC.add_edges_from((v, u) for u in nx.descendants(G, v) if u not in TC[v])
|
| 749 |
+
elif reflexive is True:
|
| 750 |
+
TC.add_edges_from(
|
| 751 |
+
(v, u) for u in nx.descendants(G, v) | {v} if u not in TC[v]
|
| 752 |
+
)
|
| 753 |
+
elif reflexive is False:
|
| 754 |
+
TC.add_edges_from((v, e[1]) for e in nx.edge_bfs(G, v) if e[1] not in TC[v])
|
| 755 |
+
|
| 756 |
+
return TC
|
| 757 |
+
|
| 758 |
+
|
| 759 |
+
@not_implemented_for("undirected")
|
| 760 |
+
@nx._dispatch(preserve_all_attrs=True)
|
| 761 |
+
def transitive_closure_dag(G, topo_order=None):
|
| 762 |
+
"""Returns the transitive closure of a directed acyclic graph.
|
| 763 |
+
|
| 764 |
+
This function is faster than the function `transitive_closure`, but fails
|
| 765 |
+
if the graph has a cycle.
|
| 766 |
+
|
| 767 |
+
The transitive closure of G = (V,E) is a graph G+ = (V,E+) such that
|
| 768 |
+
for all v, w in V there is an edge (v, w) in E+ if and only if there
|
| 769 |
+
is a non-null path from v to w in G.
|
| 770 |
+
|
| 771 |
+
Parameters
|
| 772 |
+
----------
|
| 773 |
+
G : NetworkX DiGraph
|
| 774 |
+
A directed acyclic graph (DAG)
|
| 775 |
+
|
| 776 |
+
topo_order: list or tuple, optional
|
| 777 |
+
A topological order for G (if None, the function will compute one)
|
| 778 |
+
|
| 779 |
+
Returns
|
| 780 |
+
-------
|
| 781 |
+
NetworkX DiGraph
|
| 782 |
+
The transitive closure of `G`
|
| 783 |
+
|
| 784 |
+
Raises
|
| 785 |
+
------
|
| 786 |
+
NetworkXNotImplemented
|
| 787 |
+
If `G` is not directed
|
| 788 |
+
NetworkXUnfeasible
|
| 789 |
+
If `G` has a cycle
|
| 790 |
+
|
| 791 |
+
Examples
|
| 792 |
+
--------
|
| 793 |
+
>>> DG = nx.DiGraph([(1, 2), (2, 3)])
|
| 794 |
+
>>> TC = nx.transitive_closure_dag(DG)
|
| 795 |
+
>>> TC.edges()
|
| 796 |
+
OutEdgeView([(1, 2), (1, 3), (2, 3)])
|
| 797 |
+
|
| 798 |
+
Notes
|
| 799 |
+
-----
|
| 800 |
+
This algorithm is probably simple enough to be well-known but I didn't find
|
| 801 |
+
a mention in the literature.
|
| 802 |
+
"""
|
| 803 |
+
if topo_order is None:
|
| 804 |
+
topo_order = list(topological_sort(G))
|
| 805 |
+
|
| 806 |
+
TC = G.copy()
|
| 807 |
+
|
| 808 |
+
# idea: traverse vertices following a reverse topological order, connecting
|
| 809 |
+
# each vertex to its descendants at distance 2 as we go
|
| 810 |
+
for v in reversed(topo_order):
|
| 811 |
+
TC.add_edges_from((v, u) for u in nx.descendants_at_distance(TC, v, 2))
|
| 812 |
+
|
| 813 |
+
return TC
|
| 814 |
+
|
| 815 |
+
|
| 816 |
+
@not_implemented_for("undirected")
|
| 817 |
+
@nx._dispatch
|
| 818 |
+
def transitive_reduction(G):
|
| 819 |
+
"""Returns transitive reduction of a directed graph
|
| 820 |
+
|
| 821 |
+
The transitive reduction of G = (V,E) is a graph G- = (V,E-) such that
|
| 822 |
+
for all v,w in V there is an edge (v,w) in E- if and only if (v,w) is
|
| 823 |
+
in E and there is no path from v to w in G with length greater than 1.
|
| 824 |
+
|
| 825 |
+
Parameters
|
| 826 |
+
----------
|
| 827 |
+
G : NetworkX DiGraph
|
| 828 |
+
A directed acyclic graph (DAG)
|
| 829 |
+
|
| 830 |
+
Returns
|
| 831 |
+
-------
|
| 832 |
+
NetworkX DiGraph
|
| 833 |
+
The transitive reduction of `G`
|
| 834 |
+
|
| 835 |
+
Raises
|
| 836 |
+
------
|
| 837 |
+
NetworkXError
|
| 838 |
+
If `G` is not a directed acyclic graph (DAG) transitive reduction is
|
| 839 |
+
not uniquely defined and a :exc:`NetworkXError` exception is raised.
|
| 840 |
+
|
| 841 |
+
Examples
|
| 842 |
+
--------
|
| 843 |
+
To perform transitive reduction on a DiGraph:
|
| 844 |
+
|
| 845 |
+
>>> DG = nx.DiGraph([(1, 2), (2, 3), (1, 3)])
|
| 846 |
+
>>> TR = nx.transitive_reduction(DG)
|
| 847 |
+
>>> list(TR.edges)
|
| 848 |
+
[(1, 2), (2, 3)]
|
| 849 |
+
|
| 850 |
+
To avoid unnecessary data copies, this implementation does not return a
|
| 851 |
+
DiGraph with node/edge data.
|
| 852 |
+
To perform transitive reduction on a DiGraph and transfer node/edge data:
|
| 853 |
+
|
| 854 |
+
>>> DG = nx.DiGraph()
|
| 855 |
+
>>> DG.add_edges_from([(1, 2), (2, 3), (1, 3)], color='red')
|
| 856 |
+
>>> TR = nx.transitive_reduction(DG)
|
| 857 |
+
>>> TR.add_nodes_from(DG.nodes(data=True))
|
| 858 |
+
>>> TR.add_edges_from((u, v, DG.edges[u, v]) for u, v in TR.edges)
|
| 859 |
+
>>> list(TR.edges(data=True))
|
| 860 |
+
[(1, 2, {'color': 'red'}), (2, 3, {'color': 'red'})]
|
| 861 |
+
|
| 862 |
+
References
|
| 863 |
+
----------
|
| 864 |
+
https://en.wikipedia.org/wiki/Transitive_reduction
|
| 865 |
+
|
| 866 |
+
"""
|
| 867 |
+
if not is_directed_acyclic_graph(G):
|
| 868 |
+
msg = "Directed Acyclic Graph required for transitive_reduction"
|
| 869 |
+
raise nx.NetworkXError(msg)
|
| 870 |
+
TR = nx.DiGraph()
|
| 871 |
+
TR.add_nodes_from(G.nodes())
|
| 872 |
+
descendants = {}
|
| 873 |
+
# count before removing set stored in descendants
|
| 874 |
+
check_count = dict(G.in_degree)
|
| 875 |
+
for u in G:
|
| 876 |
+
u_nbrs = set(G[u])
|
| 877 |
+
for v in G[u]:
|
| 878 |
+
if v in u_nbrs:
|
| 879 |
+
if v not in descendants:
|
| 880 |
+
descendants[v] = {y for x, y in nx.dfs_edges(G, v)}
|
| 881 |
+
u_nbrs -= descendants[v]
|
| 882 |
+
check_count[v] -= 1
|
| 883 |
+
if check_count[v] == 0:
|
| 884 |
+
del descendants[v]
|
| 885 |
+
TR.add_edges_from((u, v) for v in u_nbrs)
|
| 886 |
+
return TR
|
| 887 |
+
|
| 888 |
+
|
| 889 |
+
@not_implemented_for("undirected")
|
| 890 |
+
@nx._dispatch
|
| 891 |
+
def antichains(G, topo_order=None):
|
| 892 |
+
"""Generates antichains from a directed acyclic graph (DAG).
|
| 893 |
+
|
| 894 |
+
An antichain is a subset of a partially ordered set such that any
|
| 895 |
+
two elements in the subset are incomparable.
|
| 896 |
+
|
| 897 |
+
Parameters
|
| 898 |
+
----------
|
| 899 |
+
G : NetworkX DiGraph
|
| 900 |
+
A directed acyclic graph (DAG)
|
| 901 |
+
|
| 902 |
+
topo_order: list or tuple, optional
|
| 903 |
+
A topological order for G (if None, the function will compute one)
|
| 904 |
+
|
| 905 |
+
Yields
|
| 906 |
+
------
|
| 907 |
+
antichain : list
|
| 908 |
+
a list of nodes in `G` representing an antichain
|
| 909 |
+
|
| 910 |
+
Raises
|
| 911 |
+
------
|
| 912 |
+
NetworkXNotImplemented
|
| 913 |
+
If `G` is not directed
|
| 914 |
+
|
| 915 |
+
NetworkXUnfeasible
|
| 916 |
+
If `G` contains a cycle
|
| 917 |
+
|
| 918 |
+
Examples
|
| 919 |
+
--------
|
| 920 |
+
>>> DG = nx.DiGraph([(1, 2), (1, 3)])
|
| 921 |
+
>>> list(nx.antichains(DG))
|
| 922 |
+
[[], [3], [2], [2, 3], [1]]
|
| 923 |
+
|
| 924 |
+
Notes
|
| 925 |
+
-----
|
| 926 |
+
This function was originally developed by Peter Jipsen and Franco Saliola
|
| 927 |
+
for the SAGE project. It's included in NetworkX with permission from the
|
| 928 |
+
authors. Original SAGE code at:
|
| 929 |
+
|
| 930 |
+
https://github.com/sagemath/sage/blob/master/src/sage/combinat/posets/hasse_diagram.py
|
| 931 |
+
|
| 932 |
+
References
|
| 933 |
+
----------
|
| 934 |
+
.. [1] Free Lattices, by R. Freese, J. Jezek and J. B. Nation,
|
| 935 |
+
AMS, Vol 42, 1995, p. 226.
|
| 936 |
+
"""
|
| 937 |
+
if topo_order is None:
|
| 938 |
+
topo_order = list(nx.topological_sort(G))
|
| 939 |
+
|
| 940 |
+
TC = nx.transitive_closure_dag(G, topo_order)
|
| 941 |
+
antichains_stacks = [([], list(reversed(topo_order)))]
|
| 942 |
+
|
| 943 |
+
while antichains_stacks:
|
| 944 |
+
(antichain, stack) = antichains_stacks.pop()
|
| 945 |
+
# Invariant:
|
| 946 |
+
# - the elements of antichain are independent
|
| 947 |
+
# - the elements of stack are independent from those of antichain
|
| 948 |
+
yield antichain
|
| 949 |
+
while stack:
|
| 950 |
+
x = stack.pop()
|
| 951 |
+
new_antichain = antichain + [x]
|
| 952 |
+
new_stack = [t for t in stack if not ((t in TC[x]) or (x in TC[t]))]
|
| 953 |
+
antichains_stacks.append((new_antichain, new_stack))
|
| 954 |
+
|
| 955 |
+
|
| 956 |
+
@not_implemented_for("undirected")
|
| 957 |
+
@nx._dispatch(edge_attrs={"weight": "default_weight"})
|
| 958 |
+
def dag_longest_path(G, weight="weight", default_weight=1, topo_order=None):
|
| 959 |
+
"""Returns the longest path in a directed acyclic graph (DAG).
|
| 960 |
+
|
| 961 |
+
If `G` has edges with `weight` attribute the edge data are used as
|
| 962 |
+
weight values.
|
| 963 |
+
|
| 964 |
+
Parameters
|
| 965 |
+
----------
|
| 966 |
+
G : NetworkX DiGraph
|
| 967 |
+
A directed acyclic graph (DAG)
|
| 968 |
+
|
| 969 |
+
weight : str, optional
|
| 970 |
+
Edge data key to use for weight
|
| 971 |
+
|
| 972 |
+
default_weight : int, optional
|
| 973 |
+
The weight of edges that do not have a weight attribute
|
| 974 |
+
|
| 975 |
+
topo_order: list or tuple, optional
|
| 976 |
+
A topological order for `G` (if None, the function will compute one)
|
| 977 |
+
|
| 978 |
+
Returns
|
| 979 |
+
-------
|
| 980 |
+
list
|
| 981 |
+
Longest path
|
| 982 |
+
|
| 983 |
+
Raises
|
| 984 |
+
------
|
| 985 |
+
NetworkXNotImplemented
|
| 986 |
+
If `G` is not directed
|
| 987 |
+
|
| 988 |
+
Examples
|
| 989 |
+
--------
|
| 990 |
+
>>> DG = nx.DiGraph([(0, 1, {'cost':1}), (1, 2, {'cost':1}), (0, 2, {'cost':42})])
|
| 991 |
+
>>> list(nx.all_simple_paths(DG, 0, 2))
|
| 992 |
+
[[0, 1, 2], [0, 2]]
|
| 993 |
+
>>> nx.dag_longest_path(DG)
|
| 994 |
+
[0, 1, 2]
|
| 995 |
+
>>> nx.dag_longest_path(DG, weight="cost")
|
| 996 |
+
[0, 2]
|
| 997 |
+
|
| 998 |
+
In the case where multiple valid topological orderings exist, `topo_order`
|
| 999 |
+
can be used to specify a specific ordering:
|
| 1000 |
+
|
| 1001 |
+
>>> DG = nx.DiGraph([(0, 1), (0, 2)])
|
| 1002 |
+
>>> sorted(nx.all_topological_sorts(DG)) # Valid topological orderings
|
| 1003 |
+
[[0, 1, 2], [0, 2, 1]]
|
| 1004 |
+
>>> nx.dag_longest_path(DG, topo_order=[0, 1, 2])
|
| 1005 |
+
[0, 1]
|
| 1006 |
+
>>> nx.dag_longest_path(DG, topo_order=[0, 2, 1])
|
| 1007 |
+
[0, 2]
|
| 1008 |
+
|
| 1009 |
+
See also
|
| 1010 |
+
--------
|
| 1011 |
+
dag_longest_path_length
|
| 1012 |
+
|
| 1013 |
+
"""
|
| 1014 |
+
if not G:
|
| 1015 |
+
return []
|
| 1016 |
+
|
| 1017 |
+
if topo_order is None:
|
| 1018 |
+
topo_order = nx.topological_sort(G)
|
| 1019 |
+
|
| 1020 |
+
dist = {} # stores {v : (length, u)}
|
| 1021 |
+
for v in topo_order:
|
| 1022 |
+
us = [
|
| 1023 |
+
(
|
| 1024 |
+
dist[u][0]
|
| 1025 |
+
+ (
|
| 1026 |
+
max(data.values(), key=lambda x: x.get(weight, default_weight))
|
| 1027 |
+
if G.is_multigraph()
|
| 1028 |
+
else data
|
| 1029 |
+
).get(weight, default_weight),
|
| 1030 |
+
u,
|
| 1031 |
+
)
|
| 1032 |
+
for u, data in G.pred[v].items()
|
| 1033 |
+
]
|
| 1034 |
+
|
| 1035 |
+
# Use the best predecessor if there is one and its distance is
|
| 1036 |
+
# non-negative, otherwise terminate.
|
| 1037 |
+
maxu = max(us, key=lambda x: x[0]) if us else (0, v)
|
| 1038 |
+
dist[v] = maxu if maxu[0] >= 0 else (0, v)
|
| 1039 |
+
|
| 1040 |
+
u = None
|
| 1041 |
+
v = max(dist, key=lambda x: dist[x][0])
|
| 1042 |
+
path = []
|
| 1043 |
+
while u != v:
|
| 1044 |
+
path.append(v)
|
| 1045 |
+
u = v
|
| 1046 |
+
v = dist[v][1]
|
| 1047 |
+
|
| 1048 |
+
path.reverse()
|
| 1049 |
+
return path
|
| 1050 |
+
|
| 1051 |
+
|
| 1052 |
+
@not_implemented_for("undirected")
|
| 1053 |
+
@nx._dispatch(edge_attrs={"weight": "default_weight"})
|
| 1054 |
+
def dag_longest_path_length(G, weight="weight", default_weight=1):
|
| 1055 |
+
"""Returns the longest path length in a DAG
|
| 1056 |
+
|
| 1057 |
+
Parameters
|
| 1058 |
+
----------
|
| 1059 |
+
G : NetworkX DiGraph
|
| 1060 |
+
A directed acyclic graph (DAG)
|
| 1061 |
+
|
| 1062 |
+
weight : string, optional
|
| 1063 |
+
Edge data key to use for weight
|
| 1064 |
+
|
| 1065 |
+
default_weight : int, optional
|
| 1066 |
+
The weight of edges that do not have a weight attribute
|
| 1067 |
+
|
| 1068 |
+
Returns
|
| 1069 |
+
-------
|
| 1070 |
+
int
|
| 1071 |
+
Longest path length
|
| 1072 |
+
|
| 1073 |
+
Raises
|
| 1074 |
+
------
|
| 1075 |
+
NetworkXNotImplemented
|
| 1076 |
+
If `G` is not directed
|
| 1077 |
+
|
| 1078 |
+
Examples
|
| 1079 |
+
--------
|
| 1080 |
+
>>> DG = nx.DiGraph([(0, 1, {'cost':1}), (1, 2, {'cost':1}), (0, 2, {'cost':42})])
|
| 1081 |
+
>>> list(nx.all_simple_paths(DG, 0, 2))
|
| 1082 |
+
[[0, 1, 2], [0, 2]]
|
| 1083 |
+
>>> nx.dag_longest_path_length(DG)
|
| 1084 |
+
2
|
| 1085 |
+
>>> nx.dag_longest_path_length(DG, weight="cost")
|
| 1086 |
+
42
|
| 1087 |
+
|
| 1088 |
+
See also
|
| 1089 |
+
--------
|
| 1090 |
+
dag_longest_path
|
| 1091 |
+
"""
|
| 1092 |
+
path = nx.dag_longest_path(G, weight, default_weight)
|
| 1093 |
+
path_length = 0
|
| 1094 |
+
if G.is_multigraph():
|
| 1095 |
+
for u, v in pairwise(path):
|
| 1096 |
+
i = max(G[u][v], key=lambda x: G[u][v][x].get(weight, default_weight))
|
| 1097 |
+
path_length += G[u][v][i].get(weight, default_weight)
|
| 1098 |
+
else:
|
| 1099 |
+
for u, v in pairwise(path):
|
| 1100 |
+
path_length += G[u][v].get(weight, default_weight)
|
| 1101 |
+
|
| 1102 |
+
return path_length
|
| 1103 |
+
|
| 1104 |
+
|
| 1105 |
+
@nx._dispatch
|
| 1106 |
+
def root_to_leaf_paths(G):
|
| 1107 |
+
"""Yields root-to-leaf paths in a directed acyclic graph.
|
| 1108 |
+
|
| 1109 |
+
`G` must be a directed acyclic graph. If not, the behavior of this
|
| 1110 |
+
function is undefined. A "root" in this graph is a node of in-degree
|
| 1111 |
+
zero and a "leaf" a node of out-degree zero.
|
| 1112 |
+
|
| 1113 |
+
When invoked, this function iterates over each path from any root to
|
| 1114 |
+
any leaf. A path is a list of nodes.
|
| 1115 |
+
|
| 1116 |
+
"""
|
| 1117 |
+
roots = (v for v, d in G.in_degree() if d == 0)
|
| 1118 |
+
leaves = (v for v, d in G.out_degree() if d == 0)
|
| 1119 |
+
all_paths = partial(nx.all_simple_paths, G)
|
| 1120 |
+
# TODO In Python 3, this would be better as `yield from ...`.
|
| 1121 |
+
return chaini(starmap(all_paths, product(roots, leaves)))
|
| 1122 |
+
|
| 1123 |
+
|
| 1124 |
+
@not_implemented_for("multigraph")
|
| 1125 |
+
@not_implemented_for("undirected")
|
| 1126 |
+
@nx._dispatch
|
| 1127 |
+
def dag_to_branching(G):
|
| 1128 |
+
"""Returns a branching representing all (overlapping) paths from
|
| 1129 |
+
root nodes to leaf nodes in the given directed acyclic graph.
|
| 1130 |
+
|
| 1131 |
+
As described in :mod:`networkx.algorithms.tree.recognition`, a
|
| 1132 |
+
*branching* is a directed forest in which each node has at most one
|
| 1133 |
+
parent. In other words, a branching is a disjoint union of
|
| 1134 |
+
*arborescences*. For this function, each node of in-degree zero in
|
| 1135 |
+
`G` becomes a root of one of the arborescences, and there will be
|
| 1136 |
+
one leaf node for each distinct path from that root to a leaf node
|
| 1137 |
+
in `G`.
|
| 1138 |
+
|
| 1139 |
+
Each node `v` in `G` with *k* parents becomes *k* distinct nodes in
|
| 1140 |
+
the returned branching, one for each parent, and the sub-DAG rooted
|
| 1141 |
+
at `v` is duplicated for each copy. The algorithm then recurses on
|
| 1142 |
+
the children of each copy of `v`.
|
| 1143 |
+
|
| 1144 |
+
Parameters
|
| 1145 |
+
----------
|
| 1146 |
+
G : NetworkX graph
|
| 1147 |
+
A directed acyclic graph.
|
| 1148 |
+
|
| 1149 |
+
Returns
|
| 1150 |
+
-------
|
| 1151 |
+
DiGraph
|
| 1152 |
+
The branching in which there is a bijection between root-to-leaf
|
| 1153 |
+
paths in `G` (in which multiple paths may share the same leaf)
|
| 1154 |
+
and root-to-leaf paths in the branching (in which there is a
|
| 1155 |
+
unique path from a root to a leaf).
|
| 1156 |
+
|
| 1157 |
+
Each node has an attribute 'source' whose value is the original
|
| 1158 |
+
node to which this node corresponds. No other graph, node, or
|
| 1159 |
+
edge attributes are copied into this new graph.
|
| 1160 |
+
|
| 1161 |
+
Raises
|
| 1162 |
+
------
|
| 1163 |
+
NetworkXNotImplemented
|
| 1164 |
+
If `G` is not directed, or if `G` is a multigraph.
|
| 1165 |
+
|
| 1166 |
+
HasACycle
|
| 1167 |
+
If `G` is not acyclic.
|
| 1168 |
+
|
| 1169 |
+
Examples
|
| 1170 |
+
--------
|
| 1171 |
+
To examine which nodes in the returned branching were produced by
|
| 1172 |
+
which original node in the directed acyclic graph, we can collect
|
| 1173 |
+
the mapping from source node to new nodes into a dictionary. For
|
| 1174 |
+
example, consider the directed diamond graph::
|
| 1175 |
+
|
| 1176 |
+
>>> from collections import defaultdict
|
| 1177 |
+
>>> from operator import itemgetter
|
| 1178 |
+
>>>
|
| 1179 |
+
>>> G = nx.DiGraph(nx.utils.pairwise("abd"))
|
| 1180 |
+
>>> G.add_edges_from(nx.utils.pairwise("acd"))
|
| 1181 |
+
>>> B = nx.dag_to_branching(G)
|
| 1182 |
+
>>>
|
| 1183 |
+
>>> sources = defaultdict(set)
|
| 1184 |
+
>>> for v, source in B.nodes(data="source"):
|
| 1185 |
+
... sources[source].add(v)
|
| 1186 |
+
>>> len(sources["a"])
|
| 1187 |
+
1
|
| 1188 |
+
>>> len(sources["d"])
|
| 1189 |
+
2
|
| 1190 |
+
|
| 1191 |
+
To copy node attributes from the original graph to the new graph,
|
| 1192 |
+
you can use a dictionary like the one constructed in the above
|
| 1193 |
+
example::
|
| 1194 |
+
|
| 1195 |
+
>>> for source, nodes in sources.items():
|
| 1196 |
+
... for v in nodes:
|
| 1197 |
+
... B.nodes[v].update(G.nodes[source])
|
| 1198 |
+
|
| 1199 |
+
Notes
|
| 1200 |
+
-----
|
| 1201 |
+
This function is not idempotent in the sense that the node labels in
|
| 1202 |
+
the returned branching may be uniquely generated each time the
|
| 1203 |
+
function is invoked. In fact, the node labels may not be integers;
|
| 1204 |
+
in order to relabel the nodes to be more readable, you can use the
|
| 1205 |
+
:func:`networkx.convert_node_labels_to_integers` function.
|
| 1206 |
+
|
| 1207 |
+
The current implementation of this function uses
|
| 1208 |
+
:func:`networkx.prefix_tree`, so it is subject to the limitations of
|
| 1209 |
+
that function.
|
| 1210 |
+
|
| 1211 |
+
"""
|
| 1212 |
+
if has_cycle(G):
|
| 1213 |
+
msg = "dag_to_branching is only defined for acyclic graphs"
|
| 1214 |
+
raise nx.HasACycle(msg)
|
| 1215 |
+
paths = root_to_leaf_paths(G)
|
| 1216 |
+
B = nx.prefix_tree(paths)
|
| 1217 |
+
# Remove the synthetic `root`(0) and `NIL`(-1) nodes from the tree
|
| 1218 |
+
B.remove_node(0)
|
| 1219 |
+
B.remove_node(-1)
|
| 1220 |
+
return B
|
| 1221 |
+
|
| 1222 |
+
|
| 1223 |
+
@not_implemented_for("undirected")
|
| 1224 |
+
@nx._dispatch
|
| 1225 |
+
def compute_v_structures(G):
|
| 1226 |
+
"""Iterate through the graph to compute all v-structures.
|
| 1227 |
+
|
| 1228 |
+
V-structures are triples in the directed graph where
|
| 1229 |
+
two parent nodes point to the same child and the two parent nodes
|
| 1230 |
+
are not adjacent.
|
| 1231 |
+
|
| 1232 |
+
Parameters
|
| 1233 |
+
----------
|
| 1234 |
+
G : graph
|
| 1235 |
+
A networkx DiGraph.
|
| 1236 |
+
|
| 1237 |
+
Returns
|
| 1238 |
+
-------
|
| 1239 |
+
vstructs : iterator of tuples
|
| 1240 |
+
The v structures within the graph. Each v structure is a 3-tuple with the
|
| 1241 |
+
parent, collider, and other parent.
|
| 1242 |
+
|
| 1243 |
+
Examples
|
| 1244 |
+
--------
|
| 1245 |
+
>>> G = nx.DiGraph()
|
| 1246 |
+
>>> G.add_edges_from([(1, 2), (0, 5), (3, 1), (2, 4), (3, 1), (4, 5), (1, 5)])
|
| 1247 |
+
>>> sorted(nx.compute_v_structures(G))
|
| 1248 |
+
[(0, 5, 1), (0, 5, 4), (1, 5, 4)]
|
| 1249 |
+
|
| 1250 |
+
Notes
|
| 1251 |
+
-----
|
| 1252 |
+
https://en.wikipedia.org/wiki/Collider_(statistics)
|
| 1253 |
+
"""
|
| 1254 |
+
for collider, preds in G.pred.items():
|
| 1255 |
+
for common_parents in combinations(preds, r=2):
|
| 1256 |
+
# ensure that the colliders are the same
|
| 1257 |
+
common_parents = sorted(common_parents)
|
| 1258 |
+
yield (common_parents[0], collider, common_parents[1])
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/distance_regular.py
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
=======================
|
| 3 |
+
Distance-regular graphs
|
| 4 |
+
=======================
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import networkx as nx
|
| 8 |
+
from networkx.utils import not_implemented_for
|
| 9 |
+
|
| 10 |
+
from .distance_measures import diameter
|
| 11 |
+
|
| 12 |
+
__all__ = [
|
| 13 |
+
"is_distance_regular",
|
| 14 |
+
"is_strongly_regular",
|
| 15 |
+
"intersection_array",
|
| 16 |
+
"global_parameters",
|
| 17 |
+
]
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@nx._dispatch
|
| 21 |
+
def is_distance_regular(G):
|
| 22 |
+
"""Returns True if the graph is distance regular, False otherwise.
|
| 23 |
+
|
| 24 |
+
A connected graph G is distance-regular if for any nodes x,y
|
| 25 |
+
and any integers i,j=0,1,...,d (where d is the graph
|
| 26 |
+
diameter), the number of vertices at distance i from x and
|
| 27 |
+
distance j from y depends only on i,j and the graph distance
|
| 28 |
+
between x and y, independently of the choice of x and y.
|
| 29 |
+
|
| 30 |
+
Parameters
|
| 31 |
+
----------
|
| 32 |
+
G: Networkx graph (undirected)
|
| 33 |
+
|
| 34 |
+
Returns
|
| 35 |
+
-------
|
| 36 |
+
bool
|
| 37 |
+
True if the graph is Distance Regular, False otherwise
|
| 38 |
+
|
| 39 |
+
Examples
|
| 40 |
+
--------
|
| 41 |
+
>>> G = nx.hypercube_graph(6)
|
| 42 |
+
>>> nx.is_distance_regular(G)
|
| 43 |
+
True
|
| 44 |
+
|
| 45 |
+
See Also
|
| 46 |
+
--------
|
| 47 |
+
intersection_array, global_parameters
|
| 48 |
+
|
| 49 |
+
Notes
|
| 50 |
+
-----
|
| 51 |
+
For undirected and simple graphs only
|
| 52 |
+
|
| 53 |
+
References
|
| 54 |
+
----------
|
| 55 |
+
.. [1] Brouwer, A. E.; Cohen, A. M.; and Neumaier, A.
|
| 56 |
+
Distance-Regular Graphs. New York: Springer-Verlag, 1989.
|
| 57 |
+
.. [2] Weisstein, Eric W. "Distance-Regular Graph."
|
| 58 |
+
http://mathworld.wolfram.com/Distance-RegularGraph.html
|
| 59 |
+
|
| 60 |
+
"""
|
| 61 |
+
try:
|
| 62 |
+
intersection_array(G)
|
| 63 |
+
return True
|
| 64 |
+
except nx.NetworkXError:
|
| 65 |
+
return False
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def global_parameters(b, c):
|
| 69 |
+
"""Returns global parameters for a given intersection array.
|
| 70 |
+
|
| 71 |
+
Given a distance-regular graph G with integers b_i, c_i,i = 0,....,d
|
| 72 |
+
such that for any 2 vertices x,y in G at a distance i=d(x,y), there
|
| 73 |
+
are exactly c_i neighbors of y at a distance of i-1 from x and b_i
|
| 74 |
+
neighbors of y at a distance of i+1 from x.
|
| 75 |
+
|
| 76 |
+
Thus, a distance regular graph has the global parameters,
|
| 77 |
+
[[c_0,a_0,b_0],[c_1,a_1,b_1],......,[c_d,a_d,b_d]] for the
|
| 78 |
+
intersection array [b_0,b_1,.....b_{d-1};c_1,c_2,.....c_d]
|
| 79 |
+
where a_i+b_i+c_i=k , k= degree of every vertex.
|
| 80 |
+
|
| 81 |
+
Parameters
|
| 82 |
+
----------
|
| 83 |
+
b : list
|
| 84 |
+
|
| 85 |
+
c : list
|
| 86 |
+
|
| 87 |
+
Returns
|
| 88 |
+
-------
|
| 89 |
+
iterable
|
| 90 |
+
An iterable over three tuples.
|
| 91 |
+
|
| 92 |
+
Examples
|
| 93 |
+
--------
|
| 94 |
+
>>> G = nx.dodecahedral_graph()
|
| 95 |
+
>>> b, c = nx.intersection_array(G)
|
| 96 |
+
>>> list(nx.global_parameters(b, c))
|
| 97 |
+
[(0, 0, 3), (1, 0, 2), (1, 1, 1), (1, 1, 1), (2, 0, 1), (3, 0, 0)]
|
| 98 |
+
|
| 99 |
+
References
|
| 100 |
+
----------
|
| 101 |
+
.. [1] Weisstein, Eric W. "Global Parameters."
|
| 102 |
+
From MathWorld--A Wolfram Web Resource.
|
| 103 |
+
http://mathworld.wolfram.com/GlobalParameters.html
|
| 104 |
+
|
| 105 |
+
See Also
|
| 106 |
+
--------
|
| 107 |
+
intersection_array
|
| 108 |
+
"""
|
| 109 |
+
return ((y, b[0] - x - y, x) for x, y in zip(b + [0], [0] + c))
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
@not_implemented_for("directed", "multigraph")
|
| 113 |
+
@nx._dispatch
|
| 114 |
+
def intersection_array(G):
|
| 115 |
+
"""Returns the intersection array of a distance-regular graph.
|
| 116 |
+
|
| 117 |
+
Given a distance-regular graph G with integers b_i, c_i,i = 0,....,d
|
| 118 |
+
such that for any 2 vertices x,y in G at a distance i=d(x,y), there
|
| 119 |
+
are exactly c_i neighbors of y at a distance of i-1 from x and b_i
|
| 120 |
+
neighbors of y at a distance of i+1 from x.
|
| 121 |
+
|
| 122 |
+
A distance regular graph's intersection array is given by,
|
| 123 |
+
[b_0,b_1,.....b_{d-1};c_1,c_2,.....c_d]
|
| 124 |
+
|
| 125 |
+
Parameters
|
| 126 |
+
----------
|
| 127 |
+
G: Networkx graph (undirected)
|
| 128 |
+
|
| 129 |
+
Returns
|
| 130 |
+
-------
|
| 131 |
+
b,c: tuple of lists
|
| 132 |
+
|
| 133 |
+
Examples
|
| 134 |
+
--------
|
| 135 |
+
>>> G = nx.icosahedral_graph()
|
| 136 |
+
>>> nx.intersection_array(G)
|
| 137 |
+
([5, 2, 1], [1, 2, 5])
|
| 138 |
+
|
| 139 |
+
References
|
| 140 |
+
----------
|
| 141 |
+
.. [1] Weisstein, Eric W. "Intersection Array."
|
| 142 |
+
From MathWorld--A Wolfram Web Resource.
|
| 143 |
+
http://mathworld.wolfram.com/IntersectionArray.html
|
| 144 |
+
|
| 145 |
+
See Also
|
| 146 |
+
--------
|
| 147 |
+
global_parameters
|
| 148 |
+
"""
|
| 149 |
+
# test for regular graph (all degrees must be equal)
|
| 150 |
+
degree = iter(G.degree())
|
| 151 |
+
(_, k) = next(degree)
|
| 152 |
+
for _, knext in degree:
|
| 153 |
+
if knext != k:
|
| 154 |
+
raise nx.NetworkXError("Graph is not distance regular.")
|
| 155 |
+
k = knext
|
| 156 |
+
path_length = dict(nx.all_pairs_shortest_path_length(G))
|
| 157 |
+
diameter = max(max(path_length[n].values()) for n in path_length)
|
| 158 |
+
bint = {} # 'b' intersection array
|
| 159 |
+
cint = {} # 'c' intersection array
|
| 160 |
+
for u in G:
|
| 161 |
+
for v in G:
|
| 162 |
+
try:
|
| 163 |
+
i = path_length[u][v]
|
| 164 |
+
except KeyError as err: # graph must be connected
|
| 165 |
+
raise nx.NetworkXError("Graph is not distance regular.") from err
|
| 166 |
+
# number of neighbors of v at a distance of i-1 from u
|
| 167 |
+
c = len([n for n in G[v] if path_length[n][u] == i - 1])
|
| 168 |
+
# number of neighbors of v at a distance of i+1 from u
|
| 169 |
+
b = len([n for n in G[v] if path_length[n][u] == i + 1])
|
| 170 |
+
# b,c are independent of u and v
|
| 171 |
+
if cint.get(i, c) != c or bint.get(i, b) != b:
|
| 172 |
+
raise nx.NetworkXError("Graph is not distance regular")
|
| 173 |
+
bint[i] = b
|
| 174 |
+
cint[i] = c
|
| 175 |
+
return (
|
| 176 |
+
[bint.get(j, 0) for j in range(diameter)],
|
| 177 |
+
[cint.get(j + 1, 0) for j in range(diameter)],
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
# TODO There is a definition for directed strongly regular graphs.
|
| 182 |
+
@not_implemented_for("directed", "multigraph")
|
| 183 |
+
@nx._dispatch
|
| 184 |
+
def is_strongly_regular(G):
|
| 185 |
+
"""Returns True if and only if the given graph is strongly
|
| 186 |
+
regular.
|
| 187 |
+
|
| 188 |
+
An undirected graph is *strongly regular* if
|
| 189 |
+
|
| 190 |
+
* it is regular,
|
| 191 |
+
* each pair of adjacent vertices has the same number of neighbors in
|
| 192 |
+
common,
|
| 193 |
+
* each pair of nonadjacent vertices has the same number of neighbors
|
| 194 |
+
in common.
|
| 195 |
+
|
| 196 |
+
Each strongly regular graph is a distance-regular graph.
|
| 197 |
+
Conversely, if a distance-regular graph has diameter two, then it is
|
| 198 |
+
a strongly regular graph. For more information on distance-regular
|
| 199 |
+
graphs, see :func:`is_distance_regular`.
|
| 200 |
+
|
| 201 |
+
Parameters
|
| 202 |
+
----------
|
| 203 |
+
G : NetworkX graph
|
| 204 |
+
An undirected graph.
|
| 205 |
+
|
| 206 |
+
Returns
|
| 207 |
+
-------
|
| 208 |
+
bool
|
| 209 |
+
Whether `G` is strongly regular.
|
| 210 |
+
|
| 211 |
+
Examples
|
| 212 |
+
--------
|
| 213 |
+
|
| 214 |
+
The cycle graph on five vertices is strongly regular. It is
|
| 215 |
+
two-regular, each pair of adjacent vertices has no shared neighbors,
|
| 216 |
+
and each pair of nonadjacent vertices has one shared neighbor::
|
| 217 |
+
|
| 218 |
+
>>> G = nx.cycle_graph(5)
|
| 219 |
+
>>> nx.is_strongly_regular(G)
|
| 220 |
+
True
|
| 221 |
+
|
| 222 |
+
"""
|
| 223 |
+
# Here is an alternate implementation based directly on the
|
| 224 |
+
# definition of strongly regular graphs:
|
| 225 |
+
#
|
| 226 |
+
# return (all_equal(G.degree().values())
|
| 227 |
+
# and all_equal(len(common_neighbors(G, u, v))
|
| 228 |
+
# for u, v in G.edges())
|
| 229 |
+
# and all_equal(len(common_neighbors(G, u, v))
|
| 230 |
+
# for u, v in non_edges(G)))
|
| 231 |
+
#
|
| 232 |
+
# We instead use the fact that a distance-regular graph of diameter
|
| 233 |
+
# two is strongly regular.
|
| 234 |
+
return is_distance_regular(G) and diameter(G) == 2
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .maxflow import *
|
| 2 |
+
from .mincost import *
|
| 3 |
+
from .boykovkolmogorov import *
|
| 4 |
+
from .dinitz_alg import *
|
| 5 |
+
from .edmondskarp import *
|
| 6 |
+
from .gomory_hu import *
|
| 7 |
+
from .preflowpush import *
|
| 8 |
+
from .shortestaugmentingpath import *
|
| 9 |
+
from .capacityscaling import *
|
| 10 |
+
from .networksimplex import *
|
| 11 |
+
from .utils import build_flow_dict, build_residual_network
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (670 Bytes). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/edmondskarp.cpython-311.pyc
ADDED
|
Binary file (9.77 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/__pycache__/maxflow.cpython-311.pyc
ADDED
|
Binary file (23.7 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/edmondskarp.py
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Edmonds-Karp algorithm for maximum flow problems.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
from networkx.algorithms.flow.utils import build_residual_network
|
| 7 |
+
|
| 8 |
+
__all__ = ["edmonds_karp"]
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@nx._dispatch(
|
| 12 |
+
graphs="R",
|
| 13 |
+
preserve_edge_attrs={"R": {"capacity": float("inf"), "flow": 0}},
|
| 14 |
+
preserve_graph_attrs=True,
|
| 15 |
+
)
|
| 16 |
+
def edmonds_karp_core(R, s, t, cutoff):
|
| 17 |
+
"""Implementation of the Edmonds-Karp algorithm."""
|
| 18 |
+
R_nodes = R.nodes
|
| 19 |
+
R_pred = R.pred
|
| 20 |
+
R_succ = R.succ
|
| 21 |
+
|
| 22 |
+
inf = R.graph["inf"]
|
| 23 |
+
|
| 24 |
+
def augment(path):
|
| 25 |
+
"""Augment flow along a path from s to t."""
|
| 26 |
+
# Determine the path residual capacity.
|
| 27 |
+
flow = inf
|
| 28 |
+
it = iter(path)
|
| 29 |
+
u = next(it)
|
| 30 |
+
for v in it:
|
| 31 |
+
attr = R_succ[u][v]
|
| 32 |
+
flow = min(flow, attr["capacity"] - attr["flow"])
|
| 33 |
+
u = v
|
| 34 |
+
if flow * 2 > inf:
|
| 35 |
+
raise nx.NetworkXUnbounded("Infinite capacity path, flow unbounded above.")
|
| 36 |
+
# Augment flow along the path.
|
| 37 |
+
it = iter(path)
|
| 38 |
+
u = next(it)
|
| 39 |
+
for v in it:
|
| 40 |
+
R_succ[u][v]["flow"] += flow
|
| 41 |
+
R_succ[v][u]["flow"] -= flow
|
| 42 |
+
u = v
|
| 43 |
+
return flow
|
| 44 |
+
|
| 45 |
+
def bidirectional_bfs():
|
| 46 |
+
"""Bidirectional breadth-first search for an augmenting path."""
|
| 47 |
+
pred = {s: None}
|
| 48 |
+
q_s = [s]
|
| 49 |
+
succ = {t: None}
|
| 50 |
+
q_t = [t]
|
| 51 |
+
while True:
|
| 52 |
+
q = []
|
| 53 |
+
if len(q_s) <= len(q_t):
|
| 54 |
+
for u in q_s:
|
| 55 |
+
for v, attr in R_succ[u].items():
|
| 56 |
+
if v not in pred and attr["flow"] < attr["capacity"]:
|
| 57 |
+
pred[v] = u
|
| 58 |
+
if v in succ:
|
| 59 |
+
return v, pred, succ
|
| 60 |
+
q.append(v)
|
| 61 |
+
if not q:
|
| 62 |
+
return None, None, None
|
| 63 |
+
q_s = q
|
| 64 |
+
else:
|
| 65 |
+
for u in q_t:
|
| 66 |
+
for v, attr in R_pred[u].items():
|
| 67 |
+
if v not in succ and attr["flow"] < attr["capacity"]:
|
| 68 |
+
succ[v] = u
|
| 69 |
+
if v in pred:
|
| 70 |
+
return v, pred, succ
|
| 71 |
+
q.append(v)
|
| 72 |
+
if not q:
|
| 73 |
+
return None, None, None
|
| 74 |
+
q_t = q
|
| 75 |
+
|
| 76 |
+
# Look for shortest augmenting paths using breadth-first search.
|
| 77 |
+
flow_value = 0
|
| 78 |
+
while flow_value < cutoff:
|
| 79 |
+
v, pred, succ = bidirectional_bfs()
|
| 80 |
+
if pred is None:
|
| 81 |
+
break
|
| 82 |
+
path = [v]
|
| 83 |
+
# Trace a path from s to v.
|
| 84 |
+
u = v
|
| 85 |
+
while u != s:
|
| 86 |
+
u = pred[u]
|
| 87 |
+
path.append(u)
|
| 88 |
+
path.reverse()
|
| 89 |
+
# Trace a path from v to t.
|
| 90 |
+
u = v
|
| 91 |
+
while u != t:
|
| 92 |
+
u = succ[u]
|
| 93 |
+
path.append(u)
|
| 94 |
+
flow_value += augment(path)
|
| 95 |
+
|
| 96 |
+
return flow_value
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def edmonds_karp_impl(G, s, t, capacity, residual, cutoff):
|
| 100 |
+
"""Implementation of the Edmonds-Karp algorithm."""
|
| 101 |
+
if s not in G:
|
| 102 |
+
raise nx.NetworkXError(f"node {str(s)} not in graph")
|
| 103 |
+
if t not in G:
|
| 104 |
+
raise nx.NetworkXError(f"node {str(t)} not in graph")
|
| 105 |
+
if s == t:
|
| 106 |
+
raise nx.NetworkXError("source and sink are the same node")
|
| 107 |
+
|
| 108 |
+
if residual is None:
|
| 109 |
+
R = build_residual_network(G, capacity)
|
| 110 |
+
else:
|
| 111 |
+
R = residual
|
| 112 |
+
|
| 113 |
+
# Initialize/reset the residual network.
|
| 114 |
+
for u in R:
|
| 115 |
+
for e in R[u].values():
|
| 116 |
+
e["flow"] = 0
|
| 117 |
+
|
| 118 |
+
if cutoff is None:
|
| 119 |
+
cutoff = float("inf")
|
| 120 |
+
R.graph["flow_value"] = edmonds_karp_core(R, s, t, cutoff)
|
| 121 |
+
|
| 122 |
+
return R
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
@nx._dispatch(
|
| 126 |
+
graphs={"G": 0, "residual?": 4},
|
| 127 |
+
edge_attrs={"capacity": float("inf")},
|
| 128 |
+
preserve_edge_attrs={"residual": {"capacity": float("inf")}},
|
| 129 |
+
preserve_graph_attrs={"residual"},
|
| 130 |
+
)
|
| 131 |
+
def edmonds_karp(
|
| 132 |
+
G, s, t, capacity="capacity", residual=None, value_only=False, cutoff=None
|
| 133 |
+
):
|
| 134 |
+
"""Find a maximum single-commodity flow using the Edmonds-Karp algorithm.
|
| 135 |
+
|
| 136 |
+
This function returns the residual network resulting after computing
|
| 137 |
+
the maximum flow. See below for details about the conventions
|
| 138 |
+
NetworkX uses for defining residual networks.
|
| 139 |
+
|
| 140 |
+
This algorithm has a running time of $O(n m^2)$ for $n$ nodes and $m$
|
| 141 |
+
edges.
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
Parameters
|
| 145 |
+
----------
|
| 146 |
+
G : NetworkX graph
|
| 147 |
+
Edges of the graph are expected to have an attribute called
|
| 148 |
+
'capacity'. If this attribute is not present, the edge is
|
| 149 |
+
considered to have infinite capacity.
|
| 150 |
+
|
| 151 |
+
s : node
|
| 152 |
+
Source node for the flow.
|
| 153 |
+
|
| 154 |
+
t : node
|
| 155 |
+
Sink node for the flow.
|
| 156 |
+
|
| 157 |
+
capacity : string
|
| 158 |
+
Edges of the graph G are expected to have an attribute capacity
|
| 159 |
+
that indicates how much flow the edge can support. If this
|
| 160 |
+
attribute is not present, the edge is considered to have
|
| 161 |
+
infinite capacity. Default value: 'capacity'.
|
| 162 |
+
|
| 163 |
+
residual : NetworkX graph
|
| 164 |
+
Residual network on which the algorithm is to be executed. If None, a
|
| 165 |
+
new residual network is created. Default value: None.
|
| 166 |
+
|
| 167 |
+
value_only : bool
|
| 168 |
+
If True compute only the value of the maximum flow. This parameter
|
| 169 |
+
will be ignored by this algorithm because it is not applicable.
|
| 170 |
+
|
| 171 |
+
cutoff : integer, float
|
| 172 |
+
If specified, the algorithm will terminate when the flow value reaches
|
| 173 |
+
or exceeds the cutoff. In this case, it may be unable to immediately
|
| 174 |
+
determine a minimum cut. Default value: None.
|
| 175 |
+
|
| 176 |
+
Returns
|
| 177 |
+
-------
|
| 178 |
+
R : NetworkX DiGraph
|
| 179 |
+
Residual network after computing the maximum flow.
|
| 180 |
+
|
| 181 |
+
Raises
|
| 182 |
+
------
|
| 183 |
+
NetworkXError
|
| 184 |
+
The algorithm does not support MultiGraph and MultiDiGraph. If
|
| 185 |
+
the input graph is an instance of one of these two classes, a
|
| 186 |
+
NetworkXError is raised.
|
| 187 |
+
|
| 188 |
+
NetworkXUnbounded
|
| 189 |
+
If the graph has a path of infinite capacity, the value of a
|
| 190 |
+
feasible flow on the graph is unbounded above and the function
|
| 191 |
+
raises a NetworkXUnbounded.
|
| 192 |
+
|
| 193 |
+
See also
|
| 194 |
+
--------
|
| 195 |
+
:meth:`maximum_flow`
|
| 196 |
+
:meth:`minimum_cut`
|
| 197 |
+
:meth:`preflow_push`
|
| 198 |
+
:meth:`shortest_augmenting_path`
|
| 199 |
+
|
| 200 |
+
Notes
|
| 201 |
+
-----
|
| 202 |
+
The residual network :samp:`R` from an input graph :samp:`G` has the
|
| 203 |
+
same nodes as :samp:`G`. :samp:`R` is a DiGraph that contains a pair
|
| 204 |
+
of edges :samp:`(u, v)` and :samp:`(v, u)` iff :samp:`(u, v)` is not a
|
| 205 |
+
self-loop, and at least one of :samp:`(u, v)` and :samp:`(v, u)` exists
|
| 206 |
+
in :samp:`G`.
|
| 207 |
+
|
| 208 |
+
For each edge :samp:`(u, v)` in :samp:`R`, :samp:`R[u][v]['capacity']`
|
| 209 |
+
is equal to the capacity of :samp:`(u, v)` in :samp:`G` if it exists
|
| 210 |
+
in :samp:`G` or zero otherwise. If the capacity is infinite,
|
| 211 |
+
:samp:`R[u][v]['capacity']` will have a high arbitrary finite value
|
| 212 |
+
that does not affect the solution of the problem. This value is stored in
|
| 213 |
+
:samp:`R.graph['inf']`. For each edge :samp:`(u, v)` in :samp:`R`,
|
| 214 |
+
:samp:`R[u][v]['flow']` represents the flow function of :samp:`(u, v)` and
|
| 215 |
+
satisfies :samp:`R[u][v]['flow'] == -R[v][u]['flow']`.
|
| 216 |
+
|
| 217 |
+
The flow value, defined as the total flow into :samp:`t`, the sink, is
|
| 218 |
+
stored in :samp:`R.graph['flow_value']`. If :samp:`cutoff` is not
|
| 219 |
+
specified, reachability to :samp:`t` using only edges :samp:`(u, v)` such
|
| 220 |
+
that :samp:`R[u][v]['flow'] < R[u][v]['capacity']` induces a minimum
|
| 221 |
+
:samp:`s`-:samp:`t` cut.
|
| 222 |
+
|
| 223 |
+
Examples
|
| 224 |
+
--------
|
| 225 |
+
>>> from networkx.algorithms.flow import edmonds_karp
|
| 226 |
+
|
| 227 |
+
The functions that implement flow algorithms and output a residual
|
| 228 |
+
network, such as this one, are not imported to the base NetworkX
|
| 229 |
+
namespace, so you have to explicitly import them from the flow package.
|
| 230 |
+
|
| 231 |
+
>>> G = nx.DiGraph()
|
| 232 |
+
>>> G.add_edge("x", "a", capacity=3.0)
|
| 233 |
+
>>> G.add_edge("x", "b", capacity=1.0)
|
| 234 |
+
>>> G.add_edge("a", "c", capacity=3.0)
|
| 235 |
+
>>> G.add_edge("b", "c", capacity=5.0)
|
| 236 |
+
>>> G.add_edge("b", "d", capacity=4.0)
|
| 237 |
+
>>> G.add_edge("d", "e", capacity=2.0)
|
| 238 |
+
>>> G.add_edge("c", "y", capacity=2.0)
|
| 239 |
+
>>> G.add_edge("e", "y", capacity=3.0)
|
| 240 |
+
>>> R = edmonds_karp(G, "x", "y")
|
| 241 |
+
>>> flow_value = nx.maximum_flow_value(G, "x", "y")
|
| 242 |
+
>>> flow_value
|
| 243 |
+
3.0
|
| 244 |
+
>>> flow_value == R.graph["flow_value"]
|
| 245 |
+
True
|
| 246 |
+
|
| 247 |
+
"""
|
| 248 |
+
R = edmonds_karp_impl(G, s, t, capacity, residual, cutoff)
|
| 249 |
+
R.graph["algorithm"] = "edmonds_karp"
|
| 250 |
+
return R
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/mincost.py
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Minimum cost flow algorithms on directed connected graphs.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
__all__ = ["min_cost_flow_cost", "min_cost_flow", "cost_of_flow", "max_flow_min_cost"]
|
| 6 |
+
|
| 7 |
+
import networkx as nx
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@nx._dispatch(node_attrs="demand", edge_attrs={"capacity": float("inf"), "weight": 0})
|
| 11 |
+
def min_cost_flow_cost(G, demand="demand", capacity="capacity", weight="weight"):
|
| 12 |
+
r"""Find the cost of a minimum cost flow satisfying all demands in digraph G.
|
| 13 |
+
|
| 14 |
+
G is a digraph with edge costs and capacities and in which nodes
|
| 15 |
+
have demand, i.e., they want to send or receive some amount of
|
| 16 |
+
flow. A negative demand means that the node wants to send flow, a
|
| 17 |
+
positive demand means that the node want to receive flow. A flow on
|
| 18 |
+
the digraph G satisfies all demand if the net flow into each node
|
| 19 |
+
is equal to the demand of that node.
|
| 20 |
+
|
| 21 |
+
Parameters
|
| 22 |
+
----------
|
| 23 |
+
G : NetworkX graph
|
| 24 |
+
DiGraph on which a minimum cost flow satisfying all demands is
|
| 25 |
+
to be found.
|
| 26 |
+
|
| 27 |
+
demand : string
|
| 28 |
+
Nodes of the graph G are expected to have an attribute demand
|
| 29 |
+
that indicates how much flow a node wants to send (negative
|
| 30 |
+
demand) or receive (positive demand). Note that the sum of the
|
| 31 |
+
demands should be 0 otherwise the problem in not feasible. If
|
| 32 |
+
this attribute is not present, a node is considered to have 0
|
| 33 |
+
demand. Default value: 'demand'.
|
| 34 |
+
|
| 35 |
+
capacity : string
|
| 36 |
+
Edges of the graph G are expected to have an attribute capacity
|
| 37 |
+
that indicates how much flow the edge can support. If this
|
| 38 |
+
attribute is not present, the edge is considered to have
|
| 39 |
+
infinite capacity. Default value: 'capacity'.
|
| 40 |
+
|
| 41 |
+
weight : string
|
| 42 |
+
Edges of the graph G are expected to have an attribute weight
|
| 43 |
+
that indicates the cost incurred by sending one unit of flow on
|
| 44 |
+
that edge. If not present, the weight is considered to be 0.
|
| 45 |
+
Default value: 'weight'.
|
| 46 |
+
|
| 47 |
+
Returns
|
| 48 |
+
-------
|
| 49 |
+
flowCost : integer, float
|
| 50 |
+
Cost of a minimum cost flow satisfying all demands.
|
| 51 |
+
|
| 52 |
+
Raises
|
| 53 |
+
------
|
| 54 |
+
NetworkXError
|
| 55 |
+
This exception is raised if the input graph is not directed or
|
| 56 |
+
not connected.
|
| 57 |
+
|
| 58 |
+
NetworkXUnfeasible
|
| 59 |
+
This exception is raised in the following situations:
|
| 60 |
+
|
| 61 |
+
* The sum of the demands is not zero. Then, there is no
|
| 62 |
+
flow satisfying all demands.
|
| 63 |
+
* There is no flow satisfying all demand.
|
| 64 |
+
|
| 65 |
+
NetworkXUnbounded
|
| 66 |
+
This exception is raised if the digraph G has a cycle of
|
| 67 |
+
negative cost and infinite capacity. Then, the cost of a flow
|
| 68 |
+
satisfying all demands is unbounded below.
|
| 69 |
+
|
| 70 |
+
See also
|
| 71 |
+
--------
|
| 72 |
+
cost_of_flow, max_flow_min_cost, min_cost_flow, network_simplex
|
| 73 |
+
|
| 74 |
+
Notes
|
| 75 |
+
-----
|
| 76 |
+
This algorithm is not guaranteed to work if edge weights or demands
|
| 77 |
+
are floating point numbers (overflows and roundoff errors can
|
| 78 |
+
cause problems). As a workaround you can use integer numbers by
|
| 79 |
+
multiplying the relevant edge attributes by a convenient
|
| 80 |
+
constant factor (eg 100).
|
| 81 |
+
|
| 82 |
+
Examples
|
| 83 |
+
--------
|
| 84 |
+
A simple example of a min cost flow problem.
|
| 85 |
+
|
| 86 |
+
>>> G = nx.DiGraph()
|
| 87 |
+
>>> G.add_node("a", demand=-5)
|
| 88 |
+
>>> G.add_node("d", demand=5)
|
| 89 |
+
>>> G.add_edge("a", "b", weight=3, capacity=4)
|
| 90 |
+
>>> G.add_edge("a", "c", weight=6, capacity=10)
|
| 91 |
+
>>> G.add_edge("b", "d", weight=1, capacity=9)
|
| 92 |
+
>>> G.add_edge("c", "d", weight=2, capacity=5)
|
| 93 |
+
>>> flowCost = nx.min_cost_flow_cost(G)
|
| 94 |
+
>>> flowCost
|
| 95 |
+
24
|
| 96 |
+
"""
|
| 97 |
+
return nx.network_simplex(G, demand=demand, capacity=capacity, weight=weight)[0]
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
@nx._dispatch(node_attrs="demand", edge_attrs={"capacity": float("inf"), "weight": 0})
|
| 101 |
+
def min_cost_flow(G, demand="demand", capacity="capacity", weight="weight"):
|
| 102 |
+
r"""Returns a minimum cost flow satisfying all demands in digraph G.
|
| 103 |
+
|
| 104 |
+
G is a digraph with edge costs and capacities and in which nodes
|
| 105 |
+
have demand, i.e., they want to send or receive some amount of
|
| 106 |
+
flow. A negative demand means that the node wants to send flow, a
|
| 107 |
+
positive demand means that the node want to receive flow. A flow on
|
| 108 |
+
the digraph G satisfies all demand if the net flow into each node
|
| 109 |
+
is equal to the demand of that node.
|
| 110 |
+
|
| 111 |
+
Parameters
|
| 112 |
+
----------
|
| 113 |
+
G : NetworkX graph
|
| 114 |
+
DiGraph on which a minimum cost flow satisfying all demands is
|
| 115 |
+
to be found.
|
| 116 |
+
|
| 117 |
+
demand : string
|
| 118 |
+
Nodes of the graph G are expected to have an attribute demand
|
| 119 |
+
that indicates how much flow a node wants to send (negative
|
| 120 |
+
demand) or receive (positive demand). Note that the sum of the
|
| 121 |
+
demands should be 0 otherwise the problem in not feasible. If
|
| 122 |
+
this attribute is not present, a node is considered to have 0
|
| 123 |
+
demand. Default value: 'demand'.
|
| 124 |
+
|
| 125 |
+
capacity : string
|
| 126 |
+
Edges of the graph G are expected to have an attribute capacity
|
| 127 |
+
that indicates how much flow the edge can support. If this
|
| 128 |
+
attribute is not present, the edge is considered to have
|
| 129 |
+
infinite capacity. Default value: 'capacity'.
|
| 130 |
+
|
| 131 |
+
weight : string
|
| 132 |
+
Edges of the graph G are expected to have an attribute weight
|
| 133 |
+
that indicates the cost incurred by sending one unit of flow on
|
| 134 |
+
that edge. If not present, the weight is considered to be 0.
|
| 135 |
+
Default value: 'weight'.
|
| 136 |
+
|
| 137 |
+
Returns
|
| 138 |
+
-------
|
| 139 |
+
flowDict : dictionary
|
| 140 |
+
Dictionary of dictionaries keyed by nodes such that
|
| 141 |
+
flowDict[u][v] is the flow edge (u, v).
|
| 142 |
+
|
| 143 |
+
Raises
|
| 144 |
+
------
|
| 145 |
+
NetworkXError
|
| 146 |
+
This exception is raised if the input graph is not directed or
|
| 147 |
+
not connected.
|
| 148 |
+
|
| 149 |
+
NetworkXUnfeasible
|
| 150 |
+
This exception is raised in the following situations:
|
| 151 |
+
|
| 152 |
+
* The sum of the demands is not zero. Then, there is no
|
| 153 |
+
flow satisfying all demands.
|
| 154 |
+
* There is no flow satisfying all demand.
|
| 155 |
+
|
| 156 |
+
NetworkXUnbounded
|
| 157 |
+
This exception is raised if the digraph G has a cycle of
|
| 158 |
+
negative cost and infinite capacity. Then, the cost of a flow
|
| 159 |
+
satisfying all demands is unbounded below.
|
| 160 |
+
|
| 161 |
+
See also
|
| 162 |
+
--------
|
| 163 |
+
cost_of_flow, max_flow_min_cost, min_cost_flow_cost, network_simplex
|
| 164 |
+
|
| 165 |
+
Notes
|
| 166 |
+
-----
|
| 167 |
+
This algorithm is not guaranteed to work if edge weights or demands
|
| 168 |
+
are floating point numbers (overflows and roundoff errors can
|
| 169 |
+
cause problems). As a workaround you can use integer numbers by
|
| 170 |
+
multiplying the relevant edge attributes by a convenient
|
| 171 |
+
constant factor (eg 100).
|
| 172 |
+
|
| 173 |
+
Examples
|
| 174 |
+
--------
|
| 175 |
+
A simple example of a min cost flow problem.
|
| 176 |
+
|
| 177 |
+
>>> G = nx.DiGraph()
|
| 178 |
+
>>> G.add_node("a", demand=-5)
|
| 179 |
+
>>> G.add_node("d", demand=5)
|
| 180 |
+
>>> G.add_edge("a", "b", weight=3, capacity=4)
|
| 181 |
+
>>> G.add_edge("a", "c", weight=6, capacity=10)
|
| 182 |
+
>>> G.add_edge("b", "d", weight=1, capacity=9)
|
| 183 |
+
>>> G.add_edge("c", "d", weight=2, capacity=5)
|
| 184 |
+
>>> flowDict = nx.min_cost_flow(G)
|
| 185 |
+
"""
|
| 186 |
+
return nx.network_simplex(G, demand=demand, capacity=capacity, weight=weight)[1]
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
@nx._dispatch(edge_attrs={"weight": 0})
|
| 190 |
+
def cost_of_flow(G, flowDict, weight="weight"):
|
| 191 |
+
"""Compute the cost of the flow given by flowDict on graph G.
|
| 192 |
+
|
| 193 |
+
Note that this function does not check for the validity of the
|
| 194 |
+
flow flowDict. This function will fail if the graph G and the
|
| 195 |
+
flow don't have the same edge set.
|
| 196 |
+
|
| 197 |
+
Parameters
|
| 198 |
+
----------
|
| 199 |
+
G : NetworkX graph
|
| 200 |
+
DiGraph on which a minimum cost flow satisfying all demands is
|
| 201 |
+
to be found.
|
| 202 |
+
|
| 203 |
+
weight : string
|
| 204 |
+
Edges of the graph G are expected to have an attribute weight
|
| 205 |
+
that indicates the cost incurred by sending one unit of flow on
|
| 206 |
+
that edge. If not present, the weight is considered to be 0.
|
| 207 |
+
Default value: 'weight'.
|
| 208 |
+
|
| 209 |
+
flowDict : dictionary
|
| 210 |
+
Dictionary of dictionaries keyed by nodes such that
|
| 211 |
+
flowDict[u][v] is the flow edge (u, v).
|
| 212 |
+
|
| 213 |
+
Returns
|
| 214 |
+
-------
|
| 215 |
+
cost : Integer, float
|
| 216 |
+
The total cost of the flow. This is given by the sum over all
|
| 217 |
+
edges of the product of the edge's flow and the edge's weight.
|
| 218 |
+
|
| 219 |
+
See also
|
| 220 |
+
--------
|
| 221 |
+
max_flow_min_cost, min_cost_flow, min_cost_flow_cost, network_simplex
|
| 222 |
+
|
| 223 |
+
Notes
|
| 224 |
+
-----
|
| 225 |
+
This algorithm is not guaranteed to work if edge weights or demands
|
| 226 |
+
are floating point numbers (overflows and roundoff errors can
|
| 227 |
+
cause problems). As a workaround you can use integer numbers by
|
| 228 |
+
multiplying the relevant edge attributes by a convenient
|
| 229 |
+
constant factor (eg 100).
|
| 230 |
+
"""
|
| 231 |
+
return sum((flowDict[u][v] * d.get(weight, 0) for u, v, d in G.edges(data=True)))
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
@nx._dispatch(edge_attrs={"capacity": float("inf"), "weight": 0})
|
| 235 |
+
def max_flow_min_cost(G, s, t, capacity="capacity", weight="weight"):
|
| 236 |
+
"""Returns a maximum (s, t)-flow of minimum cost.
|
| 237 |
+
|
| 238 |
+
G is a digraph with edge costs and capacities. There is a source
|
| 239 |
+
node s and a sink node t. This function finds a maximum flow from
|
| 240 |
+
s to t whose total cost is minimized.
|
| 241 |
+
|
| 242 |
+
Parameters
|
| 243 |
+
----------
|
| 244 |
+
G : NetworkX graph
|
| 245 |
+
DiGraph on which a minimum cost flow satisfying all demands is
|
| 246 |
+
to be found.
|
| 247 |
+
|
| 248 |
+
s: node label
|
| 249 |
+
Source of the flow.
|
| 250 |
+
|
| 251 |
+
t: node label
|
| 252 |
+
Destination of the flow.
|
| 253 |
+
|
| 254 |
+
capacity: string
|
| 255 |
+
Edges of the graph G are expected to have an attribute capacity
|
| 256 |
+
that indicates how much flow the edge can support. If this
|
| 257 |
+
attribute is not present, the edge is considered to have
|
| 258 |
+
infinite capacity. Default value: 'capacity'.
|
| 259 |
+
|
| 260 |
+
weight: string
|
| 261 |
+
Edges of the graph G are expected to have an attribute weight
|
| 262 |
+
that indicates the cost incurred by sending one unit of flow on
|
| 263 |
+
that edge. If not present, the weight is considered to be 0.
|
| 264 |
+
Default value: 'weight'.
|
| 265 |
+
|
| 266 |
+
Returns
|
| 267 |
+
-------
|
| 268 |
+
flowDict: dictionary
|
| 269 |
+
Dictionary of dictionaries keyed by nodes such that
|
| 270 |
+
flowDict[u][v] is the flow edge (u, v).
|
| 271 |
+
|
| 272 |
+
Raises
|
| 273 |
+
------
|
| 274 |
+
NetworkXError
|
| 275 |
+
This exception is raised if the input graph is not directed or
|
| 276 |
+
not connected.
|
| 277 |
+
|
| 278 |
+
NetworkXUnbounded
|
| 279 |
+
This exception is raised if there is an infinite capacity path
|
| 280 |
+
from s to t in G. In this case there is no maximum flow. This
|
| 281 |
+
exception is also raised if the digraph G has a cycle of
|
| 282 |
+
negative cost and infinite capacity. Then, the cost of a flow
|
| 283 |
+
is unbounded below.
|
| 284 |
+
|
| 285 |
+
See also
|
| 286 |
+
--------
|
| 287 |
+
cost_of_flow, min_cost_flow, min_cost_flow_cost, network_simplex
|
| 288 |
+
|
| 289 |
+
Notes
|
| 290 |
+
-----
|
| 291 |
+
This algorithm is not guaranteed to work if edge weights or demands
|
| 292 |
+
are floating point numbers (overflows and roundoff errors can
|
| 293 |
+
cause problems). As a workaround you can use integer numbers by
|
| 294 |
+
multiplying the relevant edge attributes by a convenient
|
| 295 |
+
constant factor (eg 100).
|
| 296 |
+
|
| 297 |
+
Examples
|
| 298 |
+
--------
|
| 299 |
+
>>> G = nx.DiGraph()
|
| 300 |
+
>>> G.add_edges_from(
|
| 301 |
+
... [
|
| 302 |
+
... (1, 2, {"capacity": 12, "weight": 4}),
|
| 303 |
+
... (1, 3, {"capacity": 20, "weight": 6}),
|
| 304 |
+
... (2, 3, {"capacity": 6, "weight": -3}),
|
| 305 |
+
... (2, 6, {"capacity": 14, "weight": 1}),
|
| 306 |
+
... (3, 4, {"weight": 9}),
|
| 307 |
+
... (3, 5, {"capacity": 10, "weight": 5}),
|
| 308 |
+
... (4, 2, {"capacity": 19, "weight": 13}),
|
| 309 |
+
... (4, 5, {"capacity": 4, "weight": 0}),
|
| 310 |
+
... (5, 7, {"capacity": 28, "weight": 2}),
|
| 311 |
+
... (6, 5, {"capacity": 11, "weight": 1}),
|
| 312 |
+
... (6, 7, {"weight": 8}),
|
| 313 |
+
... (7, 4, {"capacity": 6, "weight": 6}),
|
| 314 |
+
... ]
|
| 315 |
+
... )
|
| 316 |
+
>>> mincostFlow = nx.max_flow_min_cost(G, 1, 7)
|
| 317 |
+
>>> mincost = nx.cost_of_flow(G, mincostFlow)
|
| 318 |
+
>>> mincost
|
| 319 |
+
373
|
| 320 |
+
>>> from networkx.algorithms.flow import maximum_flow
|
| 321 |
+
>>> maxFlow = maximum_flow(G, 1, 7)[1]
|
| 322 |
+
>>> nx.cost_of_flow(G, maxFlow) >= mincost
|
| 323 |
+
True
|
| 324 |
+
>>> mincostFlowValue = sum((mincostFlow[u][7] for u in G.predecessors(7))) - sum(
|
| 325 |
+
... (mincostFlow[7][v] for v in G.successors(7))
|
| 326 |
+
... )
|
| 327 |
+
>>> mincostFlowValue == nx.maximum_flow_value(G, 1, 7)
|
| 328 |
+
True
|
| 329 |
+
|
| 330 |
+
"""
|
| 331 |
+
maxFlow = nx.maximum_flow_value(G, s, t, capacity=capacity)
|
| 332 |
+
H = nx.DiGraph(G)
|
| 333 |
+
H.add_node(s, demand=-maxFlow)
|
| 334 |
+
H.add_node(t, demand=maxFlow)
|
| 335 |
+
return min_cost_flow(H, capacity=capacity, weight=weight)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/shortestaugmentingpath.py
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Shortest augmenting path algorithm for maximum flow problems.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from collections import deque
|
| 6 |
+
|
| 7 |
+
import networkx as nx
|
| 8 |
+
|
| 9 |
+
from .edmondskarp import edmonds_karp_core
|
| 10 |
+
from .utils import CurrentEdge, build_residual_network
|
| 11 |
+
|
| 12 |
+
__all__ = ["shortest_augmenting_path"]
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def shortest_augmenting_path_impl(G, s, t, capacity, residual, two_phase, cutoff):
|
| 16 |
+
"""Implementation of the shortest augmenting path algorithm."""
|
| 17 |
+
if s not in G:
|
| 18 |
+
raise nx.NetworkXError(f"node {str(s)} not in graph")
|
| 19 |
+
if t not in G:
|
| 20 |
+
raise nx.NetworkXError(f"node {str(t)} not in graph")
|
| 21 |
+
if s == t:
|
| 22 |
+
raise nx.NetworkXError("source and sink are the same node")
|
| 23 |
+
|
| 24 |
+
if residual is None:
|
| 25 |
+
R = build_residual_network(G, capacity)
|
| 26 |
+
else:
|
| 27 |
+
R = residual
|
| 28 |
+
|
| 29 |
+
R_nodes = R.nodes
|
| 30 |
+
R_pred = R.pred
|
| 31 |
+
R_succ = R.succ
|
| 32 |
+
|
| 33 |
+
# Initialize/reset the residual network.
|
| 34 |
+
for u in R:
|
| 35 |
+
for e in R_succ[u].values():
|
| 36 |
+
e["flow"] = 0
|
| 37 |
+
|
| 38 |
+
# Initialize heights of the nodes.
|
| 39 |
+
heights = {t: 0}
|
| 40 |
+
q = deque([(t, 0)])
|
| 41 |
+
while q:
|
| 42 |
+
u, height = q.popleft()
|
| 43 |
+
height += 1
|
| 44 |
+
for v, attr in R_pred[u].items():
|
| 45 |
+
if v not in heights and attr["flow"] < attr["capacity"]:
|
| 46 |
+
heights[v] = height
|
| 47 |
+
q.append((v, height))
|
| 48 |
+
|
| 49 |
+
if s not in heights:
|
| 50 |
+
# t is not reachable from s in the residual network. The maximum flow
|
| 51 |
+
# must be zero.
|
| 52 |
+
R.graph["flow_value"] = 0
|
| 53 |
+
return R
|
| 54 |
+
|
| 55 |
+
n = len(G)
|
| 56 |
+
m = R.size() / 2
|
| 57 |
+
|
| 58 |
+
# Initialize heights and 'current edge' data structures of the nodes.
|
| 59 |
+
for u in R:
|
| 60 |
+
R_nodes[u]["height"] = heights[u] if u in heights else n
|
| 61 |
+
R_nodes[u]["curr_edge"] = CurrentEdge(R_succ[u])
|
| 62 |
+
|
| 63 |
+
# Initialize counts of nodes in each level.
|
| 64 |
+
counts = [0] * (2 * n - 1)
|
| 65 |
+
for u in R:
|
| 66 |
+
counts[R_nodes[u]["height"]] += 1
|
| 67 |
+
|
| 68 |
+
inf = R.graph["inf"]
|
| 69 |
+
|
| 70 |
+
def augment(path):
|
| 71 |
+
"""Augment flow along a path from s to t."""
|
| 72 |
+
# Determine the path residual capacity.
|
| 73 |
+
flow = inf
|
| 74 |
+
it = iter(path)
|
| 75 |
+
u = next(it)
|
| 76 |
+
for v in it:
|
| 77 |
+
attr = R_succ[u][v]
|
| 78 |
+
flow = min(flow, attr["capacity"] - attr["flow"])
|
| 79 |
+
u = v
|
| 80 |
+
if flow * 2 > inf:
|
| 81 |
+
raise nx.NetworkXUnbounded("Infinite capacity path, flow unbounded above.")
|
| 82 |
+
# Augment flow along the path.
|
| 83 |
+
it = iter(path)
|
| 84 |
+
u = next(it)
|
| 85 |
+
for v in it:
|
| 86 |
+
R_succ[u][v]["flow"] += flow
|
| 87 |
+
R_succ[v][u]["flow"] -= flow
|
| 88 |
+
u = v
|
| 89 |
+
return flow
|
| 90 |
+
|
| 91 |
+
def relabel(u):
|
| 92 |
+
"""Relabel a node to create an admissible edge."""
|
| 93 |
+
height = n - 1
|
| 94 |
+
for v, attr in R_succ[u].items():
|
| 95 |
+
if attr["flow"] < attr["capacity"]:
|
| 96 |
+
height = min(height, R_nodes[v]["height"])
|
| 97 |
+
return height + 1
|
| 98 |
+
|
| 99 |
+
if cutoff is None:
|
| 100 |
+
cutoff = float("inf")
|
| 101 |
+
|
| 102 |
+
# Phase 1: Look for shortest augmenting paths using depth-first search.
|
| 103 |
+
|
| 104 |
+
flow_value = 0
|
| 105 |
+
path = [s]
|
| 106 |
+
u = s
|
| 107 |
+
d = n if not two_phase else int(min(m**0.5, 2 * n ** (2.0 / 3)))
|
| 108 |
+
done = R_nodes[s]["height"] >= d
|
| 109 |
+
while not done:
|
| 110 |
+
height = R_nodes[u]["height"]
|
| 111 |
+
curr_edge = R_nodes[u]["curr_edge"]
|
| 112 |
+
# Depth-first search for the next node on the path to t.
|
| 113 |
+
while True:
|
| 114 |
+
v, attr = curr_edge.get()
|
| 115 |
+
if height == R_nodes[v]["height"] + 1 and attr["flow"] < attr["capacity"]:
|
| 116 |
+
# Advance to the next node following an admissible edge.
|
| 117 |
+
path.append(v)
|
| 118 |
+
u = v
|
| 119 |
+
break
|
| 120 |
+
try:
|
| 121 |
+
curr_edge.move_to_next()
|
| 122 |
+
except StopIteration:
|
| 123 |
+
counts[height] -= 1
|
| 124 |
+
if counts[height] == 0:
|
| 125 |
+
# Gap heuristic: If relabeling causes a level to become
|
| 126 |
+
# empty, a minimum cut has been identified. The algorithm
|
| 127 |
+
# can now be terminated.
|
| 128 |
+
R.graph["flow_value"] = flow_value
|
| 129 |
+
return R
|
| 130 |
+
height = relabel(u)
|
| 131 |
+
if u == s and height >= d:
|
| 132 |
+
if not two_phase:
|
| 133 |
+
# t is disconnected from s in the residual network. No
|
| 134 |
+
# more augmenting paths exist.
|
| 135 |
+
R.graph["flow_value"] = flow_value
|
| 136 |
+
return R
|
| 137 |
+
else:
|
| 138 |
+
# t is at least d steps away from s. End of phase 1.
|
| 139 |
+
done = True
|
| 140 |
+
break
|
| 141 |
+
counts[height] += 1
|
| 142 |
+
R_nodes[u]["height"] = height
|
| 143 |
+
if u != s:
|
| 144 |
+
# After relabeling, the last edge on the path is no longer
|
| 145 |
+
# admissible. Retreat one step to look for an alternative.
|
| 146 |
+
path.pop()
|
| 147 |
+
u = path[-1]
|
| 148 |
+
break
|
| 149 |
+
if u == t:
|
| 150 |
+
# t is reached. Augment flow along the path and reset it for a new
|
| 151 |
+
# depth-first search.
|
| 152 |
+
flow_value += augment(path)
|
| 153 |
+
if flow_value >= cutoff:
|
| 154 |
+
R.graph["flow_value"] = flow_value
|
| 155 |
+
return R
|
| 156 |
+
path = [s]
|
| 157 |
+
u = s
|
| 158 |
+
|
| 159 |
+
# Phase 2: Look for shortest augmenting paths using breadth-first search.
|
| 160 |
+
flow_value += edmonds_karp_core(R, s, t, cutoff - flow_value)
|
| 161 |
+
|
| 162 |
+
R.graph["flow_value"] = flow_value
|
| 163 |
+
return R
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
@nx._dispatch(
|
| 167 |
+
graphs={"G": 0, "residual?": 4},
|
| 168 |
+
edge_attrs={"capacity": float("inf")},
|
| 169 |
+
preserve_edge_attrs={"residual": {"capacity": float("inf")}},
|
| 170 |
+
preserve_graph_attrs={"residual"},
|
| 171 |
+
)
|
| 172 |
+
def shortest_augmenting_path(
|
| 173 |
+
G,
|
| 174 |
+
s,
|
| 175 |
+
t,
|
| 176 |
+
capacity="capacity",
|
| 177 |
+
residual=None,
|
| 178 |
+
value_only=False,
|
| 179 |
+
two_phase=False,
|
| 180 |
+
cutoff=None,
|
| 181 |
+
):
|
| 182 |
+
r"""Find a maximum single-commodity flow using the shortest augmenting path
|
| 183 |
+
algorithm.
|
| 184 |
+
|
| 185 |
+
This function returns the residual network resulting after computing
|
| 186 |
+
the maximum flow. See below for details about the conventions
|
| 187 |
+
NetworkX uses for defining residual networks.
|
| 188 |
+
|
| 189 |
+
This algorithm has a running time of $O(n^2 m)$ for $n$ nodes and $m$
|
| 190 |
+
edges.
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
Parameters
|
| 194 |
+
----------
|
| 195 |
+
G : NetworkX graph
|
| 196 |
+
Edges of the graph are expected to have an attribute called
|
| 197 |
+
'capacity'. If this attribute is not present, the edge is
|
| 198 |
+
considered to have infinite capacity.
|
| 199 |
+
|
| 200 |
+
s : node
|
| 201 |
+
Source node for the flow.
|
| 202 |
+
|
| 203 |
+
t : node
|
| 204 |
+
Sink node for the flow.
|
| 205 |
+
|
| 206 |
+
capacity : string
|
| 207 |
+
Edges of the graph G are expected to have an attribute capacity
|
| 208 |
+
that indicates how much flow the edge can support. If this
|
| 209 |
+
attribute is not present, the edge is considered to have
|
| 210 |
+
infinite capacity. Default value: 'capacity'.
|
| 211 |
+
|
| 212 |
+
residual : NetworkX graph
|
| 213 |
+
Residual network on which the algorithm is to be executed. If None, a
|
| 214 |
+
new residual network is created. Default value: None.
|
| 215 |
+
|
| 216 |
+
value_only : bool
|
| 217 |
+
If True compute only the value of the maximum flow. This parameter
|
| 218 |
+
will be ignored by this algorithm because it is not applicable.
|
| 219 |
+
|
| 220 |
+
two_phase : bool
|
| 221 |
+
If True, a two-phase variant is used. The two-phase variant improves
|
| 222 |
+
the running time on unit-capacity networks from $O(nm)$ to
|
| 223 |
+
$O(\min(n^{2/3}, m^{1/2}) m)$. Default value: False.
|
| 224 |
+
|
| 225 |
+
cutoff : integer, float
|
| 226 |
+
If specified, the algorithm will terminate when the flow value reaches
|
| 227 |
+
or exceeds the cutoff. In this case, it may be unable to immediately
|
| 228 |
+
determine a minimum cut. Default value: None.
|
| 229 |
+
|
| 230 |
+
Returns
|
| 231 |
+
-------
|
| 232 |
+
R : NetworkX DiGraph
|
| 233 |
+
Residual network after computing the maximum flow.
|
| 234 |
+
|
| 235 |
+
Raises
|
| 236 |
+
------
|
| 237 |
+
NetworkXError
|
| 238 |
+
The algorithm does not support MultiGraph and MultiDiGraph. If
|
| 239 |
+
the input graph is an instance of one of these two classes, a
|
| 240 |
+
NetworkXError is raised.
|
| 241 |
+
|
| 242 |
+
NetworkXUnbounded
|
| 243 |
+
If the graph has a path of infinite capacity, the value of a
|
| 244 |
+
feasible flow on the graph is unbounded above and the function
|
| 245 |
+
raises a NetworkXUnbounded.
|
| 246 |
+
|
| 247 |
+
See also
|
| 248 |
+
--------
|
| 249 |
+
:meth:`maximum_flow`
|
| 250 |
+
:meth:`minimum_cut`
|
| 251 |
+
:meth:`edmonds_karp`
|
| 252 |
+
:meth:`preflow_push`
|
| 253 |
+
|
| 254 |
+
Notes
|
| 255 |
+
-----
|
| 256 |
+
The residual network :samp:`R` from an input graph :samp:`G` has the
|
| 257 |
+
same nodes as :samp:`G`. :samp:`R` is a DiGraph that contains a pair
|
| 258 |
+
of edges :samp:`(u, v)` and :samp:`(v, u)` iff :samp:`(u, v)` is not a
|
| 259 |
+
self-loop, and at least one of :samp:`(u, v)` and :samp:`(v, u)` exists
|
| 260 |
+
in :samp:`G`.
|
| 261 |
+
|
| 262 |
+
For each edge :samp:`(u, v)` in :samp:`R`, :samp:`R[u][v]['capacity']`
|
| 263 |
+
is equal to the capacity of :samp:`(u, v)` in :samp:`G` if it exists
|
| 264 |
+
in :samp:`G` or zero otherwise. If the capacity is infinite,
|
| 265 |
+
:samp:`R[u][v]['capacity']` will have a high arbitrary finite value
|
| 266 |
+
that does not affect the solution of the problem. This value is stored in
|
| 267 |
+
:samp:`R.graph['inf']`. For each edge :samp:`(u, v)` in :samp:`R`,
|
| 268 |
+
:samp:`R[u][v]['flow']` represents the flow function of :samp:`(u, v)` and
|
| 269 |
+
satisfies :samp:`R[u][v]['flow'] == -R[v][u]['flow']`.
|
| 270 |
+
|
| 271 |
+
The flow value, defined as the total flow into :samp:`t`, the sink, is
|
| 272 |
+
stored in :samp:`R.graph['flow_value']`. If :samp:`cutoff` is not
|
| 273 |
+
specified, reachability to :samp:`t` using only edges :samp:`(u, v)` such
|
| 274 |
+
that :samp:`R[u][v]['flow'] < R[u][v]['capacity']` induces a minimum
|
| 275 |
+
:samp:`s`-:samp:`t` cut.
|
| 276 |
+
|
| 277 |
+
Examples
|
| 278 |
+
--------
|
| 279 |
+
>>> from networkx.algorithms.flow import shortest_augmenting_path
|
| 280 |
+
|
| 281 |
+
The functions that implement flow algorithms and output a residual
|
| 282 |
+
network, such as this one, are not imported to the base NetworkX
|
| 283 |
+
namespace, so you have to explicitly import them from the flow package.
|
| 284 |
+
|
| 285 |
+
>>> G = nx.DiGraph()
|
| 286 |
+
>>> G.add_edge("x", "a", capacity=3.0)
|
| 287 |
+
>>> G.add_edge("x", "b", capacity=1.0)
|
| 288 |
+
>>> G.add_edge("a", "c", capacity=3.0)
|
| 289 |
+
>>> G.add_edge("b", "c", capacity=5.0)
|
| 290 |
+
>>> G.add_edge("b", "d", capacity=4.0)
|
| 291 |
+
>>> G.add_edge("d", "e", capacity=2.0)
|
| 292 |
+
>>> G.add_edge("c", "y", capacity=2.0)
|
| 293 |
+
>>> G.add_edge("e", "y", capacity=3.0)
|
| 294 |
+
>>> R = shortest_augmenting_path(G, "x", "y")
|
| 295 |
+
>>> flow_value = nx.maximum_flow_value(G, "x", "y")
|
| 296 |
+
>>> flow_value
|
| 297 |
+
3.0
|
| 298 |
+
>>> flow_value == R.graph["flow_value"]
|
| 299 |
+
True
|
| 300 |
+
|
| 301 |
+
"""
|
| 302 |
+
R = shortest_augmenting_path_impl(G, s, t, capacity, residual, two_phase, cutoff)
|
| 303 |
+
R.graph["algorithm"] = "shortest_augmenting_path"
|
| 304 |
+
return R
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/flow/tests/test_mincost.py
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import bz2
|
| 2 |
+
import importlib.resources
|
| 3 |
+
import os
|
| 4 |
+
import pickle
|
| 5 |
+
|
| 6 |
+
import pytest
|
| 7 |
+
|
| 8 |
+
import networkx as nx
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class TestMinCostFlow:
|
| 12 |
+
def test_simple_digraph(self):
|
| 13 |
+
G = nx.DiGraph()
|
| 14 |
+
G.add_node("a", demand=-5)
|
| 15 |
+
G.add_node("d", demand=5)
|
| 16 |
+
G.add_edge("a", "b", weight=3, capacity=4)
|
| 17 |
+
G.add_edge("a", "c", weight=6, capacity=10)
|
| 18 |
+
G.add_edge("b", "d", weight=1, capacity=9)
|
| 19 |
+
G.add_edge("c", "d", weight=2, capacity=5)
|
| 20 |
+
flowCost, H = nx.network_simplex(G)
|
| 21 |
+
soln = {"a": {"b": 4, "c": 1}, "b": {"d": 4}, "c": {"d": 1}, "d": {}}
|
| 22 |
+
assert flowCost == 24
|
| 23 |
+
assert nx.min_cost_flow_cost(G) == 24
|
| 24 |
+
assert H == soln
|
| 25 |
+
assert nx.min_cost_flow(G) == soln
|
| 26 |
+
assert nx.cost_of_flow(G, H) == 24
|
| 27 |
+
|
| 28 |
+
flowCost, H = nx.capacity_scaling(G)
|
| 29 |
+
assert flowCost == 24
|
| 30 |
+
assert nx.cost_of_flow(G, H) == 24
|
| 31 |
+
assert H == soln
|
| 32 |
+
|
| 33 |
+
def test_negcycle_infcap(self):
|
| 34 |
+
G = nx.DiGraph()
|
| 35 |
+
G.add_node("s", demand=-5)
|
| 36 |
+
G.add_node("t", demand=5)
|
| 37 |
+
G.add_edge("s", "a", weight=1, capacity=3)
|
| 38 |
+
G.add_edge("a", "b", weight=3)
|
| 39 |
+
G.add_edge("c", "a", weight=-6)
|
| 40 |
+
G.add_edge("b", "d", weight=1)
|
| 41 |
+
G.add_edge("d", "c", weight=-2)
|
| 42 |
+
G.add_edge("d", "t", weight=1, capacity=3)
|
| 43 |
+
pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
|
| 44 |
+
pytest.raises(nx.NetworkXUnbounded, nx.capacity_scaling, G)
|
| 45 |
+
|
| 46 |
+
def test_sum_demands_not_zero(self):
|
| 47 |
+
G = nx.DiGraph()
|
| 48 |
+
G.add_node("s", demand=-5)
|
| 49 |
+
G.add_node("t", demand=4)
|
| 50 |
+
G.add_edge("s", "a", weight=1, capacity=3)
|
| 51 |
+
G.add_edge("a", "b", weight=3)
|
| 52 |
+
G.add_edge("a", "c", weight=-6)
|
| 53 |
+
G.add_edge("b", "d", weight=1)
|
| 54 |
+
G.add_edge("c", "d", weight=-2)
|
| 55 |
+
G.add_edge("d", "t", weight=1, capacity=3)
|
| 56 |
+
pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
|
| 57 |
+
pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G)
|
| 58 |
+
|
| 59 |
+
def test_no_flow_satisfying_demands(self):
|
| 60 |
+
G = nx.DiGraph()
|
| 61 |
+
G.add_node("s", demand=-5)
|
| 62 |
+
G.add_node("t", demand=5)
|
| 63 |
+
G.add_edge("s", "a", weight=1, capacity=3)
|
| 64 |
+
G.add_edge("a", "b", weight=3)
|
| 65 |
+
G.add_edge("a", "c", weight=-6)
|
| 66 |
+
G.add_edge("b", "d", weight=1)
|
| 67 |
+
G.add_edge("c", "d", weight=-2)
|
| 68 |
+
G.add_edge("d", "t", weight=1, capacity=3)
|
| 69 |
+
pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
|
| 70 |
+
pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G)
|
| 71 |
+
|
| 72 |
+
def test_transshipment(self):
|
| 73 |
+
G = nx.DiGraph()
|
| 74 |
+
G.add_node("a", demand=1)
|
| 75 |
+
G.add_node("b", demand=-2)
|
| 76 |
+
G.add_node("c", demand=-2)
|
| 77 |
+
G.add_node("d", demand=3)
|
| 78 |
+
G.add_node("e", demand=-4)
|
| 79 |
+
G.add_node("f", demand=-4)
|
| 80 |
+
G.add_node("g", demand=3)
|
| 81 |
+
G.add_node("h", demand=2)
|
| 82 |
+
G.add_node("r", demand=3)
|
| 83 |
+
G.add_edge("a", "c", weight=3)
|
| 84 |
+
G.add_edge("r", "a", weight=2)
|
| 85 |
+
G.add_edge("b", "a", weight=9)
|
| 86 |
+
G.add_edge("r", "c", weight=0)
|
| 87 |
+
G.add_edge("b", "r", weight=-6)
|
| 88 |
+
G.add_edge("c", "d", weight=5)
|
| 89 |
+
G.add_edge("e", "r", weight=4)
|
| 90 |
+
G.add_edge("e", "f", weight=3)
|
| 91 |
+
G.add_edge("h", "b", weight=4)
|
| 92 |
+
G.add_edge("f", "d", weight=7)
|
| 93 |
+
G.add_edge("f", "h", weight=12)
|
| 94 |
+
G.add_edge("g", "d", weight=12)
|
| 95 |
+
G.add_edge("f", "g", weight=-1)
|
| 96 |
+
G.add_edge("h", "g", weight=-10)
|
| 97 |
+
flowCost, H = nx.network_simplex(G)
|
| 98 |
+
soln = {
|
| 99 |
+
"a": {"c": 0},
|
| 100 |
+
"b": {"a": 0, "r": 2},
|
| 101 |
+
"c": {"d": 3},
|
| 102 |
+
"d": {},
|
| 103 |
+
"e": {"r": 3, "f": 1},
|
| 104 |
+
"f": {"d": 0, "g": 3, "h": 2},
|
| 105 |
+
"g": {"d": 0},
|
| 106 |
+
"h": {"b": 0, "g": 0},
|
| 107 |
+
"r": {"a": 1, "c": 1},
|
| 108 |
+
}
|
| 109 |
+
assert flowCost == 41
|
| 110 |
+
assert nx.min_cost_flow_cost(G) == 41
|
| 111 |
+
assert H == soln
|
| 112 |
+
assert nx.min_cost_flow(G) == soln
|
| 113 |
+
assert nx.cost_of_flow(G, H) == 41
|
| 114 |
+
|
| 115 |
+
flowCost, H = nx.capacity_scaling(G)
|
| 116 |
+
assert flowCost == 41
|
| 117 |
+
assert nx.cost_of_flow(G, H) == 41
|
| 118 |
+
assert H == soln
|
| 119 |
+
|
| 120 |
+
def test_max_flow_min_cost(self):
|
| 121 |
+
G = nx.DiGraph()
|
| 122 |
+
G.add_edge("s", "a", bandwidth=6)
|
| 123 |
+
G.add_edge("s", "c", bandwidth=10, cost=10)
|
| 124 |
+
G.add_edge("a", "b", cost=6)
|
| 125 |
+
G.add_edge("b", "d", bandwidth=8, cost=7)
|
| 126 |
+
G.add_edge("c", "d", cost=10)
|
| 127 |
+
G.add_edge("d", "t", bandwidth=5, cost=5)
|
| 128 |
+
soln = {
|
| 129 |
+
"s": {"a": 5, "c": 0},
|
| 130 |
+
"a": {"b": 5},
|
| 131 |
+
"b": {"d": 5},
|
| 132 |
+
"c": {"d": 0},
|
| 133 |
+
"d": {"t": 5},
|
| 134 |
+
"t": {},
|
| 135 |
+
}
|
| 136 |
+
flow = nx.max_flow_min_cost(G, "s", "t", capacity="bandwidth", weight="cost")
|
| 137 |
+
assert flow == soln
|
| 138 |
+
assert nx.cost_of_flow(G, flow, weight="cost") == 90
|
| 139 |
+
|
| 140 |
+
G.add_edge("t", "s", cost=-100)
|
| 141 |
+
flowCost, flow = nx.capacity_scaling(G, capacity="bandwidth", weight="cost")
|
| 142 |
+
G.remove_edge("t", "s")
|
| 143 |
+
assert flowCost == -410
|
| 144 |
+
assert flow["t"]["s"] == 5
|
| 145 |
+
del flow["t"]["s"]
|
| 146 |
+
assert flow == soln
|
| 147 |
+
assert nx.cost_of_flow(G, flow, weight="cost") == 90
|
| 148 |
+
|
| 149 |
+
def test_digraph1(self):
|
| 150 |
+
# From Bradley, S. P., Hax, A. C. and Magnanti, T. L. Applied
|
| 151 |
+
# Mathematical Programming. Addison-Wesley, 1977.
|
| 152 |
+
G = nx.DiGraph()
|
| 153 |
+
G.add_node(1, demand=-20)
|
| 154 |
+
G.add_node(4, demand=5)
|
| 155 |
+
G.add_node(5, demand=15)
|
| 156 |
+
G.add_edges_from(
|
| 157 |
+
[
|
| 158 |
+
(1, 2, {"capacity": 15, "weight": 4}),
|
| 159 |
+
(1, 3, {"capacity": 8, "weight": 4}),
|
| 160 |
+
(2, 3, {"weight": 2}),
|
| 161 |
+
(2, 4, {"capacity": 4, "weight": 2}),
|
| 162 |
+
(2, 5, {"capacity": 10, "weight": 6}),
|
| 163 |
+
(3, 4, {"capacity": 15, "weight": 1}),
|
| 164 |
+
(3, 5, {"capacity": 5, "weight": 3}),
|
| 165 |
+
(4, 5, {"weight": 2}),
|
| 166 |
+
(5, 3, {"capacity": 4, "weight": 1}),
|
| 167 |
+
]
|
| 168 |
+
)
|
| 169 |
+
flowCost, H = nx.network_simplex(G)
|
| 170 |
+
soln = {
|
| 171 |
+
1: {2: 12, 3: 8},
|
| 172 |
+
2: {3: 8, 4: 4, 5: 0},
|
| 173 |
+
3: {4: 11, 5: 5},
|
| 174 |
+
4: {5: 10},
|
| 175 |
+
5: {3: 0},
|
| 176 |
+
}
|
| 177 |
+
assert flowCost == 150
|
| 178 |
+
assert nx.min_cost_flow_cost(G) == 150
|
| 179 |
+
assert H == soln
|
| 180 |
+
assert nx.min_cost_flow(G) == soln
|
| 181 |
+
assert nx.cost_of_flow(G, H) == 150
|
| 182 |
+
|
| 183 |
+
flowCost, H = nx.capacity_scaling(G)
|
| 184 |
+
assert flowCost == 150
|
| 185 |
+
assert H == soln
|
| 186 |
+
assert nx.cost_of_flow(G, H) == 150
|
| 187 |
+
|
| 188 |
+
def test_digraph2(self):
|
| 189 |
+
# Example from ticket #430 from mfrasca. Original source:
|
| 190 |
+
# http://www.cs.princeton.edu/courses/archive/spr03/cs226/lectures/mincost.4up.pdf, slide 11.
|
| 191 |
+
G = nx.DiGraph()
|
| 192 |
+
G.add_edge("s", 1, capacity=12)
|
| 193 |
+
G.add_edge("s", 2, capacity=6)
|
| 194 |
+
G.add_edge("s", 3, capacity=14)
|
| 195 |
+
G.add_edge(1, 2, capacity=11, weight=4)
|
| 196 |
+
G.add_edge(2, 3, capacity=9, weight=6)
|
| 197 |
+
G.add_edge(1, 4, capacity=5, weight=5)
|
| 198 |
+
G.add_edge(1, 5, capacity=2, weight=12)
|
| 199 |
+
G.add_edge(2, 5, capacity=4, weight=4)
|
| 200 |
+
G.add_edge(2, 6, capacity=2, weight=6)
|
| 201 |
+
G.add_edge(3, 6, capacity=31, weight=3)
|
| 202 |
+
G.add_edge(4, 5, capacity=18, weight=4)
|
| 203 |
+
G.add_edge(5, 6, capacity=9, weight=5)
|
| 204 |
+
G.add_edge(4, "t", capacity=3)
|
| 205 |
+
G.add_edge(5, "t", capacity=7)
|
| 206 |
+
G.add_edge(6, "t", capacity=22)
|
| 207 |
+
flow = nx.max_flow_min_cost(G, "s", "t")
|
| 208 |
+
soln = {
|
| 209 |
+
1: {2: 6, 4: 5, 5: 1},
|
| 210 |
+
2: {3: 6, 5: 4, 6: 2},
|
| 211 |
+
3: {6: 20},
|
| 212 |
+
4: {5: 2, "t": 3},
|
| 213 |
+
5: {6: 0, "t": 7},
|
| 214 |
+
6: {"t": 22},
|
| 215 |
+
"s": {1: 12, 2: 6, 3: 14},
|
| 216 |
+
"t": {},
|
| 217 |
+
}
|
| 218 |
+
assert flow == soln
|
| 219 |
+
|
| 220 |
+
G.add_edge("t", "s", weight=-100)
|
| 221 |
+
flowCost, flow = nx.capacity_scaling(G)
|
| 222 |
+
G.remove_edge("t", "s")
|
| 223 |
+
assert flow["t"]["s"] == 32
|
| 224 |
+
assert flowCost == -3007
|
| 225 |
+
del flow["t"]["s"]
|
| 226 |
+
assert flow == soln
|
| 227 |
+
assert nx.cost_of_flow(G, flow) == 193
|
| 228 |
+
|
| 229 |
+
def test_digraph3(self):
|
| 230 |
+
"""Combinatorial Optimization: Algorithms and Complexity,
|
| 231 |
+
Papadimitriou Steiglitz at page 140 has an example, 7.1, but that
|
| 232 |
+
admits multiple solutions, so I alter it a bit. From ticket #430
|
| 233 |
+
by mfrasca."""
|
| 234 |
+
|
| 235 |
+
G = nx.DiGraph()
|
| 236 |
+
G.add_edge("s", "a")
|
| 237 |
+
G["s"]["a"].update({0: 2, 1: 4})
|
| 238 |
+
G.add_edge("s", "b")
|
| 239 |
+
G["s"]["b"].update({0: 2, 1: 1})
|
| 240 |
+
G.add_edge("a", "b")
|
| 241 |
+
G["a"]["b"].update({0: 5, 1: 2})
|
| 242 |
+
G.add_edge("a", "t")
|
| 243 |
+
G["a"]["t"].update({0: 1, 1: 5})
|
| 244 |
+
G.add_edge("b", "a")
|
| 245 |
+
G["b"]["a"].update({0: 1, 1: 3})
|
| 246 |
+
G.add_edge("b", "t")
|
| 247 |
+
G["b"]["t"].update({0: 3, 1: 2})
|
| 248 |
+
|
| 249 |
+
"PS.ex.7.1: testing main function"
|
| 250 |
+
sol = nx.max_flow_min_cost(G, "s", "t", capacity=0, weight=1)
|
| 251 |
+
flow = sum(v for v in sol["s"].values())
|
| 252 |
+
assert 4 == flow
|
| 253 |
+
assert 23 == nx.cost_of_flow(G, sol, weight=1)
|
| 254 |
+
assert sol["s"] == {"a": 2, "b": 2}
|
| 255 |
+
assert sol["a"] == {"b": 1, "t": 1}
|
| 256 |
+
assert sol["b"] == {"a": 0, "t": 3}
|
| 257 |
+
assert sol["t"] == {}
|
| 258 |
+
|
| 259 |
+
G.add_edge("t", "s")
|
| 260 |
+
G["t"]["s"].update({1: -100})
|
| 261 |
+
flowCost, sol = nx.capacity_scaling(G, capacity=0, weight=1)
|
| 262 |
+
G.remove_edge("t", "s")
|
| 263 |
+
flow = sum(v for v in sol["s"].values())
|
| 264 |
+
assert 4 == flow
|
| 265 |
+
assert sol["t"]["s"] == 4
|
| 266 |
+
assert flowCost == -377
|
| 267 |
+
del sol["t"]["s"]
|
| 268 |
+
assert sol["s"] == {"a": 2, "b": 2}
|
| 269 |
+
assert sol["a"] == {"b": 1, "t": 1}
|
| 270 |
+
assert sol["b"] == {"a": 0, "t": 3}
|
| 271 |
+
assert sol["t"] == {}
|
| 272 |
+
assert nx.cost_of_flow(G, sol, weight=1) == 23
|
| 273 |
+
|
| 274 |
+
def test_zero_capacity_edges(self):
|
| 275 |
+
"""Address issue raised in ticket #617 by arv."""
|
| 276 |
+
G = nx.DiGraph()
|
| 277 |
+
G.add_edges_from(
|
| 278 |
+
[
|
| 279 |
+
(1, 2, {"capacity": 1, "weight": 1}),
|
| 280 |
+
(1, 5, {"capacity": 1, "weight": 1}),
|
| 281 |
+
(2, 3, {"capacity": 0, "weight": 1}),
|
| 282 |
+
(2, 5, {"capacity": 1, "weight": 1}),
|
| 283 |
+
(5, 3, {"capacity": 2, "weight": 1}),
|
| 284 |
+
(5, 4, {"capacity": 0, "weight": 1}),
|
| 285 |
+
(3, 4, {"capacity": 2, "weight": 1}),
|
| 286 |
+
]
|
| 287 |
+
)
|
| 288 |
+
G.nodes[1]["demand"] = -1
|
| 289 |
+
G.nodes[2]["demand"] = -1
|
| 290 |
+
G.nodes[4]["demand"] = 2
|
| 291 |
+
|
| 292 |
+
flowCost, H = nx.network_simplex(G)
|
| 293 |
+
soln = {1: {2: 0, 5: 1}, 2: {3: 0, 5: 1}, 3: {4: 2}, 4: {}, 5: {3: 2, 4: 0}}
|
| 294 |
+
assert flowCost == 6
|
| 295 |
+
assert nx.min_cost_flow_cost(G) == 6
|
| 296 |
+
assert H == soln
|
| 297 |
+
assert nx.min_cost_flow(G) == soln
|
| 298 |
+
assert nx.cost_of_flow(G, H) == 6
|
| 299 |
+
|
| 300 |
+
flowCost, H = nx.capacity_scaling(G)
|
| 301 |
+
assert flowCost == 6
|
| 302 |
+
assert H == soln
|
| 303 |
+
assert nx.cost_of_flow(G, H) == 6
|
| 304 |
+
|
| 305 |
+
def test_digon(self):
|
| 306 |
+
"""Check if digons are handled properly. Taken from ticket
|
| 307 |
+
#618 by arv."""
|
| 308 |
+
nodes = [(1, {}), (2, {"demand": -4}), (3, {"demand": 4})]
|
| 309 |
+
edges = [
|
| 310 |
+
(1, 2, {"capacity": 3, "weight": 600000}),
|
| 311 |
+
(2, 1, {"capacity": 2, "weight": 0}),
|
| 312 |
+
(2, 3, {"capacity": 5, "weight": 714285}),
|
| 313 |
+
(3, 2, {"capacity": 2, "weight": 0}),
|
| 314 |
+
]
|
| 315 |
+
G = nx.DiGraph(edges)
|
| 316 |
+
G.add_nodes_from(nodes)
|
| 317 |
+
flowCost, H = nx.network_simplex(G)
|
| 318 |
+
soln = {1: {2: 0}, 2: {1: 0, 3: 4}, 3: {2: 0}}
|
| 319 |
+
assert flowCost == 2857140
|
| 320 |
+
assert nx.min_cost_flow_cost(G) == 2857140
|
| 321 |
+
assert H == soln
|
| 322 |
+
assert nx.min_cost_flow(G) == soln
|
| 323 |
+
assert nx.cost_of_flow(G, H) == 2857140
|
| 324 |
+
|
| 325 |
+
flowCost, H = nx.capacity_scaling(G)
|
| 326 |
+
assert flowCost == 2857140
|
| 327 |
+
assert H == soln
|
| 328 |
+
assert nx.cost_of_flow(G, H) == 2857140
|
| 329 |
+
|
| 330 |
+
def test_deadend(self):
|
| 331 |
+
"""Check if one-node cycles are handled properly. Taken from ticket
|
| 332 |
+
#2906 from @sshraven."""
|
| 333 |
+
G = nx.DiGraph()
|
| 334 |
+
|
| 335 |
+
G.add_nodes_from(range(5), demand=0)
|
| 336 |
+
G.nodes[4]["demand"] = -13
|
| 337 |
+
G.nodes[3]["demand"] = 13
|
| 338 |
+
|
| 339 |
+
G.add_edges_from([(0, 2), (0, 3), (2, 1)], capacity=20, weight=0.1)
|
| 340 |
+
pytest.raises(nx.NetworkXUnfeasible, nx.min_cost_flow, G)
|
| 341 |
+
|
| 342 |
+
def test_infinite_capacity_neg_digon(self):
|
| 343 |
+
"""An infinite capacity negative cost digon results in an unbounded
|
| 344 |
+
instance."""
|
| 345 |
+
nodes = [(1, {}), (2, {"demand": -4}), (3, {"demand": 4})]
|
| 346 |
+
edges = [
|
| 347 |
+
(1, 2, {"weight": -600}),
|
| 348 |
+
(2, 1, {"weight": 0}),
|
| 349 |
+
(2, 3, {"capacity": 5, "weight": 714285}),
|
| 350 |
+
(3, 2, {"capacity": 2, "weight": 0}),
|
| 351 |
+
]
|
| 352 |
+
G = nx.DiGraph(edges)
|
| 353 |
+
G.add_nodes_from(nodes)
|
| 354 |
+
pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G)
|
| 355 |
+
pytest.raises(nx.NetworkXUnbounded, nx.capacity_scaling, G)
|
| 356 |
+
|
| 357 |
+
def test_finite_capacity_neg_digon(self):
|
| 358 |
+
"""The digon should receive the maximum amount of flow it can handle.
|
| 359 |
+
Taken from ticket #749 by @chuongdo."""
|
| 360 |
+
G = nx.DiGraph()
|
| 361 |
+
G.add_edge("a", "b", capacity=1, weight=-1)
|
| 362 |
+
G.add_edge("b", "a", capacity=1, weight=-1)
|
| 363 |
+
min_cost = -2
|
| 364 |
+
assert nx.min_cost_flow_cost(G) == min_cost
|
| 365 |
+
|
| 366 |
+
flowCost, H = nx.capacity_scaling(G)
|
| 367 |
+
assert flowCost == -2
|
| 368 |
+
assert H == {"a": {"b": 1}, "b": {"a": 1}}
|
| 369 |
+
assert nx.cost_of_flow(G, H) == -2
|
| 370 |
+
|
| 371 |
+
def test_multidigraph(self):
|
| 372 |
+
"""Multidigraphs are acceptable."""
|
| 373 |
+
G = nx.MultiDiGraph()
|
| 374 |
+
G.add_weighted_edges_from([(1, 2, 1), (2, 3, 2)], weight="capacity")
|
| 375 |
+
flowCost, H = nx.network_simplex(G)
|
| 376 |
+
assert flowCost == 0
|
| 377 |
+
assert H == {1: {2: {0: 0}}, 2: {3: {0: 0}}, 3: {}}
|
| 378 |
+
|
| 379 |
+
flowCost, H = nx.capacity_scaling(G)
|
| 380 |
+
assert flowCost == 0
|
| 381 |
+
assert H == {1: {2: {0: 0}}, 2: {3: {0: 0}}, 3: {}}
|
| 382 |
+
|
| 383 |
+
def test_negative_selfloops(self):
|
| 384 |
+
"""Negative selfloops should cause an exception if uncapacitated and
|
| 385 |
+
always be saturated otherwise.
|
| 386 |
+
"""
|
| 387 |
+
G = nx.DiGraph()
|
| 388 |
+
G.add_edge(1, 1, weight=-1)
|
| 389 |
+
pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G)
|
| 390 |
+
pytest.raises(nx.NetworkXUnbounded, nx.capacity_scaling, G)
|
| 391 |
+
G[1][1]["capacity"] = 2
|
| 392 |
+
flowCost, H = nx.network_simplex(G)
|
| 393 |
+
assert flowCost == -2
|
| 394 |
+
assert H == {1: {1: 2}}
|
| 395 |
+
flowCost, H = nx.capacity_scaling(G)
|
| 396 |
+
assert flowCost == -2
|
| 397 |
+
assert H == {1: {1: 2}}
|
| 398 |
+
|
| 399 |
+
G = nx.MultiDiGraph()
|
| 400 |
+
G.add_edge(1, 1, "x", weight=-1)
|
| 401 |
+
G.add_edge(1, 1, "y", weight=1)
|
| 402 |
+
pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G)
|
| 403 |
+
pytest.raises(nx.NetworkXUnbounded, nx.capacity_scaling, G)
|
| 404 |
+
G[1][1]["x"]["capacity"] = 2
|
| 405 |
+
flowCost, H = nx.network_simplex(G)
|
| 406 |
+
assert flowCost == -2
|
| 407 |
+
assert H == {1: {1: {"x": 2, "y": 0}}}
|
| 408 |
+
flowCost, H = nx.capacity_scaling(G)
|
| 409 |
+
assert flowCost == -2
|
| 410 |
+
assert H == {1: {1: {"x": 2, "y": 0}}}
|
| 411 |
+
|
| 412 |
+
def test_bone_shaped(self):
|
| 413 |
+
# From #1283
|
| 414 |
+
G = nx.DiGraph()
|
| 415 |
+
G.add_node(0, demand=-4)
|
| 416 |
+
G.add_node(1, demand=2)
|
| 417 |
+
G.add_node(2, demand=2)
|
| 418 |
+
G.add_node(3, demand=4)
|
| 419 |
+
G.add_node(4, demand=-2)
|
| 420 |
+
G.add_node(5, demand=-2)
|
| 421 |
+
G.add_edge(0, 1, capacity=4)
|
| 422 |
+
G.add_edge(0, 2, capacity=4)
|
| 423 |
+
G.add_edge(4, 3, capacity=4)
|
| 424 |
+
G.add_edge(5, 3, capacity=4)
|
| 425 |
+
G.add_edge(0, 3, capacity=0)
|
| 426 |
+
flowCost, H = nx.network_simplex(G)
|
| 427 |
+
assert flowCost == 0
|
| 428 |
+
assert H == {0: {1: 2, 2: 2, 3: 0}, 1: {}, 2: {}, 3: {}, 4: {3: 2}, 5: {3: 2}}
|
| 429 |
+
flowCost, H = nx.capacity_scaling(G)
|
| 430 |
+
assert flowCost == 0
|
| 431 |
+
assert H == {0: {1: 2, 2: 2, 3: 0}, 1: {}, 2: {}, 3: {}, 4: {3: 2}, 5: {3: 2}}
|
| 432 |
+
|
| 433 |
+
def test_exceptions(self):
|
| 434 |
+
G = nx.Graph()
|
| 435 |
+
pytest.raises(nx.NetworkXNotImplemented, nx.network_simplex, G)
|
| 436 |
+
pytest.raises(nx.NetworkXNotImplemented, nx.capacity_scaling, G)
|
| 437 |
+
G = nx.MultiGraph()
|
| 438 |
+
pytest.raises(nx.NetworkXNotImplemented, nx.network_simplex, G)
|
| 439 |
+
pytest.raises(nx.NetworkXNotImplemented, nx.capacity_scaling, G)
|
| 440 |
+
G = nx.DiGraph()
|
| 441 |
+
pytest.raises(nx.NetworkXError, nx.network_simplex, G)
|
| 442 |
+
# pytest.raises(nx.NetworkXError, nx.capacity_scaling, G)
|
| 443 |
+
G.add_node(0, demand=float("inf"))
|
| 444 |
+
pytest.raises(nx.NetworkXError, nx.network_simplex, G)
|
| 445 |
+
pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G)
|
| 446 |
+
G.nodes[0]["demand"] = 0
|
| 447 |
+
G.add_node(1, demand=0)
|
| 448 |
+
G.add_edge(0, 1, weight=-float("inf"))
|
| 449 |
+
pytest.raises(nx.NetworkXError, nx.network_simplex, G)
|
| 450 |
+
pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G)
|
| 451 |
+
G[0][1]["weight"] = 0
|
| 452 |
+
G.add_edge(0, 0, weight=float("inf"))
|
| 453 |
+
pytest.raises(nx.NetworkXError, nx.network_simplex, G)
|
| 454 |
+
# pytest.raises(nx.NetworkXError, nx.capacity_scaling, G)
|
| 455 |
+
G[0][0]["weight"] = 0
|
| 456 |
+
G[0][1]["capacity"] = -1
|
| 457 |
+
pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
|
| 458 |
+
# pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G)
|
| 459 |
+
G[0][1]["capacity"] = 0
|
| 460 |
+
G[0][0]["capacity"] = -1
|
| 461 |
+
pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G)
|
| 462 |
+
# pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G)
|
| 463 |
+
|
| 464 |
+
def test_large(self):
|
| 465 |
+
fname = (
|
| 466 |
+
importlib.resources.files("networkx.algorithms.flow.tests")
|
| 467 |
+
/ "netgen-2.gpickle.bz2"
|
| 468 |
+
)
|
| 469 |
+
with bz2.BZ2File(fname, "rb") as f:
|
| 470 |
+
G = pickle.load(f)
|
| 471 |
+
flowCost, flowDict = nx.network_simplex(G)
|
| 472 |
+
assert 6749969302 == flowCost
|
| 473 |
+
assert 6749969302 == nx.cost_of_flow(G, flowDict)
|
| 474 |
+
flowCost, flowDict = nx.capacity_scaling(G)
|
| 475 |
+
assert 6749969302 == flowCost
|
| 476 |
+
assert 6749969302 == nx.cost_of_flow(G, flowDict)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/graph_hashing.py
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Functions for hashing graphs to strings.
|
| 3 |
+
Isomorphic graphs should be assigned identical hashes.
|
| 4 |
+
For now, only Weisfeiler-Lehman hashing is implemented.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from collections import Counter, defaultdict
|
| 8 |
+
from hashlib import blake2b
|
| 9 |
+
|
| 10 |
+
import networkx as nx
|
| 11 |
+
|
| 12 |
+
__all__ = ["weisfeiler_lehman_graph_hash", "weisfeiler_lehman_subgraph_hashes"]
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def _hash_label(label, digest_size):
|
| 16 |
+
return blake2b(label.encode("ascii"), digest_size=digest_size).hexdigest()
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def _init_node_labels(G, edge_attr, node_attr):
|
| 20 |
+
if node_attr:
|
| 21 |
+
return {u: str(dd[node_attr]) for u, dd in G.nodes(data=True)}
|
| 22 |
+
elif edge_attr:
|
| 23 |
+
return {u: "" for u in G}
|
| 24 |
+
else:
|
| 25 |
+
return {u: str(deg) for u, deg in G.degree()}
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _neighborhood_aggregate(G, node, node_labels, edge_attr=None):
|
| 29 |
+
"""
|
| 30 |
+
Compute new labels for given node by aggregating
|
| 31 |
+
the labels of each node's neighbors.
|
| 32 |
+
"""
|
| 33 |
+
label_list = []
|
| 34 |
+
for nbr in G.neighbors(node):
|
| 35 |
+
prefix = "" if edge_attr is None else str(G[node][nbr][edge_attr])
|
| 36 |
+
label_list.append(prefix + node_labels[nbr])
|
| 37 |
+
return node_labels[node] + "".join(sorted(label_list))
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@nx._dispatch(edge_attrs={"edge_attr": None}, node_attrs="node_attr")
|
| 41 |
+
def weisfeiler_lehman_graph_hash(
|
| 42 |
+
G, edge_attr=None, node_attr=None, iterations=3, digest_size=16
|
| 43 |
+
):
|
| 44 |
+
"""Return Weisfeiler Lehman (WL) graph hash.
|
| 45 |
+
|
| 46 |
+
The function iteratively aggregates and hashes neighbourhoods of each node.
|
| 47 |
+
After each node's neighbors are hashed to obtain updated node labels,
|
| 48 |
+
a hashed histogram of resulting labels is returned as the final hash.
|
| 49 |
+
|
| 50 |
+
Hashes are identical for isomorphic graphs and strong guarantees that
|
| 51 |
+
non-isomorphic graphs will get different hashes. See [1]_ for details.
|
| 52 |
+
|
| 53 |
+
If no node or edge attributes are provided, the degree of each node
|
| 54 |
+
is used as its initial label.
|
| 55 |
+
Otherwise, node and/or edge labels are used to compute the hash.
|
| 56 |
+
|
| 57 |
+
Parameters
|
| 58 |
+
----------
|
| 59 |
+
G: graph
|
| 60 |
+
The graph to be hashed.
|
| 61 |
+
Can have node and/or edge attributes. Can also have no attributes.
|
| 62 |
+
edge_attr: string, default=None
|
| 63 |
+
The key in edge attribute dictionary to be used for hashing.
|
| 64 |
+
If None, edge labels are ignored.
|
| 65 |
+
node_attr: string, default=None
|
| 66 |
+
The key in node attribute dictionary to be used for hashing.
|
| 67 |
+
If None, and no edge_attr given, use the degrees of the nodes as labels.
|
| 68 |
+
iterations: int, default=3
|
| 69 |
+
Number of neighbor aggregations to perform.
|
| 70 |
+
Should be larger for larger graphs.
|
| 71 |
+
digest_size: int, default=16
|
| 72 |
+
Size (in bits) of blake2b hash digest to use for hashing node labels.
|
| 73 |
+
|
| 74 |
+
Returns
|
| 75 |
+
-------
|
| 76 |
+
h : string
|
| 77 |
+
Hexadecimal string corresponding to hash of the input graph.
|
| 78 |
+
|
| 79 |
+
Examples
|
| 80 |
+
--------
|
| 81 |
+
Two graphs with edge attributes that are isomorphic, except for
|
| 82 |
+
differences in the edge labels.
|
| 83 |
+
|
| 84 |
+
>>> G1 = nx.Graph()
|
| 85 |
+
>>> G1.add_edges_from(
|
| 86 |
+
... [
|
| 87 |
+
... (1, 2, {"label": "A"}),
|
| 88 |
+
... (2, 3, {"label": "A"}),
|
| 89 |
+
... (3, 1, {"label": "A"}),
|
| 90 |
+
... (1, 4, {"label": "B"}),
|
| 91 |
+
... ]
|
| 92 |
+
... )
|
| 93 |
+
>>> G2 = nx.Graph()
|
| 94 |
+
>>> G2.add_edges_from(
|
| 95 |
+
... [
|
| 96 |
+
... (5, 6, {"label": "B"}),
|
| 97 |
+
... (6, 7, {"label": "A"}),
|
| 98 |
+
... (7, 5, {"label": "A"}),
|
| 99 |
+
... (7, 8, {"label": "A"}),
|
| 100 |
+
... ]
|
| 101 |
+
... )
|
| 102 |
+
|
| 103 |
+
Omitting the `edge_attr` option, results in identical hashes.
|
| 104 |
+
|
| 105 |
+
>>> nx.weisfeiler_lehman_graph_hash(G1)
|
| 106 |
+
'7bc4dde9a09d0b94c5097b219891d81a'
|
| 107 |
+
>>> nx.weisfeiler_lehman_graph_hash(G2)
|
| 108 |
+
'7bc4dde9a09d0b94c5097b219891d81a'
|
| 109 |
+
|
| 110 |
+
With edge labels, the graphs are no longer assigned
|
| 111 |
+
the same hash digest.
|
| 112 |
+
|
| 113 |
+
>>> nx.weisfeiler_lehman_graph_hash(G1, edge_attr="label")
|
| 114 |
+
'c653d85538bcf041d88c011f4f905f10'
|
| 115 |
+
>>> nx.weisfeiler_lehman_graph_hash(G2, edge_attr="label")
|
| 116 |
+
'3dcd84af1ca855d0eff3c978d88e7ec7'
|
| 117 |
+
|
| 118 |
+
Notes
|
| 119 |
+
-----
|
| 120 |
+
To return the WL hashes of each subgraph of a graph, use
|
| 121 |
+
`weisfeiler_lehman_subgraph_hashes`
|
| 122 |
+
|
| 123 |
+
Similarity between hashes does not imply similarity between graphs.
|
| 124 |
+
|
| 125 |
+
References
|
| 126 |
+
----------
|
| 127 |
+
.. [1] Shervashidze, Nino, Pascal Schweitzer, Erik Jan Van Leeuwen,
|
| 128 |
+
Kurt Mehlhorn, and Karsten M. Borgwardt. Weisfeiler Lehman
|
| 129 |
+
Graph Kernels. Journal of Machine Learning Research. 2011.
|
| 130 |
+
http://www.jmlr.org/papers/volume12/shervashidze11a/shervashidze11a.pdf
|
| 131 |
+
|
| 132 |
+
See also
|
| 133 |
+
--------
|
| 134 |
+
weisfeiler_lehman_subgraph_hashes
|
| 135 |
+
"""
|
| 136 |
+
|
| 137 |
+
def weisfeiler_lehman_step(G, labels, edge_attr=None):
|
| 138 |
+
"""
|
| 139 |
+
Apply neighborhood aggregation to each node
|
| 140 |
+
in the graph.
|
| 141 |
+
Computes a dictionary with labels for each node.
|
| 142 |
+
"""
|
| 143 |
+
new_labels = {}
|
| 144 |
+
for node in G.nodes():
|
| 145 |
+
label = _neighborhood_aggregate(G, node, labels, edge_attr=edge_attr)
|
| 146 |
+
new_labels[node] = _hash_label(label, digest_size)
|
| 147 |
+
return new_labels
|
| 148 |
+
|
| 149 |
+
# set initial node labels
|
| 150 |
+
node_labels = _init_node_labels(G, edge_attr, node_attr)
|
| 151 |
+
|
| 152 |
+
subgraph_hash_counts = []
|
| 153 |
+
for _ in range(iterations):
|
| 154 |
+
node_labels = weisfeiler_lehman_step(G, node_labels, edge_attr=edge_attr)
|
| 155 |
+
counter = Counter(node_labels.values())
|
| 156 |
+
# sort the counter, extend total counts
|
| 157 |
+
subgraph_hash_counts.extend(sorted(counter.items(), key=lambda x: x[0]))
|
| 158 |
+
|
| 159 |
+
# hash the final counter
|
| 160 |
+
return _hash_label(str(tuple(subgraph_hash_counts)), digest_size)
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
@nx._dispatch(edge_attrs={"edge_attr": None}, node_attrs="node_attr")
|
| 164 |
+
def weisfeiler_lehman_subgraph_hashes(
|
| 165 |
+
G, edge_attr=None, node_attr=None, iterations=3, digest_size=16
|
| 166 |
+
):
|
| 167 |
+
"""
|
| 168 |
+
Return a dictionary of subgraph hashes by node.
|
| 169 |
+
|
| 170 |
+
Dictionary keys are nodes in `G`, and values are a list of hashes.
|
| 171 |
+
Each hash corresponds to a subgraph rooted at a given node u in `G`.
|
| 172 |
+
Lists of subgraph hashes are sorted in increasing order of depth from
|
| 173 |
+
their root node, with the hash at index i corresponding to a subgraph
|
| 174 |
+
of nodes at most i edges distance from u. Thus, each list will contain
|
| 175 |
+
``iterations + 1`` elements - a hash for a subgraph at each depth, and
|
| 176 |
+
additionally a hash of the initial node label (or equivalently a
|
| 177 |
+
subgraph of depth 0)
|
| 178 |
+
|
| 179 |
+
The function iteratively aggregates and hashes neighbourhoods of each node.
|
| 180 |
+
This is achieved for each step by replacing for each node its label from
|
| 181 |
+
the previous iteration with its hashed 1-hop neighborhood aggregate.
|
| 182 |
+
The new node label is then appended to a list of node labels for each
|
| 183 |
+
node.
|
| 184 |
+
|
| 185 |
+
To aggregate neighborhoods at each step for a node $n$, all labels of
|
| 186 |
+
nodes adjacent to $n$ are concatenated. If the `edge_attr` parameter is set,
|
| 187 |
+
labels for each neighboring node are prefixed with the value of this attribute
|
| 188 |
+
along the connecting edge from this neighbor to node $n$. The resulting string
|
| 189 |
+
is then hashed to compress this information into a fixed digest size.
|
| 190 |
+
|
| 191 |
+
Thus, at the $i$-th iteration, nodes within $i$ hops influence any given
|
| 192 |
+
hashed node label. We can therefore say that at depth $i$ for node $n$
|
| 193 |
+
we have a hash for a subgraph induced by the $2i$-hop neighborhood of $n$.
|
| 194 |
+
|
| 195 |
+
The output can be used to to create general Weisfeiler-Lehman graph kernels,
|
| 196 |
+
or generate features for graphs or nodes - for example to generate 'words' in
|
| 197 |
+
a graph as seen in the 'graph2vec' algorithm.
|
| 198 |
+
See [1]_ & [2]_ respectively for details.
|
| 199 |
+
|
| 200 |
+
Hashes are identical for isomorphic subgraphs and there exist strong
|
| 201 |
+
guarantees that non-isomorphic graphs will get different hashes.
|
| 202 |
+
See [1]_ for details.
|
| 203 |
+
|
| 204 |
+
If no node or edge attributes are provided, the degree of each node
|
| 205 |
+
is used as its initial label.
|
| 206 |
+
Otherwise, node and/or edge labels are used to compute the hash.
|
| 207 |
+
|
| 208 |
+
Parameters
|
| 209 |
+
----------
|
| 210 |
+
G: graph
|
| 211 |
+
The graph to be hashed.
|
| 212 |
+
Can have node and/or edge attributes. Can also have no attributes.
|
| 213 |
+
edge_attr: string, default=None
|
| 214 |
+
The key in edge attribute dictionary to be used for hashing.
|
| 215 |
+
If None, edge labels are ignored.
|
| 216 |
+
node_attr: string, default=None
|
| 217 |
+
The key in node attribute dictionary to be used for hashing.
|
| 218 |
+
If None, and no edge_attr given, use the degrees of the nodes as labels.
|
| 219 |
+
iterations: int, default=3
|
| 220 |
+
Number of neighbor aggregations to perform.
|
| 221 |
+
Should be larger for larger graphs.
|
| 222 |
+
digest_size: int, default=16
|
| 223 |
+
Size (in bits) of blake2b hash digest to use for hashing node labels.
|
| 224 |
+
The default size is 16 bits
|
| 225 |
+
|
| 226 |
+
Returns
|
| 227 |
+
-------
|
| 228 |
+
node_subgraph_hashes : dict
|
| 229 |
+
A dictionary with each key given by a node in G, and each value given
|
| 230 |
+
by the subgraph hashes in order of depth from the key node.
|
| 231 |
+
|
| 232 |
+
Examples
|
| 233 |
+
--------
|
| 234 |
+
Finding similar nodes in different graphs:
|
| 235 |
+
|
| 236 |
+
>>> G1 = nx.Graph()
|
| 237 |
+
>>> G1.add_edges_from([
|
| 238 |
+
... (1, 2), (2, 3), (2, 4), (3, 5), (4, 6), (5, 7), (6, 7)
|
| 239 |
+
... ])
|
| 240 |
+
>>> G2 = nx.Graph()
|
| 241 |
+
>>> G2.add_edges_from([
|
| 242 |
+
... (1, 3), (2, 3), (1, 6), (1, 5), (4, 6)
|
| 243 |
+
... ])
|
| 244 |
+
>>> g1_hashes = nx.weisfeiler_lehman_subgraph_hashes(G1, iterations=3, digest_size=8)
|
| 245 |
+
>>> g2_hashes = nx.weisfeiler_lehman_subgraph_hashes(G2, iterations=3, digest_size=8)
|
| 246 |
+
|
| 247 |
+
Even though G1 and G2 are not isomorphic (they have different numbers of edges),
|
| 248 |
+
the hash sequence of depth 3 for node 1 in G1 and node 5 in G2 are similar:
|
| 249 |
+
|
| 250 |
+
>>> g1_hashes[1]
|
| 251 |
+
['a93b64973cfc8897', 'db1b43ae35a1878f', '57872a7d2059c1c0']
|
| 252 |
+
>>> g2_hashes[5]
|
| 253 |
+
['a93b64973cfc8897', 'db1b43ae35a1878f', '1716d2a4012fa4bc']
|
| 254 |
+
|
| 255 |
+
The first 2 WL subgraph hashes match. From this we can conclude that it's very
|
| 256 |
+
likely the neighborhood of 4 hops around these nodes are isomorphic: each
|
| 257 |
+
iteration aggregates 1-hop neighbourhoods meaning hashes at depth $n$ are influenced
|
| 258 |
+
by every node within $2n$ hops.
|
| 259 |
+
|
| 260 |
+
However the neighborhood of 6 hops is no longer isomorphic since their 3rd hash does
|
| 261 |
+
not match.
|
| 262 |
+
|
| 263 |
+
These nodes may be candidates to be classified together since their local topology
|
| 264 |
+
is similar.
|
| 265 |
+
|
| 266 |
+
Notes
|
| 267 |
+
-----
|
| 268 |
+
To hash the full graph when subgraph hashes are not needed, use
|
| 269 |
+
`weisfeiler_lehman_graph_hash` for efficiency.
|
| 270 |
+
|
| 271 |
+
Similarity between hashes does not imply similarity between graphs.
|
| 272 |
+
|
| 273 |
+
References
|
| 274 |
+
----------
|
| 275 |
+
.. [1] Shervashidze, Nino, Pascal Schweitzer, Erik Jan Van Leeuwen,
|
| 276 |
+
Kurt Mehlhorn, and Karsten M. Borgwardt. Weisfeiler Lehman
|
| 277 |
+
Graph Kernels. Journal of Machine Learning Research. 2011.
|
| 278 |
+
http://www.jmlr.org/papers/volume12/shervashidze11a/shervashidze11a.pdf
|
| 279 |
+
.. [2] Annamalai Narayanan, Mahinthan Chandramohan, Rajasekar Venkatesan,
|
| 280 |
+
Lihui Chen, Yang Liu and Shantanu Jaiswa. graph2vec: Learning
|
| 281 |
+
Distributed Representations of Graphs. arXiv. 2017
|
| 282 |
+
https://arxiv.org/pdf/1707.05005.pdf
|
| 283 |
+
|
| 284 |
+
See also
|
| 285 |
+
--------
|
| 286 |
+
weisfeiler_lehman_graph_hash
|
| 287 |
+
"""
|
| 288 |
+
|
| 289 |
+
def weisfeiler_lehman_step(G, labels, node_subgraph_hashes, edge_attr=None):
|
| 290 |
+
"""
|
| 291 |
+
Apply neighborhood aggregation to each node
|
| 292 |
+
in the graph.
|
| 293 |
+
Computes a dictionary with labels for each node.
|
| 294 |
+
Appends the new hashed label to the dictionary of subgraph hashes
|
| 295 |
+
originating from and indexed by each node in G
|
| 296 |
+
"""
|
| 297 |
+
new_labels = {}
|
| 298 |
+
for node in G.nodes():
|
| 299 |
+
label = _neighborhood_aggregate(G, node, labels, edge_attr=edge_attr)
|
| 300 |
+
hashed_label = _hash_label(label, digest_size)
|
| 301 |
+
new_labels[node] = hashed_label
|
| 302 |
+
node_subgraph_hashes[node].append(hashed_label)
|
| 303 |
+
return new_labels
|
| 304 |
+
|
| 305 |
+
node_labels = _init_node_labels(G, edge_attr, node_attr)
|
| 306 |
+
|
| 307 |
+
node_subgraph_hashes = defaultdict(list)
|
| 308 |
+
for _ in range(iterations):
|
| 309 |
+
node_labels = weisfeiler_lehman_step(
|
| 310 |
+
G, node_labels, node_subgraph_hashes, edge_attr
|
| 311 |
+
)
|
| 312 |
+
|
| 313 |
+
return dict(node_subgraph_hashes)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/graphical.py
ADDED
|
@@ -0,0 +1,483 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Test sequences for graphiness.
|
| 2 |
+
"""
|
| 3 |
+
import heapq
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
|
| 7 |
+
__all__ = [
|
| 8 |
+
"is_graphical",
|
| 9 |
+
"is_multigraphical",
|
| 10 |
+
"is_pseudographical",
|
| 11 |
+
"is_digraphical",
|
| 12 |
+
"is_valid_degree_sequence_erdos_gallai",
|
| 13 |
+
"is_valid_degree_sequence_havel_hakimi",
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@nx._dispatch(graphs=None)
|
| 18 |
+
def is_graphical(sequence, method="eg"):
|
| 19 |
+
"""Returns True if sequence is a valid degree sequence.
|
| 20 |
+
|
| 21 |
+
A degree sequence is valid if some graph can realize it.
|
| 22 |
+
|
| 23 |
+
Parameters
|
| 24 |
+
----------
|
| 25 |
+
sequence : list or iterable container
|
| 26 |
+
A sequence of integer node degrees
|
| 27 |
+
|
| 28 |
+
method : "eg" | "hh" (default: 'eg')
|
| 29 |
+
The method used to validate the degree sequence.
|
| 30 |
+
"eg" corresponds to the Erdős-Gallai algorithm
|
| 31 |
+
[EG1960]_, [choudum1986]_, and
|
| 32 |
+
"hh" to the Havel-Hakimi algorithm
|
| 33 |
+
[havel1955]_, [hakimi1962]_, [CL1996]_.
|
| 34 |
+
|
| 35 |
+
Returns
|
| 36 |
+
-------
|
| 37 |
+
valid : bool
|
| 38 |
+
True if the sequence is a valid degree sequence and False if not.
|
| 39 |
+
|
| 40 |
+
Examples
|
| 41 |
+
--------
|
| 42 |
+
>>> G = nx.path_graph(4)
|
| 43 |
+
>>> sequence = (d for n, d in G.degree())
|
| 44 |
+
>>> nx.is_graphical(sequence)
|
| 45 |
+
True
|
| 46 |
+
|
| 47 |
+
To test a non-graphical sequence:
|
| 48 |
+
>>> sequence_list = [d for n, d in G.degree()]
|
| 49 |
+
>>> sequence_list[-1] += 1
|
| 50 |
+
>>> nx.is_graphical(sequence_list)
|
| 51 |
+
False
|
| 52 |
+
|
| 53 |
+
References
|
| 54 |
+
----------
|
| 55 |
+
.. [EG1960] Erdős and Gallai, Mat. Lapok 11 264, 1960.
|
| 56 |
+
.. [choudum1986] S.A. Choudum. "A simple proof of the Erdős-Gallai theorem on
|
| 57 |
+
graph sequences." Bulletin of the Australian Mathematical Society, 33,
|
| 58 |
+
pp 67-70, 1986. https://doi.org/10.1017/S0004972700002872
|
| 59 |
+
.. [havel1955] Havel, V. "A Remark on the Existence of Finite Graphs"
|
| 60 |
+
Casopis Pest. Mat. 80, 477-480, 1955.
|
| 61 |
+
.. [hakimi1962] Hakimi, S. "On the Realizability of a Set of Integers as
|
| 62 |
+
Degrees of the Vertices of a Graph." SIAM J. Appl. Math. 10, 496-506, 1962.
|
| 63 |
+
.. [CL1996] G. Chartrand and L. Lesniak, "Graphs and Digraphs",
|
| 64 |
+
Chapman and Hall/CRC, 1996.
|
| 65 |
+
"""
|
| 66 |
+
if method == "eg":
|
| 67 |
+
valid = is_valid_degree_sequence_erdos_gallai(list(sequence))
|
| 68 |
+
elif method == "hh":
|
| 69 |
+
valid = is_valid_degree_sequence_havel_hakimi(list(sequence))
|
| 70 |
+
else:
|
| 71 |
+
msg = "`method` must be 'eg' or 'hh'"
|
| 72 |
+
raise nx.NetworkXException(msg)
|
| 73 |
+
return valid
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def _basic_graphical_tests(deg_sequence):
|
| 77 |
+
# Sort and perform some simple tests on the sequence
|
| 78 |
+
deg_sequence = nx.utils.make_list_of_ints(deg_sequence)
|
| 79 |
+
p = len(deg_sequence)
|
| 80 |
+
num_degs = [0] * p
|
| 81 |
+
dmax, dmin, dsum, n = 0, p, 0, 0
|
| 82 |
+
for d in deg_sequence:
|
| 83 |
+
# Reject if degree is negative or larger than the sequence length
|
| 84 |
+
if d < 0 or d >= p:
|
| 85 |
+
raise nx.NetworkXUnfeasible
|
| 86 |
+
# Process only the non-zero integers
|
| 87 |
+
elif d > 0:
|
| 88 |
+
dmax, dmin, dsum, n = max(dmax, d), min(dmin, d), dsum + d, n + 1
|
| 89 |
+
num_degs[d] += 1
|
| 90 |
+
# Reject sequence if it has odd sum or is oversaturated
|
| 91 |
+
if dsum % 2 or dsum > n * (n - 1):
|
| 92 |
+
raise nx.NetworkXUnfeasible
|
| 93 |
+
return dmax, dmin, dsum, n, num_degs
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
@nx._dispatch(graphs=None)
|
| 97 |
+
def is_valid_degree_sequence_havel_hakimi(deg_sequence):
|
| 98 |
+
r"""Returns True if deg_sequence can be realized by a simple graph.
|
| 99 |
+
|
| 100 |
+
The validation proceeds using the Havel-Hakimi theorem
|
| 101 |
+
[havel1955]_, [hakimi1962]_, [CL1996]_.
|
| 102 |
+
Worst-case run time is $O(s)$ where $s$ is the sum of the sequence.
|
| 103 |
+
|
| 104 |
+
Parameters
|
| 105 |
+
----------
|
| 106 |
+
deg_sequence : list
|
| 107 |
+
A list of integers where each element specifies the degree of a node
|
| 108 |
+
in a graph.
|
| 109 |
+
|
| 110 |
+
Returns
|
| 111 |
+
-------
|
| 112 |
+
valid : bool
|
| 113 |
+
True if deg_sequence is graphical and False if not.
|
| 114 |
+
|
| 115 |
+
Examples
|
| 116 |
+
--------
|
| 117 |
+
>>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (3, 4), (4, 2), (5, 1), (5, 4)])
|
| 118 |
+
>>> sequence = (d for _, d in G.degree())
|
| 119 |
+
>>> nx.is_valid_degree_sequence_havel_hakimi(sequence)
|
| 120 |
+
True
|
| 121 |
+
|
| 122 |
+
To test a non-valid sequence:
|
| 123 |
+
>>> sequence_list = [d for _, d in G.degree()]
|
| 124 |
+
>>> sequence_list[-1] += 1
|
| 125 |
+
>>> nx.is_valid_degree_sequence_havel_hakimi(sequence_list)
|
| 126 |
+
False
|
| 127 |
+
|
| 128 |
+
Notes
|
| 129 |
+
-----
|
| 130 |
+
The ZZ condition says that for the sequence d if
|
| 131 |
+
|
| 132 |
+
.. math::
|
| 133 |
+
|d| >= \frac{(\max(d) + \min(d) + 1)^2}{4*\min(d)}
|
| 134 |
+
|
| 135 |
+
then d is graphical. This was shown in Theorem 6 in [1]_.
|
| 136 |
+
|
| 137 |
+
References
|
| 138 |
+
----------
|
| 139 |
+
.. [1] I.E. Zverovich and V.E. Zverovich. "Contributions to the theory
|
| 140 |
+
of graphic sequences", Discrete Mathematics, 105, pp. 292-303 (1992).
|
| 141 |
+
.. [havel1955] Havel, V. "A Remark on the Existence of Finite Graphs"
|
| 142 |
+
Casopis Pest. Mat. 80, 477-480, 1955.
|
| 143 |
+
.. [hakimi1962] Hakimi, S. "On the Realizability of a Set of Integers as
|
| 144 |
+
Degrees of the Vertices of a Graph." SIAM J. Appl. Math. 10, 496-506, 1962.
|
| 145 |
+
.. [CL1996] G. Chartrand and L. Lesniak, "Graphs and Digraphs",
|
| 146 |
+
Chapman and Hall/CRC, 1996.
|
| 147 |
+
"""
|
| 148 |
+
try:
|
| 149 |
+
dmax, dmin, dsum, n, num_degs = _basic_graphical_tests(deg_sequence)
|
| 150 |
+
except nx.NetworkXUnfeasible:
|
| 151 |
+
return False
|
| 152 |
+
# Accept if sequence has no non-zero degrees or passes the ZZ condition
|
| 153 |
+
if n == 0 or 4 * dmin * n >= (dmax + dmin + 1) * (dmax + dmin + 1):
|
| 154 |
+
return True
|
| 155 |
+
|
| 156 |
+
modstubs = [0] * (dmax + 1)
|
| 157 |
+
# Successively reduce degree sequence by removing the maximum degree
|
| 158 |
+
while n > 0:
|
| 159 |
+
# Retrieve the maximum degree in the sequence
|
| 160 |
+
while num_degs[dmax] == 0:
|
| 161 |
+
dmax -= 1
|
| 162 |
+
# If there are not enough stubs to connect to, then the sequence is
|
| 163 |
+
# not graphical
|
| 164 |
+
if dmax > n - 1:
|
| 165 |
+
return False
|
| 166 |
+
|
| 167 |
+
# Remove largest stub in list
|
| 168 |
+
num_degs[dmax], n = num_degs[dmax] - 1, n - 1
|
| 169 |
+
# Reduce the next dmax largest stubs
|
| 170 |
+
mslen = 0
|
| 171 |
+
k = dmax
|
| 172 |
+
for i in range(dmax):
|
| 173 |
+
while num_degs[k] == 0:
|
| 174 |
+
k -= 1
|
| 175 |
+
num_degs[k], n = num_degs[k] - 1, n - 1
|
| 176 |
+
if k > 1:
|
| 177 |
+
modstubs[mslen] = k - 1
|
| 178 |
+
mslen += 1
|
| 179 |
+
# Add back to the list any non-zero stubs that were removed
|
| 180 |
+
for i in range(mslen):
|
| 181 |
+
stub = modstubs[i]
|
| 182 |
+
num_degs[stub], n = num_degs[stub] + 1, n + 1
|
| 183 |
+
return True
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
@nx._dispatch(graphs=None)
|
| 187 |
+
def is_valid_degree_sequence_erdos_gallai(deg_sequence):
|
| 188 |
+
r"""Returns True if deg_sequence can be realized by a simple graph.
|
| 189 |
+
|
| 190 |
+
The validation is done using the Erdős-Gallai theorem [EG1960]_.
|
| 191 |
+
|
| 192 |
+
Parameters
|
| 193 |
+
----------
|
| 194 |
+
deg_sequence : list
|
| 195 |
+
A list of integers
|
| 196 |
+
|
| 197 |
+
Returns
|
| 198 |
+
-------
|
| 199 |
+
valid : bool
|
| 200 |
+
True if deg_sequence is graphical and False if not.
|
| 201 |
+
|
| 202 |
+
Examples
|
| 203 |
+
--------
|
| 204 |
+
>>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (3, 4), (4, 2), (5, 1), (5, 4)])
|
| 205 |
+
>>> sequence = (d for _, d in G.degree())
|
| 206 |
+
>>> nx.is_valid_degree_sequence_erdos_gallai(sequence)
|
| 207 |
+
True
|
| 208 |
+
|
| 209 |
+
To test a non-valid sequence:
|
| 210 |
+
>>> sequence_list = [d for _, d in G.degree()]
|
| 211 |
+
>>> sequence_list[-1] += 1
|
| 212 |
+
>>> nx.is_valid_degree_sequence_erdos_gallai(sequence_list)
|
| 213 |
+
False
|
| 214 |
+
|
| 215 |
+
Notes
|
| 216 |
+
-----
|
| 217 |
+
|
| 218 |
+
This implementation uses an equivalent form of the Erdős-Gallai criterion.
|
| 219 |
+
Worst-case run time is $O(n)$ where $n$ is the length of the sequence.
|
| 220 |
+
|
| 221 |
+
Specifically, a sequence d is graphical if and only if the
|
| 222 |
+
sum of the sequence is even and for all strong indices k in the sequence,
|
| 223 |
+
|
| 224 |
+
.. math::
|
| 225 |
+
|
| 226 |
+
\sum_{i=1}^{k} d_i \leq k(k-1) + \sum_{j=k+1}^{n} \min(d_i,k)
|
| 227 |
+
= k(n-1) - ( k \sum_{j=0}^{k-1} n_j - \sum_{j=0}^{k-1} j n_j )
|
| 228 |
+
|
| 229 |
+
A strong index k is any index where d_k >= k and the value n_j is the
|
| 230 |
+
number of occurrences of j in d. The maximal strong index is called the
|
| 231 |
+
Durfee index.
|
| 232 |
+
|
| 233 |
+
This particular rearrangement comes from the proof of Theorem 3 in [2]_.
|
| 234 |
+
|
| 235 |
+
The ZZ condition says that for the sequence d if
|
| 236 |
+
|
| 237 |
+
.. math::
|
| 238 |
+
|d| >= \frac{(\max(d) + \min(d) + 1)^2}{4*\min(d)}
|
| 239 |
+
|
| 240 |
+
then d is graphical. This was shown in Theorem 6 in [2]_.
|
| 241 |
+
|
| 242 |
+
References
|
| 243 |
+
----------
|
| 244 |
+
.. [1] A. Tripathi and S. Vijay. "A note on a theorem of Erdős & Gallai",
|
| 245 |
+
Discrete Mathematics, 265, pp. 417-420 (2003).
|
| 246 |
+
.. [2] I.E. Zverovich and V.E. Zverovich. "Contributions to the theory
|
| 247 |
+
of graphic sequences", Discrete Mathematics, 105, pp. 292-303 (1992).
|
| 248 |
+
.. [EG1960] Erdős and Gallai, Mat. Lapok 11 264, 1960.
|
| 249 |
+
"""
|
| 250 |
+
try:
|
| 251 |
+
dmax, dmin, dsum, n, num_degs = _basic_graphical_tests(deg_sequence)
|
| 252 |
+
except nx.NetworkXUnfeasible:
|
| 253 |
+
return False
|
| 254 |
+
# Accept if sequence has no non-zero degrees or passes the ZZ condition
|
| 255 |
+
if n == 0 or 4 * dmin * n >= (dmax + dmin + 1) * (dmax + dmin + 1):
|
| 256 |
+
return True
|
| 257 |
+
|
| 258 |
+
# Perform the EG checks using the reformulation of Zverovich and Zverovich
|
| 259 |
+
k, sum_deg, sum_nj, sum_jnj = 0, 0, 0, 0
|
| 260 |
+
for dk in range(dmax, dmin - 1, -1):
|
| 261 |
+
if dk < k + 1: # Check if already past Durfee index
|
| 262 |
+
return True
|
| 263 |
+
if num_degs[dk] > 0:
|
| 264 |
+
run_size = num_degs[dk] # Process a run of identical-valued degrees
|
| 265 |
+
if dk < k + run_size: # Check if end of run is past Durfee index
|
| 266 |
+
run_size = dk - k # Adjust back to Durfee index
|
| 267 |
+
sum_deg += run_size * dk
|
| 268 |
+
for v in range(run_size):
|
| 269 |
+
sum_nj += num_degs[k + v]
|
| 270 |
+
sum_jnj += (k + v) * num_degs[k + v]
|
| 271 |
+
k += run_size
|
| 272 |
+
if sum_deg > k * (n - 1) - k * sum_nj + sum_jnj:
|
| 273 |
+
return False
|
| 274 |
+
return True
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
@nx._dispatch(graphs=None)
|
| 278 |
+
def is_multigraphical(sequence):
|
| 279 |
+
"""Returns True if some multigraph can realize the sequence.
|
| 280 |
+
|
| 281 |
+
Parameters
|
| 282 |
+
----------
|
| 283 |
+
sequence : list
|
| 284 |
+
A list of integers
|
| 285 |
+
|
| 286 |
+
Returns
|
| 287 |
+
-------
|
| 288 |
+
valid : bool
|
| 289 |
+
True if deg_sequence is a multigraphic degree sequence and False if not.
|
| 290 |
+
|
| 291 |
+
Examples
|
| 292 |
+
--------
|
| 293 |
+
>>> G = nx.MultiGraph([(1, 2), (1, 3), (2, 3), (3, 4), (4, 2), (5, 1), (5, 4)])
|
| 294 |
+
>>> sequence = (d for _, d in G.degree())
|
| 295 |
+
>>> nx.is_multigraphical(sequence)
|
| 296 |
+
True
|
| 297 |
+
|
| 298 |
+
To test a non-multigraphical sequence:
|
| 299 |
+
>>> sequence_list = [d for _, d in G.degree()]
|
| 300 |
+
>>> sequence_list[-1] += 1
|
| 301 |
+
>>> nx.is_multigraphical(sequence_list)
|
| 302 |
+
False
|
| 303 |
+
|
| 304 |
+
Notes
|
| 305 |
+
-----
|
| 306 |
+
The worst-case run time is $O(n)$ where $n$ is the length of the sequence.
|
| 307 |
+
|
| 308 |
+
References
|
| 309 |
+
----------
|
| 310 |
+
.. [1] S. L. Hakimi. "On the realizability of a set of integers as
|
| 311 |
+
degrees of the vertices of a linear graph", J. SIAM, 10, pp. 496-506
|
| 312 |
+
(1962).
|
| 313 |
+
"""
|
| 314 |
+
try:
|
| 315 |
+
deg_sequence = nx.utils.make_list_of_ints(sequence)
|
| 316 |
+
except nx.NetworkXError:
|
| 317 |
+
return False
|
| 318 |
+
dsum, dmax = 0, 0
|
| 319 |
+
for d in deg_sequence:
|
| 320 |
+
if d < 0:
|
| 321 |
+
return False
|
| 322 |
+
dsum, dmax = dsum + d, max(dmax, d)
|
| 323 |
+
if dsum % 2 or dsum < 2 * dmax:
|
| 324 |
+
return False
|
| 325 |
+
return True
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
@nx._dispatch(graphs=None)
|
| 329 |
+
def is_pseudographical(sequence):
|
| 330 |
+
"""Returns True if some pseudograph can realize the sequence.
|
| 331 |
+
|
| 332 |
+
Every nonnegative integer sequence with an even sum is pseudographical
|
| 333 |
+
(see [1]_).
|
| 334 |
+
|
| 335 |
+
Parameters
|
| 336 |
+
----------
|
| 337 |
+
sequence : list or iterable container
|
| 338 |
+
A sequence of integer node degrees
|
| 339 |
+
|
| 340 |
+
Returns
|
| 341 |
+
-------
|
| 342 |
+
valid : bool
|
| 343 |
+
True if the sequence is a pseudographic degree sequence and False if not.
|
| 344 |
+
|
| 345 |
+
Examples
|
| 346 |
+
--------
|
| 347 |
+
>>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (3, 4), (4, 2), (5, 1), (5, 4)])
|
| 348 |
+
>>> sequence = (d for _, d in G.degree())
|
| 349 |
+
>>> nx.is_pseudographical(sequence)
|
| 350 |
+
True
|
| 351 |
+
|
| 352 |
+
To test a non-pseudographical sequence:
|
| 353 |
+
>>> sequence_list = [d for _, d in G.degree()]
|
| 354 |
+
>>> sequence_list[-1] += 1
|
| 355 |
+
>>> nx.is_pseudographical(sequence_list)
|
| 356 |
+
False
|
| 357 |
+
|
| 358 |
+
Notes
|
| 359 |
+
-----
|
| 360 |
+
The worst-case run time is $O(n)$ where n is the length of the sequence.
|
| 361 |
+
|
| 362 |
+
References
|
| 363 |
+
----------
|
| 364 |
+
.. [1] F. Boesch and F. Harary. "Line removal algorithms for graphs
|
| 365 |
+
and their degree lists", IEEE Trans. Circuits and Systems, CAS-23(12),
|
| 366 |
+
pp. 778-782 (1976).
|
| 367 |
+
"""
|
| 368 |
+
try:
|
| 369 |
+
deg_sequence = nx.utils.make_list_of_ints(sequence)
|
| 370 |
+
except nx.NetworkXError:
|
| 371 |
+
return False
|
| 372 |
+
return sum(deg_sequence) % 2 == 0 and min(deg_sequence) >= 0
|
| 373 |
+
|
| 374 |
+
|
| 375 |
+
@nx._dispatch(graphs=None)
|
| 376 |
+
def is_digraphical(in_sequence, out_sequence):
|
| 377 |
+
r"""Returns True if some directed graph can realize the in- and out-degree
|
| 378 |
+
sequences.
|
| 379 |
+
|
| 380 |
+
Parameters
|
| 381 |
+
----------
|
| 382 |
+
in_sequence : list or iterable container
|
| 383 |
+
A sequence of integer node in-degrees
|
| 384 |
+
|
| 385 |
+
out_sequence : list or iterable container
|
| 386 |
+
A sequence of integer node out-degrees
|
| 387 |
+
|
| 388 |
+
Returns
|
| 389 |
+
-------
|
| 390 |
+
valid : bool
|
| 391 |
+
True if in and out-sequences are digraphic False if not.
|
| 392 |
+
|
| 393 |
+
Examples
|
| 394 |
+
--------
|
| 395 |
+
>>> G = nx.DiGraph([(1, 2), (1, 3), (2, 3), (3, 4), (4, 2), (5, 1), (5, 4)])
|
| 396 |
+
>>> in_seq = (d for n, d in G.in_degree())
|
| 397 |
+
>>> out_seq = (d for n, d in G.out_degree())
|
| 398 |
+
>>> nx.is_digraphical(in_seq, out_seq)
|
| 399 |
+
True
|
| 400 |
+
|
| 401 |
+
To test a non-digraphical scenario:
|
| 402 |
+
>>> in_seq_list = [d for n, d in G.in_degree()]
|
| 403 |
+
>>> in_seq_list[-1] += 1
|
| 404 |
+
>>> nx.is_digraphical(in_seq_list, out_seq)
|
| 405 |
+
False
|
| 406 |
+
|
| 407 |
+
Notes
|
| 408 |
+
-----
|
| 409 |
+
This algorithm is from Kleitman and Wang [1]_.
|
| 410 |
+
The worst case runtime is $O(s \times \log n)$ where $s$ and $n$ are the
|
| 411 |
+
sum and length of the sequences respectively.
|
| 412 |
+
|
| 413 |
+
References
|
| 414 |
+
----------
|
| 415 |
+
.. [1] D.J. Kleitman and D.L. Wang
|
| 416 |
+
Algorithms for Constructing Graphs and Digraphs with Given Valences
|
| 417 |
+
and Factors, Discrete Mathematics, 6(1), pp. 79-88 (1973)
|
| 418 |
+
"""
|
| 419 |
+
try:
|
| 420 |
+
in_deg_sequence = nx.utils.make_list_of_ints(in_sequence)
|
| 421 |
+
out_deg_sequence = nx.utils.make_list_of_ints(out_sequence)
|
| 422 |
+
except nx.NetworkXError:
|
| 423 |
+
return False
|
| 424 |
+
# Process the sequences and form two heaps to store degree pairs with
|
| 425 |
+
# either zero or non-zero out degrees
|
| 426 |
+
sumin, sumout, nin, nout = 0, 0, len(in_deg_sequence), len(out_deg_sequence)
|
| 427 |
+
maxn = max(nin, nout)
|
| 428 |
+
maxin = 0
|
| 429 |
+
if maxn == 0:
|
| 430 |
+
return True
|
| 431 |
+
stubheap, zeroheap = [], []
|
| 432 |
+
for n in range(maxn):
|
| 433 |
+
in_deg, out_deg = 0, 0
|
| 434 |
+
if n < nout:
|
| 435 |
+
out_deg = out_deg_sequence[n]
|
| 436 |
+
if n < nin:
|
| 437 |
+
in_deg = in_deg_sequence[n]
|
| 438 |
+
if in_deg < 0 or out_deg < 0:
|
| 439 |
+
return False
|
| 440 |
+
sumin, sumout, maxin = sumin + in_deg, sumout + out_deg, max(maxin, in_deg)
|
| 441 |
+
if in_deg > 0:
|
| 442 |
+
stubheap.append((-1 * out_deg, -1 * in_deg))
|
| 443 |
+
elif out_deg > 0:
|
| 444 |
+
zeroheap.append(-1 * out_deg)
|
| 445 |
+
if sumin != sumout:
|
| 446 |
+
return False
|
| 447 |
+
heapq.heapify(stubheap)
|
| 448 |
+
heapq.heapify(zeroheap)
|
| 449 |
+
|
| 450 |
+
modstubs = [(0, 0)] * (maxin + 1)
|
| 451 |
+
# Successively reduce degree sequence by removing the maximum out degree
|
| 452 |
+
while stubheap:
|
| 453 |
+
# Take the first value in the sequence with non-zero in degree
|
| 454 |
+
(freeout, freein) = heapq.heappop(stubheap)
|
| 455 |
+
freein *= -1
|
| 456 |
+
if freein > len(stubheap) + len(zeroheap):
|
| 457 |
+
return False
|
| 458 |
+
|
| 459 |
+
# Attach out stubs to the nodes with the most in stubs
|
| 460 |
+
mslen = 0
|
| 461 |
+
for i in range(freein):
|
| 462 |
+
if zeroheap and (not stubheap or stubheap[0][0] > zeroheap[0]):
|
| 463 |
+
stubout = heapq.heappop(zeroheap)
|
| 464 |
+
stubin = 0
|
| 465 |
+
else:
|
| 466 |
+
(stubout, stubin) = heapq.heappop(stubheap)
|
| 467 |
+
if stubout == 0:
|
| 468 |
+
return False
|
| 469 |
+
# Check if target is now totally connected
|
| 470 |
+
if stubout + 1 < 0 or stubin < 0:
|
| 471 |
+
modstubs[mslen] = (stubout + 1, stubin)
|
| 472 |
+
mslen += 1
|
| 473 |
+
|
| 474 |
+
# Add back the nodes to the heap that still have available stubs
|
| 475 |
+
for i in range(mslen):
|
| 476 |
+
stub = modstubs[i]
|
| 477 |
+
if stub[1] < 0:
|
| 478 |
+
heapq.heappush(stubheap, stub)
|
| 479 |
+
else:
|
| 480 |
+
heapq.heappush(zeroheap, stub[0])
|
| 481 |
+
if freeout < 0:
|
| 482 |
+
heapq.heappush(zeroheap, freeout)
|
| 483 |
+
return True
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/hierarchy.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Flow Hierarchy.
|
| 3 |
+
"""
|
| 4 |
+
import networkx as nx
|
| 5 |
+
|
| 6 |
+
__all__ = ["flow_hierarchy"]
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
@nx._dispatch(edge_attrs="weight")
|
| 10 |
+
def flow_hierarchy(G, weight=None):
|
| 11 |
+
"""Returns the flow hierarchy of a directed network.
|
| 12 |
+
|
| 13 |
+
Flow hierarchy is defined as the fraction of edges not participating
|
| 14 |
+
in cycles in a directed graph [1]_.
|
| 15 |
+
|
| 16 |
+
Parameters
|
| 17 |
+
----------
|
| 18 |
+
G : DiGraph or MultiDiGraph
|
| 19 |
+
A directed graph
|
| 20 |
+
|
| 21 |
+
weight : string, optional (default=None)
|
| 22 |
+
Attribute to use for edge weights. If None the weight defaults to 1.
|
| 23 |
+
|
| 24 |
+
Returns
|
| 25 |
+
-------
|
| 26 |
+
h : float
|
| 27 |
+
Flow hierarchy value
|
| 28 |
+
|
| 29 |
+
Notes
|
| 30 |
+
-----
|
| 31 |
+
The algorithm described in [1]_ computes the flow hierarchy through
|
| 32 |
+
exponentiation of the adjacency matrix. This function implements an
|
| 33 |
+
alternative approach that finds strongly connected components.
|
| 34 |
+
An edge is in a cycle if and only if it is in a strongly connected
|
| 35 |
+
component, which can be found in $O(m)$ time using Tarjan's algorithm.
|
| 36 |
+
|
| 37 |
+
References
|
| 38 |
+
----------
|
| 39 |
+
.. [1] Luo, J.; Magee, C.L. (2011),
|
| 40 |
+
Detecting evolving patterns of self-organizing networks by flow
|
| 41 |
+
hierarchy measurement, Complexity, Volume 16 Issue 6 53-61.
|
| 42 |
+
DOI: 10.1002/cplx.20368
|
| 43 |
+
http://web.mit.edu/~cmagee/www/documents/28-DetectingEvolvingPatterns_FlowHierarchy.pdf
|
| 44 |
+
"""
|
| 45 |
+
if not G.is_directed():
|
| 46 |
+
raise nx.NetworkXError("G must be a digraph in flow_hierarchy")
|
| 47 |
+
scc = nx.strongly_connected_components(G)
|
| 48 |
+
return 1 - sum(G.subgraph(c).size(weight) for c in scc) / G.size(weight)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/isolate.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Functions for identifying isolate (degree zero) nodes.
|
| 3 |
+
"""
|
| 4 |
+
import networkx as nx
|
| 5 |
+
|
| 6 |
+
__all__ = ["is_isolate", "isolates", "number_of_isolates"]
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
@nx._dispatch
|
| 10 |
+
def is_isolate(G, n):
|
| 11 |
+
"""Determines whether a node is an isolate.
|
| 12 |
+
|
| 13 |
+
An *isolate* is a node with no neighbors (that is, with degree
|
| 14 |
+
zero). For directed graphs, this means no in-neighbors and no
|
| 15 |
+
out-neighbors.
|
| 16 |
+
|
| 17 |
+
Parameters
|
| 18 |
+
----------
|
| 19 |
+
G : NetworkX graph
|
| 20 |
+
|
| 21 |
+
n : node
|
| 22 |
+
A node in `G`.
|
| 23 |
+
|
| 24 |
+
Returns
|
| 25 |
+
-------
|
| 26 |
+
is_isolate : bool
|
| 27 |
+
True if and only if `n` has no neighbors.
|
| 28 |
+
|
| 29 |
+
Examples
|
| 30 |
+
--------
|
| 31 |
+
>>> G = nx.Graph()
|
| 32 |
+
>>> G.add_edge(1, 2)
|
| 33 |
+
>>> G.add_node(3)
|
| 34 |
+
>>> nx.is_isolate(G, 2)
|
| 35 |
+
False
|
| 36 |
+
>>> nx.is_isolate(G, 3)
|
| 37 |
+
True
|
| 38 |
+
"""
|
| 39 |
+
return G.degree(n) == 0
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
@nx._dispatch
|
| 43 |
+
def isolates(G):
|
| 44 |
+
"""Iterator over isolates in the graph.
|
| 45 |
+
|
| 46 |
+
An *isolate* is a node with no neighbors (that is, with degree
|
| 47 |
+
zero). For directed graphs, this means no in-neighbors and no
|
| 48 |
+
out-neighbors.
|
| 49 |
+
|
| 50 |
+
Parameters
|
| 51 |
+
----------
|
| 52 |
+
G : NetworkX graph
|
| 53 |
+
|
| 54 |
+
Returns
|
| 55 |
+
-------
|
| 56 |
+
iterator
|
| 57 |
+
An iterator over the isolates of `G`.
|
| 58 |
+
|
| 59 |
+
Examples
|
| 60 |
+
--------
|
| 61 |
+
To get a list of all isolates of a graph, use the :class:`list`
|
| 62 |
+
constructor::
|
| 63 |
+
|
| 64 |
+
>>> G = nx.Graph()
|
| 65 |
+
>>> G.add_edge(1, 2)
|
| 66 |
+
>>> G.add_node(3)
|
| 67 |
+
>>> list(nx.isolates(G))
|
| 68 |
+
[3]
|
| 69 |
+
|
| 70 |
+
To remove all isolates in the graph, first create a list of the
|
| 71 |
+
isolates, then use :meth:`Graph.remove_nodes_from`::
|
| 72 |
+
|
| 73 |
+
>>> G.remove_nodes_from(list(nx.isolates(G)))
|
| 74 |
+
>>> list(G)
|
| 75 |
+
[1, 2]
|
| 76 |
+
|
| 77 |
+
For digraphs, isolates have zero in-degree and zero out_degre::
|
| 78 |
+
|
| 79 |
+
>>> G = nx.DiGraph([(0, 1), (1, 2)])
|
| 80 |
+
>>> G.add_node(3)
|
| 81 |
+
>>> list(nx.isolates(G))
|
| 82 |
+
[3]
|
| 83 |
+
|
| 84 |
+
"""
|
| 85 |
+
return (n for n, d in G.degree() if d == 0)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
@nx._dispatch
|
| 89 |
+
def number_of_isolates(G):
|
| 90 |
+
"""Returns the number of isolates in the graph.
|
| 91 |
+
|
| 92 |
+
An *isolate* is a node with no neighbors (that is, with degree
|
| 93 |
+
zero). For directed graphs, this means no in-neighbors and no
|
| 94 |
+
out-neighbors.
|
| 95 |
+
|
| 96 |
+
Parameters
|
| 97 |
+
----------
|
| 98 |
+
G : NetworkX graph
|
| 99 |
+
|
| 100 |
+
Returns
|
| 101 |
+
-------
|
| 102 |
+
int
|
| 103 |
+
The number of degree zero nodes in the graph `G`.
|
| 104 |
+
|
| 105 |
+
"""
|
| 106 |
+
# TODO This can be parallelized.
|
| 107 |
+
return sum(1 for v in isolates(G))
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/tests/__pycache__/test_pagerank.cpython-311.pyc
ADDED
|
Binary file (14.4 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/link_analysis/tests/test_hits.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
|
| 5 |
+
np = pytest.importorskip("numpy")
|
| 6 |
+
sp = pytest.importorskip("scipy")
|
| 7 |
+
|
| 8 |
+
from networkx.algorithms.link_analysis.hits_alg import (
|
| 9 |
+
_hits_numpy,
|
| 10 |
+
_hits_python,
|
| 11 |
+
_hits_scipy,
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
# Example from
|
| 15 |
+
# A. Langville and C. Meyer, "A survey of eigenvector methods of web
|
| 16 |
+
# information retrieval." http://citeseer.ist.psu.edu/713792.html
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class TestHITS:
|
| 20 |
+
@classmethod
|
| 21 |
+
def setup_class(cls):
|
| 22 |
+
G = nx.DiGraph()
|
| 23 |
+
|
| 24 |
+
edges = [(1, 3), (1, 5), (2, 1), (3, 5), (5, 4), (5, 3), (6, 5)]
|
| 25 |
+
|
| 26 |
+
G.add_edges_from(edges, weight=1)
|
| 27 |
+
cls.G = G
|
| 28 |
+
cls.G.a = dict(
|
| 29 |
+
zip(sorted(G), [0.000000, 0.000000, 0.366025, 0.133975, 0.500000, 0.000000])
|
| 30 |
+
)
|
| 31 |
+
cls.G.h = dict(
|
| 32 |
+
zip(sorted(G), [0.366025, 0.000000, 0.211325, 0.000000, 0.211325, 0.211325])
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
def test_hits_numpy(self):
|
| 36 |
+
G = self.G
|
| 37 |
+
h, a = _hits_numpy(G)
|
| 38 |
+
for n in G:
|
| 39 |
+
assert h[n] == pytest.approx(G.h[n], abs=1e-4)
|
| 40 |
+
for n in G:
|
| 41 |
+
assert a[n] == pytest.approx(G.a[n], abs=1e-4)
|
| 42 |
+
|
| 43 |
+
@pytest.mark.parametrize("hits_alg", (nx.hits, _hits_python, _hits_scipy))
|
| 44 |
+
def test_hits(self, hits_alg):
|
| 45 |
+
G = self.G
|
| 46 |
+
h, a = hits_alg(G, tol=1.0e-08)
|
| 47 |
+
for n in G:
|
| 48 |
+
assert h[n] == pytest.approx(G.h[n], abs=1e-4)
|
| 49 |
+
for n in G:
|
| 50 |
+
assert a[n] == pytest.approx(G.a[n], abs=1e-4)
|
| 51 |
+
nstart = {i: 1.0 / 2 for i in G}
|
| 52 |
+
h, a = hits_alg(G, nstart=nstart)
|
| 53 |
+
for n in G:
|
| 54 |
+
assert h[n] == pytest.approx(G.h[n], abs=1e-4)
|
| 55 |
+
for n in G:
|
| 56 |
+
assert a[n] == pytest.approx(G.a[n], abs=1e-4)
|
| 57 |
+
|
| 58 |
+
def test_empty(self):
|
| 59 |
+
G = nx.Graph()
|
| 60 |
+
assert nx.hits(G) == ({}, {})
|
| 61 |
+
assert _hits_numpy(G) == ({}, {})
|
| 62 |
+
assert _hits_python(G) == ({}, {})
|
| 63 |
+
assert _hits_scipy(G) == ({}, {})
|
| 64 |
+
|
| 65 |
+
def test_hits_not_convergent(self):
|
| 66 |
+
G = nx.path_graph(50)
|
| 67 |
+
with pytest.raises(nx.PowerIterationFailedConvergence):
|
| 68 |
+
_hits_scipy(G, max_iter=1)
|
| 69 |
+
with pytest.raises(nx.PowerIterationFailedConvergence):
|
| 70 |
+
_hits_python(G, max_iter=1)
|
| 71 |
+
with pytest.raises(nx.PowerIterationFailedConvergence):
|
| 72 |
+
_hits_scipy(G, max_iter=0)
|
| 73 |
+
with pytest.raises(nx.PowerIterationFailedConvergence):
|
| 74 |
+
_hits_python(G, max_iter=0)
|
| 75 |
+
with pytest.raises(ValueError):
|
| 76 |
+
nx.hits(G, max_iter=0)
|
| 77 |
+
with pytest.raises(sp.sparse.linalg.ArpackNoConvergence):
|
| 78 |
+
nx.hits(G, max_iter=1)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/lowest_common_ancestors.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Algorithms for finding the lowest common ancestor of trees and DAGs."""
|
| 2 |
+
from collections import defaultdict
|
| 3 |
+
from collections.abc import Mapping, Set
|
| 4 |
+
from itertools import combinations_with_replacement
|
| 5 |
+
|
| 6 |
+
import networkx as nx
|
| 7 |
+
from networkx.utils import UnionFind, arbitrary_element, not_implemented_for
|
| 8 |
+
|
| 9 |
+
__all__ = [
|
| 10 |
+
"all_pairs_lowest_common_ancestor",
|
| 11 |
+
"tree_all_pairs_lowest_common_ancestor",
|
| 12 |
+
"lowest_common_ancestor",
|
| 13 |
+
]
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@not_implemented_for("undirected")
|
| 17 |
+
@nx._dispatch
|
| 18 |
+
def all_pairs_lowest_common_ancestor(G, pairs=None):
|
| 19 |
+
"""Return the lowest common ancestor of all pairs or the provided pairs
|
| 20 |
+
|
| 21 |
+
Parameters
|
| 22 |
+
----------
|
| 23 |
+
G : NetworkX directed graph
|
| 24 |
+
|
| 25 |
+
pairs : iterable of pairs of nodes, optional (default: all pairs)
|
| 26 |
+
The pairs of nodes of interest.
|
| 27 |
+
If None, will find the LCA of all pairs of nodes.
|
| 28 |
+
|
| 29 |
+
Yields
|
| 30 |
+
------
|
| 31 |
+
((node1, node2), lca) : 2-tuple
|
| 32 |
+
Where lca is least common ancestor of node1 and node2.
|
| 33 |
+
Note that for the default case, the order of the node pair is not considered,
|
| 34 |
+
e.g. you will not get both ``(a, b)`` and ``(b, a)``
|
| 35 |
+
|
| 36 |
+
Raises
|
| 37 |
+
------
|
| 38 |
+
NetworkXPointlessConcept
|
| 39 |
+
If `G` is null.
|
| 40 |
+
NetworkXError
|
| 41 |
+
If `G` is not a DAG.
|
| 42 |
+
|
| 43 |
+
Examples
|
| 44 |
+
--------
|
| 45 |
+
The default behavior is to yield the lowest common ancestor for all
|
| 46 |
+
possible combinations of nodes in `G`, including self-pairings:
|
| 47 |
+
|
| 48 |
+
>>> G = nx.DiGraph([(0, 1), (0, 3), (1, 2)])
|
| 49 |
+
>>> dict(nx.all_pairs_lowest_common_ancestor(G))
|
| 50 |
+
{(0, 0): 0, (0, 1): 0, (0, 3): 0, (0, 2): 0, (1, 1): 1, (1, 3): 0, (1, 2): 1, (3, 3): 3, (3, 2): 0, (2, 2): 2}
|
| 51 |
+
|
| 52 |
+
The pairs argument can be used to limit the output to only the
|
| 53 |
+
specified node pairings:
|
| 54 |
+
|
| 55 |
+
>>> dict(nx.all_pairs_lowest_common_ancestor(G, pairs=[(1, 2), (2, 3)]))
|
| 56 |
+
{(1, 2): 1, (2, 3): 0}
|
| 57 |
+
|
| 58 |
+
Notes
|
| 59 |
+
-----
|
| 60 |
+
Only defined on non-null directed acyclic graphs.
|
| 61 |
+
|
| 62 |
+
See Also
|
| 63 |
+
--------
|
| 64 |
+
lowest_common_ancestor
|
| 65 |
+
"""
|
| 66 |
+
if not nx.is_directed_acyclic_graph(G):
|
| 67 |
+
raise nx.NetworkXError("LCA only defined on directed acyclic graphs.")
|
| 68 |
+
if len(G) == 0:
|
| 69 |
+
raise nx.NetworkXPointlessConcept("LCA meaningless on null graphs.")
|
| 70 |
+
|
| 71 |
+
if pairs is None:
|
| 72 |
+
pairs = combinations_with_replacement(G, 2)
|
| 73 |
+
else:
|
| 74 |
+
# Convert iterator to iterable, if necessary. Trim duplicates.
|
| 75 |
+
pairs = dict.fromkeys(pairs)
|
| 76 |
+
# Verify that each of the nodes in the provided pairs is in G
|
| 77 |
+
nodeset = set(G)
|
| 78 |
+
for pair in pairs:
|
| 79 |
+
if set(pair) - nodeset:
|
| 80 |
+
raise nx.NodeNotFound(
|
| 81 |
+
f"Node(s) {set(pair) - nodeset} from pair {pair} not in G."
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
# Once input validation is done, construct the generator
|
| 85 |
+
def generate_lca_from_pairs(G, pairs):
|
| 86 |
+
ancestor_cache = {}
|
| 87 |
+
|
| 88 |
+
for v, w in pairs:
|
| 89 |
+
if v not in ancestor_cache:
|
| 90 |
+
ancestor_cache[v] = nx.ancestors(G, v)
|
| 91 |
+
ancestor_cache[v].add(v)
|
| 92 |
+
if w not in ancestor_cache:
|
| 93 |
+
ancestor_cache[w] = nx.ancestors(G, w)
|
| 94 |
+
ancestor_cache[w].add(w)
|
| 95 |
+
|
| 96 |
+
common_ancestors = ancestor_cache[v] & ancestor_cache[w]
|
| 97 |
+
|
| 98 |
+
if common_ancestors:
|
| 99 |
+
common_ancestor = next(iter(common_ancestors))
|
| 100 |
+
while True:
|
| 101 |
+
successor = None
|
| 102 |
+
for lower_ancestor in G.successors(common_ancestor):
|
| 103 |
+
if lower_ancestor in common_ancestors:
|
| 104 |
+
successor = lower_ancestor
|
| 105 |
+
break
|
| 106 |
+
if successor is None:
|
| 107 |
+
break
|
| 108 |
+
common_ancestor = successor
|
| 109 |
+
yield ((v, w), common_ancestor)
|
| 110 |
+
|
| 111 |
+
return generate_lca_from_pairs(G, pairs)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
@not_implemented_for("undirected")
|
| 115 |
+
@nx._dispatch
|
| 116 |
+
def lowest_common_ancestor(G, node1, node2, default=None):
|
| 117 |
+
"""Compute the lowest common ancestor of the given pair of nodes.
|
| 118 |
+
|
| 119 |
+
Parameters
|
| 120 |
+
----------
|
| 121 |
+
G : NetworkX directed graph
|
| 122 |
+
|
| 123 |
+
node1, node2 : nodes in the graph.
|
| 124 |
+
|
| 125 |
+
default : object
|
| 126 |
+
Returned if no common ancestor between `node1` and `node2`
|
| 127 |
+
|
| 128 |
+
Returns
|
| 129 |
+
-------
|
| 130 |
+
The lowest common ancestor of node1 and node2,
|
| 131 |
+
or default if they have no common ancestors.
|
| 132 |
+
|
| 133 |
+
Examples
|
| 134 |
+
--------
|
| 135 |
+
>>> G = nx.DiGraph()
|
| 136 |
+
>>> nx.add_path(G, (0, 1, 2, 3))
|
| 137 |
+
>>> nx.add_path(G, (0, 4, 3))
|
| 138 |
+
>>> nx.lowest_common_ancestor(G, 2, 4)
|
| 139 |
+
0
|
| 140 |
+
|
| 141 |
+
See Also
|
| 142 |
+
--------
|
| 143 |
+
all_pairs_lowest_common_ancestor"""
|
| 144 |
+
|
| 145 |
+
ans = list(all_pairs_lowest_common_ancestor(G, pairs=[(node1, node2)]))
|
| 146 |
+
if ans:
|
| 147 |
+
assert len(ans) == 1
|
| 148 |
+
return ans[0][1]
|
| 149 |
+
return default
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
@not_implemented_for("undirected")
|
| 153 |
+
@nx._dispatch
|
| 154 |
+
def tree_all_pairs_lowest_common_ancestor(G, root=None, pairs=None):
|
| 155 |
+
r"""Yield the lowest common ancestor for sets of pairs in a tree.
|
| 156 |
+
|
| 157 |
+
Parameters
|
| 158 |
+
----------
|
| 159 |
+
G : NetworkX directed graph (must be a tree)
|
| 160 |
+
|
| 161 |
+
root : node, optional (default: None)
|
| 162 |
+
The root of the subtree to operate on.
|
| 163 |
+
If None, assume the entire graph has exactly one source and use that.
|
| 164 |
+
|
| 165 |
+
pairs : iterable or iterator of pairs of nodes, optional (default: None)
|
| 166 |
+
The pairs of interest. If None, Defaults to all pairs of nodes
|
| 167 |
+
under `root` that have a lowest common ancestor.
|
| 168 |
+
|
| 169 |
+
Returns
|
| 170 |
+
-------
|
| 171 |
+
lcas : generator of tuples `((u, v), lca)` where `u` and `v` are nodes
|
| 172 |
+
in `pairs` and `lca` is their lowest common ancestor.
|
| 173 |
+
|
| 174 |
+
Examples
|
| 175 |
+
--------
|
| 176 |
+
>>> import pprint
|
| 177 |
+
>>> G = nx.DiGraph([(1, 3), (2, 4), (1, 2)])
|
| 178 |
+
>>> pprint.pprint(dict(nx.tree_all_pairs_lowest_common_ancestor(G)))
|
| 179 |
+
{(1, 1): 1,
|
| 180 |
+
(2, 1): 1,
|
| 181 |
+
(2, 2): 2,
|
| 182 |
+
(3, 1): 1,
|
| 183 |
+
(3, 2): 1,
|
| 184 |
+
(3, 3): 3,
|
| 185 |
+
(3, 4): 1,
|
| 186 |
+
(4, 1): 1,
|
| 187 |
+
(4, 2): 2,
|
| 188 |
+
(4, 4): 4}
|
| 189 |
+
|
| 190 |
+
We can also use `pairs` argument to specify the pairs of nodes for which we
|
| 191 |
+
want to compute lowest common ancestors. Here is an example:
|
| 192 |
+
|
| 193 |
+
>>> dict(nx.tree_all_pairs_lowest_common_ancestor(G, pairs=[(1, 4), (2, 3)]))
|
| 194 |
+
{(2, 3): 1, (1, 4): 1}
|
| 195 |
+
|
| 196 |
+
Notes
|
| 197 |
+
-----
|
| 198 |
+
Only defined on non-null trees represented with directed edges from
|
| 199 |
+
parents to children. Uses Tarjan's off-line lowest-common-ancestors
|
| 200 |
+
algorithm. Runs in time $O(4 \times (V + E + P))$ time, where 4 is the largest
|
| 201 |
+
value of the inverse Ackermann function likely to ever come up in actual
|
| 202 |
+
use, and $P$ is the number of pairs requested (or $V^2$ if all are needed).
|
| 203 |
+
|
| 204 |
+
Tarjan, R. E. (1979), "Applications of path compression on balanced trees",
|
| 205 |
+
Journal of the ACM 26 (4): 690-715, doi:10.1145/322154.322161.
|
| 206 |
+
|
| 207 |
+
See Also
|
| 208 |
+
--------
|
| 209 |
+
all_pairs_lowest_common_ancestor: similar routine for general DAGs
|
| 210 |
+
lowest_common_ancestor: just a single pair for general DAGs
|
| 211 |
+
"""
|
| 212 |
+
if len(G) == 0:
|
| 213 |
+
raise nx.NetworkXPointlessConcept("LCA meaningless on null graphs.")
|
| 214 |
+
|
| 215 |
+
# Index pairs of interest for efficient lookup from either side.
|
| 216 |
+
if pairs is not None:
|
| 217 |
+
pair_dict = defaultdict(set)
|
| 218 |
+
# See note on all_pairs_lowest_common_ancestor.
|
| 219 |
+
if not isinstance(pairs, (Mapping, Set)):
|
| 220 |
+
pairs = set(pairs)
|
| 221 |
+
for u, v in pairs:
|
| 222 |
+
for n in (u, v):
|
| 223 |
+
if n not in G:
|
| 224 |
+
msg = f"The node {str(n)} is not in the digraph."
|
| 225 |
+
raise nx.NodeNotFound(msg)
|
| 226 |
+
pair_dict[u].add(v)
|
| 227 |
+
pair_dict[v].add(u)
|
| 228 |
+
|
| 229 |
+
# If root is not specified, find the exactly one node with in degree 0 and
|
| 230 |
+
# use it. Raise an error if none are found, or more than one is. Also check
|
| 231 |
+
# for any nodes with in degree larger than 1, which would imply G is not a
|
| 232 |
+
# tree.
|
| 233 |
+
if root is None:
|
| 234 |
+
for n, deg in G.in_degree:
|
| 235 |
+
if deg == 0:
|
| 236 |
+
if root is not None:
|
| 237 |
+
msg = "No root specified and tree has multiple sources."
|
| 238 |
+
raise nx.NetworkXError(msg)
|
| 239 |
+
root = n
|
| 240 |
+
# checking deg>1 is not sufficient for MultiDiGraphs
|
| 241 |
+
elif deg > 1 and len(G.pred[n]) > 1:
|
| 242 |
+
msg = "Tree LCA only defined on trees; use DAG routine."
|
| 243 |
+
raise nx.NetworkXError(msg)
|
| 244 |
+
if root is None:
|
| 245 |
+
raise nx.NetworkXError("Graph contains a cycle.")
|
| 246 |
+
|
| 247 |
+
# Iterative implementation of Tarjan's offline lca algorithm
|
| 248 |
+
# as described in CLRS on page 521 (2nd edition)/page 584 (3rd edition)
|
| 249 |
+
uf = UnionFind()
|
| 250 |
+
ancestors = {}
|
| 251 |
+
for node in G:
|
| 252 |
+
ancestors[node] = uf[node]
|
| 253 |
+
|
| 254 |
+
colors = defaultdict(bool)
|
| 255 |
+
for node in nx.dfs_postorder_nodes(G, root):
|
| 256 |
+
colors[node] = True
|
| 257 |
+
for v in pair_dict[node] if pairs is not None else G:
|
| 258 |
+
if colors[v]:
|
| 259 |
+
# If the user requested both directions of a pair, give it.
|
| 260 |
+
# Otherwise, just give one.
|
| 261 |
+
if pairs is not None and (node, v) in pairs:
|
| 262 |
+
yield (node, v), ancestors[uf[v]]
|
| 263 |
+
if pairs is None or (v, node) in pairs:
|
| 264 |
+
yield (v, node), ancestors[uf[v]]
|
| 265 |
+
if node != root:
|
| 266 |
+
parent = arbitrary_element(G.pred[node])
|
| 267 |
+
uf.union(parent, node)
|
| 268 |
+
ancestors[uf[parent]] = parent
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/minors/__pycache__/contraction.cpython-311.pyc
ADDED
|
Binary file (25 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/minors/tests/test_contraction.py
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Unit tests for the :mod:`networkx.algorithms.minors.contraction` module."""
|
| 2 |
+
import pytest
|
| 3 |
+
|
| 4 |
+
import networkx as nx
|
| 5 |
+
from networkx.utils import arbitrary_element, edges_equal, nodes_equal
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def test_quotient_graph_complete_multipartite():
|
| 9 |
+
"""Tests that the quotient graph of the complete *n*-partite graph
|
| 10 |
+
under the "same neighbors" node relation is the complete graph on *n*
|
| 11 |
+
nodes.
|
| 12 |
+
|
| 13 |
+
"""
|
| 14 |
+
G = nx.complete_multipartite_graph(2, 3, 4)
|
| 15 |
+
# Two nodes are equivalent if they are not adjacent but have the same
|
| 16 |
+
# neighbor set.
|
| 17 |
+
|
| 18 |
+
def same_neighbors(u, v):
|
| 19 |
+
return u not in G[v] and v not in G[u] and G[u] == G[v]
|
| 20 |
+
|
| 21 |
+
expected = nx.complete_graph(3)
|
| 22 |
+
actual = nx.quotient_graph(G, same_neighbors)
|
| 23 |
+
# It won't take too long to run a graph isomorphism algorithm on such
|
| 24 |
+
# small graphs.
|
| 25 |
+
assert nx.is_isomorphic(expected, actual)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def test_quotient_graph_complete_bipartite():
|
| 29 |
+
"""Tests that the quotient graph of the complete bipartite graph under
|
| 30 |
+
the "same neighbors" node relation is `K_2`.
|
| 31 |
+
|
| 32 |
+
"""
|
| 33 |
+
G = nx.complete_bipartite_graph(2, 3)
|
| 34 |
+
# Two nodes are equivalent if they are not adjacent but have the same
|
| 35 |
+
# neighbor set.
|
| 36 |
+
|
| 37 |
+
def same_neighbors(u, v):
|
| 38 |
+
return u not in G[v] and v not in G[u] and G[u] == G[v]
|
| 39 |
+
|
| 40 |
+
expected = nx.complete_graph(2)
|
| 41 |
+
actual = nx.quotient_graph(G, same_neighbors)
|
| 42 |
+
# It won't take too long to run a graph isomorphism algorithm on such
|
| 43 |
+
# small graphs.
|
| 44 |
+
assert nx.is_isomorphic(expected, actual)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def test_quotient_graph_edge_relation():
|
| 48 |
+
"""Tests for specifying an alternate edge relation for the quotient
|
| 49 |
+
graph.
|
| 50 |
+
|
| 51 |
+
"""
|
| 52 |
+
G = nx.path_graph(5)
|
| 53 |
+
|
| 54 |
+
def identity(u, v):
|
| 55 |
+
return u == v
|
| 56 |
+
|
| 57 |
+
def same_parity(b, c):
|
| 58 |
+
return arbitrary_element(b) % 2 == arbitrary_element(c) % 2
|
| 59 |
+
|
| 60 |
+
actual = nx.quotient_graph(G, identity, same_parity)
|
| 61 |
+
expected = nx.Graph()
|
| 62 |
+
expected.add_edges_from([(0, 2), (0, 4), (2, 4)])
|
| 63 |
+
expected.add_edge(1, 3)
|
| 64 |
+
assert nx.is_isomorphic(actual, expected)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def test_condensation_as_quotient():
|
| 68 |
+
"""This tests that the condensation of a graph can be viewed as the
|
| 69 |
+
quotient graph under the "in the same connected component" equivalence
|
| 70 |
+
relation.
|
| 71 |
+
|
| 72 |
+
"""
|
| 73 |
+
# This example graph comes from the file `test_strongly_connected.py`.
|
| 74 |
+
G = nx.DiGraph()
|
| 75 |
+
G.add_edges_from(
|
| 76 |
+
[
|
| 77 |
+
(1, 2),
|
| 78 |
+
(2, 3),
|
| 79 |
+
(2, 11),
|
| 80 |
+
(2, 12),
|
| 81 |
+
(3, 4),
|
| 82 |
+
(4, 3),
|
| 83 |
+
(4, 5),
|
| 84 |
+
(5, 6),
|
| 85 |
+
(6, 5),
|
| 86 |
+
(6, 7),
|
| 87 |
+
(7, 8),
|
| 88 |
+
(7, 9),
|
| 89 |
+
(7, 10),
|
| 90 |
+
(8, 9),
|
| 91 |
+
(9, 7),
|
| 92 |
+
(10, 6),
|
| 93 |
+
(11, 2),
|
| 94 |
+
(11, 4),
|
| 95 |
+
(11, 6),
|
| 96 |
+
(12, 6),
|
| 97 |
+
(12, 11),
|
| 98 |
+
]
|
| 99 |
+
)
|
| 100 |
+
scc = list(nx.strongly_connected_components(G))
|
| 101 |
+
C = nx.condensation(G, scc)
|
| 102 |
+
component_of = C.graph["mapping"]
|
| 103 |
+
# Two nodes are equivalent if they are in the same connected component.
|
| 104 |
+
|
| 105 |
+
def same_component(u, v):
|
| 106 |
+
return component_of[u] == component_of[v]
|
| 107 |
+
|
| 108 |
+
Q = nx.quotient_graph(G, same_component)
|
| 109 |
+
assert nx.is_isomorphic(C, Q)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def test_path():
|
| 113 |
+
G = nx.path_graph(6)
|
| 114 |
+
partition = [{0, 1}, {2, 3}, {4, 5}]
|
| 115 |
+
M = nx.quotient_graph(G, partition, relabel=True)
|
| 116 |
+
assert nodes_equal(M, [0, 1, 2])
|
| 117 |
+
assert edges_equal(M.edges(), [(0, 1), (1, 2)])
|
| 118 |
+
for n in M:
|
| 119 |
+
assert M.nodes[n]["nedges"] == 1
|
| 120 |
+
assert M.nodes[n]["nnodes"] == 2
|
| 121 |
+
assert M.nodes[n]["density"] == 1
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def test_path__partition_provided_as_dict_of_lists():
|
| 125 |
+
G = nx.path_graph(6)
|
| 126 |
+
partition = {0: [0, 1], 2: [2, 3], 4: [4, 5]}
|
| 127 |
+
M = nx.quotient_graph(G, partition, relabel=True)
|
| 128 |
+
assert nodes_equal(M, [0, 1, 2])
|
| 129 |
+
assert edges_equal(M.edges(), [(0, 1), (1, 2)])
|
| 130 |
+
for n in M:
|
| 131 |
+
assert M.nodes[n]["nedges"] == 1
|
| 132 |
+
assert M.nodes[n]["nnodes"] == 2
|
| 133 |
+
assert M.nodes[n]["density"] == 1
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def test_path__partition_provided_as_dict_of_tuples():
|
| 137 |
+
G = nx.path_graph(6)
|
| 138 |
+
partition = {0: (0, 1), 2: (2, 3), 4: (4, 5)}
|
| 139 |
+
M = nx.quotient_graph(G, partition, relabel=True)
|
| 140 |
+
assert nodes_equal(M, [0, 1, 2])
|
| 141 |
+
assert edges_equal(M.edges(), [(0, 1), (1, 2)])
|
| 142 |
+
for n in M:
|
| 143 |
+
assert M.nodes[n]["nedges"] == 1
|
| 144 |
+
assert M.nodes[n]["nnodes"] == 2
|
| 145 |
+
assert M.nodes[n]["density"] == 1
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def test_path__partition_provided_as_dict_of_sets():
|
| 149 |
+
G = nx.path_graph(6)
|
| 150 |
+
partition = {0: {0, 1}, 2: {2, 3}, 4: {4, 5}}
|
| 151 |
+
M = nx.quotient_graph(G, partition, relabel=True)
|
| 152 |
+
assert nodes_equal(M, [0, 1, 2])
|
| 153 |
+
assert edges_equal(M.edges(), [(0, 1), (1, 2)])
|
| 154 |
+
for n in M:
|
| 155 |
+
assert M.nodes[n]["nedges"] == 1
|
| 156 |
+
assert M.nodes[n]["nnodes"] == 2
|
| 157 |
+
assert M.nodes[n]["density"] == 1
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def test_multigraph_path():
|
| 161 |
+
G = nx.MultiGraph(nx.path_graph(6))
|
| 162 |
+
partition = [{0, 1}, {2, 3}, {4, 5}]
|
| 163 |
+
M = nx.quotient_graph(G, partition, relabel=True)
|
| 164 |
+
assert nodes_equal(M, [0, 1, 2])
|
| 165 |
+
assert edges_equal(M.edges(), [(0, 1), (1, 2)])
|
| 166 |
+
for n in M:
|
| 167 |
+
assert M.nodes[n]["nedges"] == 1
|
| 168 |
+
assert M.nodes[n]["nnodes"] == 2
|
| 169 |
+
assert M.nodes[n]["density"] == 1
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def test_directed_path():
|
| 173 |
+
G = nx.DiGraph()
|
| 174 |
+
nx.add_path(G, range(6))
|
| 175 |
+
partition = [{0, 1}, {2, 3}, {4, 5}]
|
| 176 |
+
M = nx.quotient_graph(G, partition, relabel=True)
|
| 177 |
+
assert nodes_equal(M, [0, 1, 2])
|
| 178 |
+
assert edges_equal(M.edges(), [(0, 1), (1, 2)])
|
| 179 |
+
for n in M:
|
| 180 |
+
assert M.nodes[n]["nedges"] == 1
|
| 181 |
+
assert M.nodes[n]["nnodes"] == 2
|
| 182 |
+
assert M.nodes[n]["density"] == 0.5
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
def test_directed_multigraph_path():
|
| 186 |
+
G = nx.MultiDiGraph()
|
| 187 |
+
nx.add_path(G, range(6))
|
| 188 |
+
partition = [{0, 1}, {2, 3}, {4, 5}]
|
| 189 |
+
M = nx.quotient_graph(G, partition, relabel=True)
|
| 190 |
+
assert nodes_equal(M, [0, 1, 2])
|
| 191 |
+
assert edges_equal(M.edges(), [(0, 1), (1, 2)])
|
| 192 |
+
for n in M:
|
| 193 |
+
assert M.nodes[n]["nedges"] == 1
|
| 194 |
+
assert M.nodes[n]["nnodes"] == 2
|
| 195 |
+
assert M.nodes[n]["density"] == 0.5
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def test_overlapping_blocks():
|
| 199 |
+
with pytest.raises(nx.NetworkXException):
|
| 200 |
+
G = nx.path_graph(6)
|
| 201 |
+
partition = [{0, 1, 2}, {2, 3}, {4, 5}]
|
| 202 |
+
nx.quotient_graph(G, partition)
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def test_weighted_path():
|
| 206 |
+
G = nx.path_graph(6)
|
| 207 |
+
for i in range(5):
|
| 208 |
+
G[i][i + 1]["w"] = i + 1
|
| 209 |
+
partition = [{0, 1}, {2, 3}, {4, 5}]
|
| 210 |
+
M = nx.quotient_graph(G, partition, weight="w", relabel=True)
|
| 211 |
+
assert nodes_equal(M, [0, 1, 2])
|
| 212 |
+
assert edges_equal(M.edges(), [(0, 1), (1, 2)])
|
| 213 |
+
assert M[0][1]["weight"] == 2
|
| 214 |
+
assert M[1][2]["weight"] == 4
|
| 215 |
+
for n in M:
|
| 216 |
+
assert M.nodes[n]["nedges"] == 1
|
| 217 |
+
assert M.nodes[n]["nnodes"] == 2
|
| 218 |
+
assert M.nodes[n]["density"] == 1
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def test_barbell():
|
| 222 |
+
G = nx.barbell_graph(3, 0)
|
| 223 |
+
partition = [{0, 1, 2}, {3, 4, 5}]
|
| 224 |
+
M = nx.quotient_graph(G, partition, relabel=True)
|
| 225 |
+
assert nodes_equal(M, [0, 1])
|
| 226 |
+
assert edges_equal(M.edges(), [(0, 1)])
|
| 227 |
+
for n in M:
|
| 228 |
+
assert M.nodes[n]["nedges"] == 3
|
| 229 |
+
assert M.nodes[n]["nnodes"] == 3
|
| 230 |
+
assert M.nodes[n]["density"] == 1
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
def test_barbell_plus():
|
| 234 |
+
G = nx.barbell_graph(3, 0)
|
| 235 |
+
# Add an extra edge joining the bells.
|
| 236 |
+
G.add_edge(0, 5)
|
| 237 |
+
partition = [{0, 1, 2}, {3, 4, 5}]
|
| 238 |
+
M = nx.quotient_graph(G, partition, relabel=True)
|
| 239 |
+
assert nodes_equal(M, [0, 1])
|
| 240 |
+
assert edges_equal(M.edges(), [(0, 1)])
|
| 241 |
+
assert M[0][1]["weight"] == 2
|
| 242 |
+
for n in M:
|
| 243 |
+
assert M.nodes[n]["nedges"] == 3
|
| 244 |
+
assert M.nodes[n]["nnodes"] == 3
|
| 245 |
+
assert M.nodes[n]["density"] == 1
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def test_blockmodel():
|
| 249 |
+
G = nx.path_graph(6)
|
| 250 |
+
partition = [[0, 1], [2, 3], [4, 5]]
|
| 251 |
+
M = nx.quotient_graph(G, partition, relabel=True)
|
| 252 |
+
assert nodes_equal(M.nodes(), [0, 1, 2])
|
| 253 |
+
assert edges_equal(M.edges(), [(0, 1), (1, 2)])
|
| 254 |
+
for n in M.nodes():
|
| 255 |
+
assert M.nodes[n]["nedges"] == 1
|
| 256 |
+
assert M.nodes[n]["nnodes"] == 2
|
| 257 |
+
assert M.nodes[n]["density"] == 1.0
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
def test_multigraph_blockmodel():
|
| 261 |
+
G = nx.MultiGraph(nx.path_graph(6))
|
| 262 |
+
partition = [[0, 1], [2, 3], [4, 5]]
|
| 263 |
+
M = nx.quotient_graph(G, partition, create_using=nx.MultiGraph(), relabel=True)
|
| 264 |
+
assert nodes_equal(M.nodes(), [0, 1, 2])
|
| 265 |
+
assert edges_equal(M.edges(), [(0, 1), (1, 2)])
|
| 266 |
+
for n in M.nodes():
|
| 267 |
+
assert M.nodes[n]["nedges"] == 1
|
| 268 |
+
assert M.nodes[n]["nnodes"] == 2
|
| 269 |
+
assert M.nodes[n]["density"] == 1.0
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
def test_quotient_graph_incomplete_partition():
|
| 273 |
+
G = nx.path_graph(6)
|
| 274 |
+
partition = []
|
| 275 |
+
H = nx.quotient_graph(G, partition, relabel=True)
|
| 276 |
+
assert nodes_equal(H.nodes(), [])
|
| 277 |
+
assert edges_equal(H.edges(), [])
|
| 278 |
+
|
| 279 |
+
partition = [[0, 1], [2, 3], [5]]
|
| 280 |
+
H = nx.quotient_graph(G, partition, relabel=True)
|
| 281 |
+
assert nodes_equal(H.nodes(), [0, 1, 2])
|
| 282 |
+
assert edges_equal(H.edges(), [(0, 1)])
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def test_undirected_node_contraction():
|
| 286 |
+
"""Tests for node contraction in an undirected graph."""
|
| 287 |
+
G = nx.cycle_graph(4)
|
| 288 |
+
actual = nx.contracted_nodes(G, 0, 1)
|
| 289 |
+
expected = nx.cycle_graph(3)
|
| 290 |
+
expected.add_edge(0, 0)
|
| 291 |
+
assert nx.is_isomorphic(actual, expected)
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def test_directed_node_contraction():
|
| 295 |
+
"""Tests for node contraction in a directed graph."""
|
| 296 |
+
G = nx.DiGraph(nx.cycle_graph(4))
|
| 297 |
+
actual = nx.contracted_nodes(G, 0, 1)
|
| 298 |
+
expected = nx.DiGraph(nx.cycle_graph(3))
|
| 299 |
+
expected.add_edge(0, 0)
|
| 300 |
+
expected.add_edge(0, 0)
|
| 301 |
+
assert nx.is_isomorphic(actual, expected)
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
def test_undirected_node_contraction_no_copy():
|
| 305 |
+
"""Tests for node contraction in an undirected graph
|
| 306 |
+
by making changes in place."""
|
| 307 |
+
G = nx.cycle_graph(4)
|
| 308 |
+
actual = nx.contracted_nodes(G, 0, 1, copy=False)
|
| 309 |
+
expected = nx.cycle_graph(3)
|
| 310 |
+
expected.add_edge(0, 0)
|
| 311 |
+
assert nx.is_isomorphic(actual, G)
|
| 312 |
+
assert nx.is_isomorphic(actual, expected)
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
def test_directed_node_contraction_no_copy():
|
| 316 |
+
"""Tests for node contraction in a directed graph
|
| 317 |
+
by making changes in place."""
|
| 318 |
+
G = nx.DiGraph(nx.cycle_graph(4))
|
| 319 |
+
actual = nx.contracted_nodes(G, 0, 1, copy=False)
|
| 320 |
+
expected = nx.DiGraph(nx.cycle_graph(3))
|
| 321 |
+
expected.add_edge(0, 0)
|
| 322 |
+
expected.add_edge(0, 0)
|
| 323 |
+
assert nx.is_isomorphic(actual, G)
|
| 324 |
+
assert nx.is_isomorphic(actual, expected)
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
def test_create_multigraph():
|
| 328 |
+
"""Tests that using a MultiGraph creates multiple edges."""
|
| 329 |
+
G = nx.path_graph(3, create_using=nx.MultiGraph())
|
| 330 |
+
G.add_edge(0, 1)
|
| 331 |
+
G.add_edge(0, 0)
|
| 332 |
+
G.add_edge(0, 2)
|
| 333 |
+
actual = nx.contracted_nodes(G, 0, 2)
|
| 334 |
+
expected = nx.MultiGraph()
|
| 335 |
+
expected.add_edge(0, 1)
|
| 336 |
+
expected.add_edge(0, 1)
|
| 337 |
+
expected.add_edge(0, 1)
|
| 338 |
+
expected.add_edge(0, 0)
|
| 339 |
+
expected.add_edge(0, 0)
|
| 340 |
+
assert edges_equal(actual.edges, expected.edges)
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
def test_multigraph_keys():
|
| 344 |
+
"""Tests that multiedge keys are reset in new graph."""
|
| 345 |
+
G = nx.path_graph(3, create_using=nx.MultiGraph())
|
| 346 |
+
G.add_edge(0, 1, 5)
|
| 347 |
+
G.add_edge(0, 0, 0)
|
| 348 |
+
G.add_edge(0, 2, 5)
|
| 349 |
+
actual = nx.contracted_nodes(G, 0, 2)
|
| 350 |
+
expected = nx.MultiGraph()
|
| 351 |
+
expected.add_edge(0, 1, 0)
|
| 352 |
+
expected.add_edge(0, 1, 5)
|
| 353 |
+
expected.add_edge(0, 1, 2) # keyed as 2 b/c 2 edges already in G
|
| 354 |
+
expected.add_edge(0, 0, 0)
|
| 355 |
+
expected.add_edge(0, 0, 1) # this comes from (0, 2, 5)
|
| 356 |
+
assert edges_equal(actual.edges, expected.edges)
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
def test_node_attributes():
|
| 360 |
+
"""Tests that node contraction preserves node attributes."""
|
| 361 |
+
G = nx.cycle_graph(4)
|
| 362 |
+
# Add some data to the two nodes being contracted.
|
| 363 |
+
G.nodes[0]["foo"] = "bar"
|
| 364 |
+
G.nodes[1]["baz"] = "xyzzy"
|
| 365 |
+
actual = nx.contracted_nodes(G, 0, 1)
|
| 366 |
+
# We expect that contracting the nodes 0 and 1 in C_4 yields K_3, but
|
| 367 |
+
# with nodes labeled 0, 2, and 3, and with a -loop on 0.
|
| 368 |
+
expected = nx.complete_graph(3)
|
| 369 |
+
expected = nx.relabel_nodes(expected, {1: 2, 2: 3})
|
| 370 |
+
expected.add_edge(0, 0)
|
| 371 |
+
cdict = {1: {"baz": "xyzzy"}}
|
| 372 |
+
expected.nodes[0].update({"foo": "bar", "contraction": cdict})
|
| 373 |
+
assert nx.is_isomorphic(actual, expected)
|
| 374 |
+
assert actual.nodes == expected.nodes
|
| 375 |
+
|
| 376 |
+
|
| 377 |
+
def test_edge_attributes():
|
| 378 |
+
"""Tests that node contraction preserves edge attributes."""
|
| 379 |
+
# Shape: src1 --> dest <-- src2
|
| 380 |
+
G = nx.DiGraph([("src1", "dest"), ("src2", "dest")])
|
| 381 |
+
G["src1"]["dest"]["value"] = "src1-->dest"
|
| 382 |
+
G["src2"]["dest"]["value"] = "src2-->dest"
|
| 383 |
+
H = nx.MultiDiGraph(G)
|
| 384 |
+
|
| 385 |
+
G = nx.contracted_nodes(G, "src1", "src2") # New Shape: src1 --> dest
|
| 386 |
+
assert G.edges[("src1", "dest")]["value"] == "src1-->dest"
|
| 387 |
+
assert (
|
| 388 |
+
G.edges[("src1", "dest")]["contraction"][("src2", "dest")]["value"]
|
| 389 |
+
== "src2-->dest"
|
| 390 |
+
)
|
| 391 |
+
|
| 392 |
+
H = nx.contracted_nodes(H, "src1", "src2") # New Shape: src1 -(x2)-> dest
|
| 393 |
+
assert len(H.edges(("src1", "dest"))) == 2
|
| 394 |
+
|
| 395 |
+
|
| 396 |
+
def test_without_self_loops():
|
| 397 |
+
"""Tests for node contraction without preserving -loops."""
|
| 398 |
+
G = nx.cycle_graph(4)
|
| 399 |
+
actual = nx.contracted_nodes(G, 0, 1, self_loops=False)
|
| 400 |
+
expected = nx.complete_graph(3)
|
| 401 |
+
assert nx.is_isomorphic(actual, expected)
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
def test_contract_loop_graph():
|
| 405 |
+
"""Tests for node contraction when nodes have loops."""
|
| 406 |
+
G = nx.cycle_graph(4)
|
| 407 |
+
G.add_edge(0, 0)
|
| 408 |
+
actual = nx.contracted_nodes(G, 0, 1)
|
| 409 |
+
expected = nx.complete_graph([0, 2, 3])
|
| 410 |
+
expected.add_edge(0, 0)
|
| 411 |
+
expected.add_edge(0, 0)
|
| 412 |
+
assert edges_equal(actual.edges, expected.edges)
|
| 413 |
+
actual = nx.contracted_nodes(G, 1, 0)
|
| 414 |
+
expected = nx.complete_graph([1, 2, 3])
|
| 415 |
+
expected.add_edge(1, 1)
|
| 416 |
+
expected.add_edge(1, 1)
|
| 417 |
+
assert edges_equal(actual.edges, expected.edges)
|
| 418 |
+
|
| 419 |
+
|
| 420 |
+
def test_undirected_edge_contraction():
|
| 421 |
+
"""Tests for edge contraction in an undirected graph."""
|
| 422 |
+
G = nx.cycle_graph(4)
|
| 423 |
+
actual = nx.contracted_edge(G, (0, 1))
|
| 424 |
+
expected = nx.complete_graph(3)
|
| 425 |
+
expected.add_edge(0, 0)
|
| 426 |
+
assert nx.is_isomorphic(actual, expected)
|
| 427 |
+
|
| 428 |
+
|
| 429 |
+
def test_multigraph_edge_contraction():
|
| 430 |
+
"""Tests for edge contraction in a multigraph"""
|
| 431 |
+
G = nx.cycle_graph(4)
|
| 432 |
+
actual = nx.contracted_edge(G, (0, 1, 0))
|
| 433 |
+
expected = nx.complete_graph(3)
|
| 434 |
+
expected.add_edge(0, 0)
|
| 435 |
+
assert nx.is_isomorphic(actual, expected)
|
| 436 |
+
|
| 437 |
+
|
| 438 |
+
def test_nonexistent_edge():
|
| 439 |
+
"""Tests that attempting to contract a nonexistent edge raises an
|
| 440 |
+
exception.
|
| 441 |
+
|
| 442 |
+
"""
|
| 443 |
+
with pytest.raises(ValueError):
|
| 444 |
+
G = nx.cycle_graph(4)
|
| 445 |
+
nx.contracted_edge(G, (0, 2))
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/mis.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Algorithm to find a maximal (not maximum) independent set.
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
import networkx as nx
|
| 6 |
+
from networkx.utils import not_implemented_for, py_random_state
|
| 7 |
+
|
| 8 |
+
__all__ = ["maximal_independent_set"]
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@not_implemented_for("directed")
|
| 12 |
+
@py_random_state(2)
|
| 13 |
+
@nx._dispatch
|
| 14 |
+
def maximal_independent_set(G, nodes=None, seed=None):
|
| 15 |
+
"""Returns a random maximal independent set guaranteed to contain
|
| 16 |
+
a given set of nodes.
|
| 17 |
+
|
| 18 |
+
An independent set is a set of nodes such that the subgraph
|
| 19 |
+
of G induced by these nodes contains no edges. A maximal
|
| 20 |
+
independent set is an independent set such that it is not possible
|
| 21 |
+
to add a new node and still get an independent set.
|
| 22 |
+
|
| 23 |
+
Parameters
|
| 24 |
+
----------
|
| 25 |
+
G : NetworkX graph
|
| 26 |
+
|
| 27 |
+
nodes : list or iterable
|
| 28 |
+
Nodes that must be part of the independent set. This set of nodes
|
| 29 |
+
must be independent.
|
| 30 |
+
|
| 31 |
+
seed : integer, random_state, or None (default)
|
| 32 |
+
Indicator of random number generation state.
|
| 33 |
+
See :ref:`Randomness<randomness>`.
|
| 34 |
+
|
| 35 |
+
Returns
|
| 36 |
+
-------
|
| 37 |
+
indep_nodes : list
|
| 38 |
+
List of nodes that are part of a maximal independent set.
|
| 39 |
+
|
| 40 |
+
Raises
|
| 41 |
+
------
|
| 42 |
+
NetworkXUnfeasible
|
| 43 |
+
If the nodes in the provided list are not part of the graph or
|
| 44 |
+
do not form an independent set, an exception is raised.
|
| 45 |
+
|
| 46 |
+
NetworkXNotImplemented
|
| 47 |
+
If `G` is directed.
|
| 48 |
+
|
| 49 |
+
Examples
|
| 50 |
+
--------
|
| 51 |
+
>>> G = nx.path_graph(5)
|
| 52 |
+
>>> nx.maximal_independent_set(G) # doctest: +SKIP
|
| 53 |
+
[4, 0, 2]
|
| 54 |
+
>>> nx.maximal_independent_set(G, [1]) # doctest: +SKIP
|
| 55 |
+
[1, 3]
|
| 56 |
+
|
| 57 |
+
Notes
|
| 58 |
+
-----
|
| 59 |
+
This algorithm does not solve the maximum independent set problem.
|
| 60 |
+
|
| 61 |
+
"""
|
| 62 |
+
if not nodes:
|
| 63 |
+
nodes = {seed.choice(list(G))}
|
| 64 |
+
else:
|
| 65 |
+
nodes = set(nodes)
|
| 66 |
+
if not nodes.issubset(G):
|
| 67 |
+
raise nx.NetworkXUnfeasible(f"{nodes} is not a subset of the nodes of G")
|
| 68 |
+
neighbors = set.union(*[set(G.adj[v]) for v in nodes])
|
| 69 |
+
if set.intersection(neighbors, nodes):
|
| 70 |
+
raise nx.NetworkXUnfeasible(f"{nodes} is not an independent set of G")
|
| 71 |
+
indep_nodes = list(nodes)
|
| 72 |
+
available_nodes = set(G.nodes()).difference(neighbors.union(nodes))
|
| 73 |
+
while available_nodes:
|
| 74 |
+
node = seed.choice(list(available_nodes))
|
| 75 |
+
indep_nodes.append(node)
|
| 76 |
+
available_nodes.difference_update(list(G.adj[node]) + [node])
|
| 77 |
+
return indep_nodes
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/moral.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
r"""Function for computing the moral graph of a directed graph."""
|
| 2 |
+
|
| 3 |
+
import itertools
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
from networkx.utils import not_implemented_for
|
| 7 |
+
|
| 8 |
+
__all__ = ["moral_graph"]
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@not_implemented_for("undirected")
|
| 12 |
+
@nx._dispatch
|
| 13 |
+
def moral_graph(G):
|
| 14 |
+
r"""Return the Moral Graph
|
| 15 |
+
|
| 16 |
+
Returns the moralized graph of a given directed graph.
|
| 17 |
+
|
| 18 |
+
Parameters
|
| 19 |
+
----------
|
| 20 |
+
G : NetworkX graph
|
| 21 |
+
Directed graph
|
| 22 |
+
|
| 23 |
+
Returns
|
| 24 |
+
-------
|
| 25 |
+
H : NetworkX graph
|
| 26 |
+
The undirected moralized graph of G
|
| 27 |
+
|
| 28 |
+
Raises
|
| 29 |
+
------
|
| 30 |
+
NetworkXNotImplemented
|
| 31 |
+
If `G` is undirected.
|
| 32 |
+
|
| 33 |
+
Examples
|
| 34 |
+
--------
|
| 35 |
+
>>> G = nx.DiGraph([(1, 2), (2, 3), (2, 5), (3, 4), (4, 3)])
|
| 36 |
+
>>> G_moral = nx.moral_graph(G)
|
| 37 |
+
>>> G_moral.edges()
|
| 38 |
+
EdgeView([(1, 2), (2, 3), (2, 5), (2, 4), (3, 4)])
|
| 39 |
+
|
| 40 |
+
Notes
|
| 41 |
+
-----
|
| 42 |
+
A moral graph is an undirected graph H = (V, E) generated from a
|
| 43 |
+
directed Graph, where if a node has more than one parent node, edges
|
| 44 |
+
between these parent nodes are inserted and all directed edges become
|
| 45 |
+
undirected.
|
| 46 |
+
|
| 47 |
+
https://en.wikipedia.org/wiki/Moral_graph
|
| 48 |
+
|
| 49 |
+
References
|
| 50 |
+
----------
|
| 51 |
+
.. [1] Wray L. Buntine. 1995. Chain graphs for learning.
|
| 52 |
+
In Proceedings of the Eleventh conference on Uncertainty
|
| 53 |
+
in artificial intelligence (UAI'95)
|
| 54 |
+
"""
|
| 55 |
+
H = G.to_undirected()
|
| 56 |
+
for preds in G.pred.values():
|
| 57 |
+
predecessors_combinations = itertools.combinations(preds, r=2)
|
| 58 |
+
H.add_edges_from(predecessors_combinations)
|
| 59 |
+
return H
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/networkx/algorithms/node_classification.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" This module provides the functions for node classification problem.
|
| 2 |
+
|
| 3 |
+
The functions in this module are not imported
|
| 4 |
+
into the top level `networkx` namespace.
|
| 5 |
+
You can access these functions by importing
|
| 6 |
+
the `networkx.algorithms.node_classification` modules,
|
| 7 |
+
then accessing the functions as attributes of `node_classification`.
|
| 8 |
+
For example:
|
| 9 |
+
|
| 10 |
+
>>> from networkx.algorithms import node_classification
|
| 11 |
+
>>> G = nx.path_graph(4)
|
| 12 |
+
>>> G.edges()
|
| 13 |
+
EdgeView([(0, 1), (1, 2), (2, 3)])
|
| 14 |
+
>>> G.nodes[0]["label"] = "A"
|
| 15 |
+
>>> G.nodes[3]["label"] = "B"
|
| 16 |
+
>>> node_classification.harmonic_function(G)
|
| 17 |
+
['A', 'A', 'B', 'B']
|
| 18 |
+
|
| 19 |
+
References
|
| 20 |
+
----------
|
| 21 |
+
Zhu, X., Ghahramani, Z., & Lafferty, J. (2003, August).
|
| 22 |
+
Semi-supervised learning using gaussian fields and harmonic functions.
|
| 23 |
+
In ICML (Vol. 3, pp. 912-919).
|
| 24 |
+
"""
|
| 25 |
+
import networkx as nx
|
| 26 |
+
|
| 27 |
+
__all__ = ["harmonic_function", "local_and_global_consistency"]
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
@nx.utils.not_implemented_for("directed")
|
| 31 |
+
@nx._dispatch(node_attrs="label_name")
|
| 32 |
+
def harmonic_function(G, max_iter=30, label_name="label"):
|
| 33 |
+
"""Node classification by Harmonic function
|
| 34 |
+
|
| 35 |
+
Function for computing Harmonic function algorithm by Zhu et al.
|
| 36 |
+
|
| 37 |
+
Parameters
|
| 38 |
+
----------
|
| 39 |
+
G : NetworkX Graph
|
| 40 |
+
max_iter : int
|
| 41 |
+
maximum number of iterations allowed
|
| 42 |
+
label_name : string
|
| 43 |
+
name of target labels to predict
|
| 44 |
+
|
| 45 |
+
Returns
|
| 46 |
+
-------
|
| 47 |
+
predicted : list
|
| 48 |
+
List of length ``len(G)`` with the predicted labels for each node.
|
| 49 |
+
|
| 50 |
+
Raises
|
| 51 |
+
------
|
| 52 |
+
NetworkXError
|
| 53 |
+
If no nodes in `G` have attribute `label_name`.
|
| 54 |
+
|
| 55 |
+
Examples
|
| 56 |
+
--------
|
| 57 |
+
>>> from networkx.algorithms import node_classification
|
| 58 |
+
>>> G = nx.path_graph(4)
|
| 59 |
+
>>> G.nodes[0]["label"] = "A"
|
| 60 |
+
>>> G.nodes[3]["label"] = "B"
|
| 61 |
+
>>> G.nodes(data=True)
|
| 62 |
+
NodeDataView({0: {'label': 'A'}, 1: {}, 2: {}, 3: {'label': 'B'}})
|
| 63 |
+
>>> G.edges()
|
| 64 |
+
EdgeView([(0, 1), (1, 2), (2, 3)])
|
| 65 |
+
>>> predicted = node_classification.harmonic_function(G)
|
| 66 |
+
>>> predicted
|
| 67 |
+
['A', 'A', 'B', 'B']
|
| 68 |
+
|
| 69 |
+
References
|
| 70 |
+
----------
|
| 71 |
+
Zhu, X., Ghahramani, Z., & Lafferty, J. (2003, August).
|
| 72 |
+
Semi-supervised learning using gaussian fields and harmonic functions.
|
| 73 |
+
In ICML (Vol. 3, pp. 912-919).
|
| 74 |
+
"""
|
| 75 |
+
import numpy as np
|
| 76 |
+
import scipy as sp
|
| 77 |
+
|
| 78 |
+
X = nx.to_scipy_sparse_array(G) # adjacency matrix
|
| 79 |
+
labels, label_dict = _get_label_info(G, label_name)
|
| 80 |
+
|
| 81 |
+
if labels.shape[0] == 0:
|
| 82 |
+
raise nx.NetworkXError(
|
| 83 |
+
f"No node on the input graph is labeled by '{label_name}'."
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
n_samples = X.shape[0]
|
| 87 |
+
n_classes = label_dict.shape[0]
|
| 88 |
+
F = np.zeros((n_samples, n_classes))
|
| 89 |
+
|
| 90 |
+
# Build propagation matrix
|
| 91 |
+
degrees = X.sum(axis=0)
|
| 92 |
+
degrees[degrees == 0] = 1 # Avoid division by 0
|
| 93 |
+
# TODO: csr_array
|
| 94 |
+
D = sp.sparse.csr_array(sp.sparse.diags((1.0 / degrees), offsets=0))
|
| 95 |
+
P = (D @ X).tolil()
|
| 96 |
+
P[labels[:, 0]] = 0 # labels[:, 0] indicates IDs of labeled nodes
|
| 97 |
+
# Build base matrix
|
| 98 |
+
B = np.zeros((n_samples, n_classes))
|
| 99 |
+
B[labels[:, 0], labels[:, 1]] = 1
|
| 100 |
+
|
| 101 |
+
for _ in range(max_iter):
|
| 102 |
+
F = (P @ F) + B
|
| 103 |
+
|
| 104 |
+
return label_dict[np.argmax(F, axis=1)].tolist()
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
@nx.utils.not_implemented_for("directed")
|
| 108 |
+
@nx._dispatch(node_attrs="label_name")
|
| 109 |
+
def local_and_global_consistency(G, alpha=0.99, max_iter=30, label_name="label"):
|
| 110 |
+
"""Node classification by Local and Global Consistency
|
| 111 |
+
|
| 112 |
+
Function for computing Local and global consistency algorithm by Zhou et al.
|
| 113 |
+
|
| 114 |
+
Parameters
|
| 115 |
+
----------
|
| 116 |
+
G : NetworkX Graph
|
| 117 |
+
alpha : float
|
| 118 |
+
Clamping factor
|
| 119 |
+
max_iter : int
|
| 120 |
+
Maximum number of iterations allowed
|
| 121 |
+
label_name : string
|
| 122 |
+
Name of target labels to predict
|
| 123 |
+
|
| 124 |
+
Returns
|
| 125 |
+
-------
|
| 126 |
+
predicted : list
|
| 127 |
+
List of length ``len(G)`` with the predicted labels for each node.
|
| 128 |
+
|
| 129 |
+
Raises
|
| 130 |
+
------
|
| 131 |
+
NetworkXError
|
| 132 |
+
If no nodes in `G` have attribute `label_name`.
|
| 133 |
+
|
| 134 |
+
Examples
|
| 135 |
+
--------
|
| 136 |
+
>>> from networkx.algorithms import node_classification
|
| 137 |
+
>>> G = nx.path_graph(4)
|
| 138 |
+
>>> G.nodes[0]["label"] = "A"
|
| 139 |
+
>>> G.nodes[3]["label"] = "B"
|
| 140 |
+
>>> G.nodes(data=True)
|
| 141 |
+
NodeDataView({0: {'label': 'A'}, 1: {}, 2: {}, 3: {'label': 'B'}})
|
| 142 |
+
>>> G.edges()
|
| 143 |
+
EdgeView([(0, 1), (1, 2), (2, 3)])
|
| 144 |
+
>>> predicted = node_classification.local_and_global_consistency(G)
|
| 145 |
+
>>> predicted
|
| 146 |
+
['A', 'A', 'B', 'B']
|
| 147 |
+
|
| 148 |
+
References
|
| 149 |
+
----------
|
| 150 |
+
Zhou, D., Bousquet, O., Lal, T. N., Weston, J., & Schölkopf, B. (2004).
|
| 151 |
+
Learning with local and global consistency.
|
| 152 |
+
Advances in neural information processing systems, 16(16), 321-328.
|
| 153 |
+
"""
|
| 154 |
+
import numpy as np
|
| 155 |
+
import scipy as sp
|
| 156 |
+
|
| 157 |
+
X = nx.to_scipy_sparse_array(G) # adjacency matrix
|
| 158 |
+
labels, label_dict = _get_label_info(G, label_name)
|
| 159 |
+
|
| 160 |
+
if labels.shape[0] == 0:
|
| 161 |
+
raise nx.NetworkXError(
|
| 162 |
+
f"No node on the input graph is labeled by '{label_name}'."
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
n_samples = X.shape[0]
|
| 166 |
+
n_classes = label_dict.shape[0]
|
| 167 |
+
F = np.zeros((n_samples, n_classes))
|
| 168 |
+
|
| 169 |
+
# Build propagation matrix
|
| 170 |
+
degrees = X.sum(axis=0)
|
| 171 |
+
degrees[degrees == 0] = 1 # Avoid division by 0
|
| 172 |
+
# TODO: csr_array
|
| 173 |
+
D2 = np.sqrt(sp.sparse.csr_array(sp.sparse.diags((1.0 / degrees), offsets=0)))
|
| 174 |
+
P = alpha * ((D2 @ X) @ D2)
|
| 175 |
+
# Build base matrix
|
| 176 |
+
B = np.zeros((n_samples, n_classes))
|
| 177 |
+
B[labels[:, 0], labels[:, 1]] = 1 - alpha
|
| 178 |
+
|
| 179 |
+
for _ in range(max_iter):
|
| 180 |
+
F = (P @ F) + B
|
| 181 |
+
|
| 182 |
+
return label_dict[np.argmax(F, axis=1)].tolist()
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
def _get_label_info(G, label_name):
|
| 186 |
+
"""Get and return information of labels from the input graph
|
| 187 |
+
|
| 188 |
+
Parameters
|
| 189 |
+
----------
|
| 190 |
+
G : Network X graph
|
| 191 |
+
label_name : string
|
| 192 |
+
Name of the target label
|
| 193 |
+
|
| 194 |
+
Returns
|
| 195 |
+
-------
|
| 196 |
+
labels : numpy array, shape = [n_labeled_samples, 2]
|
| 197 |
+
Array of pairs of labeled node ID and label ID
|
| 198 |
+
label_dict : numpy array, shape = [n_classes]
|
| 199 |
+
Array of labels
|
| 200 |
+
i-th element contains the label corresponding label ID `i`
|
| 201 |
+
"""
|
| 202 |
+
import numpy as np
|
| 203 |
+
|
| 204 |
+
labels = []
|
| 205 |
+
label_to_id = {}
|
| 206 |
+
lid = 0
|
| 207 |
+
for i, n in enumerate(G.nodes(data=True)):
|
| 208 |
+
if label_name in n[1]:
|
| 209 |
+
label = n[1][label_name]
|
| 210 |
+
if label not in label_to_id:
|
| 211 |
+
label_to_id[label] = lid
|
| 212 |
+
lid += 1
|
| 213 |
+
labels.append([i, label_to_id[label]])
|
| 214 |
+
labels = np.array(labels)
|
| 215 |
+
label_dict = np.array(
|
| 216 |
+
[label for label, _ in sorted(label_to_id.items(), key=lambda x: x[1])]
|
| 217 |
+
)
|
| 218 |
+
return (labels, label_dict)
|