Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__init__.py +87 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/basic.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/cluster.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/covering.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/edgelist.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/generators.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/matching.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/matrix.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/projection.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/spectral.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/basic.py +322 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/centrality.py +290 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/cluster.py +278 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/covering.py +57 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/edgelist.py +360 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/extendability.py +105 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/generators.py +604 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/matching.py +590 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/matrix.py +168 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/projection.py +526 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/redundancy.py +112 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/spectral.py +69 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_betweenness_centrality.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_betweenness_centrality.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_betweenness_centrality_subset.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_closeness.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_group.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_load_centrality.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_voterank.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__init__.py +5 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/beamsearch.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/breadth_first_search.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/depth_first_search.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/edgebfs.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/edgedfs.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/beamsearch.py +90 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/breadth_first_search.py +575 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/depth_first_search.py +529 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/edgebfs.py +178 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/edgedfs.py +176 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__init__.py +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_beamsearch.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_bfs.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_dfs.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_edgebfs.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_edgedfs.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/test_beamsearch.py +25 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/test_bfs.py +203 -0
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__init__.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
r"""This module provides functions and operations for bipartite
|
| 2 |
+
graphs. Bipartite graphs `B = (U, V, E)` have two node sets `U,V` and edges in
|
| 3 |
+
`E` that only connect nodes from opposite sets. It is common in the literature
|
| 4 |
+
to use an spatial analogy referring to the two node sets as top and bottom nodes.
|
| 5 |
+
|
| 6 |
+
The bipartite algorithms are not imported into the networkx namespace
|
| 7 |
+
at the top level so the easiest way to use them is with:
|
| 8 |
+
|
| 9 |
+
>>> from networkx.algorithms import bipartite
|
| 10 |
+
|
| 11 |
+
NetworkX does not have a custom bipartite graph class but the Graph()
|
| 12 |
+
or DiGraph() classes can be used to represent bipartite graphs. However,
|
| 13 |
+
you have to keep track of which set each node belongs to, and make
|
| 14 |
+
sure that there is no edge between nodes of the same set. The convention used
|
| 15 |
+
in NetworkX is to use a node attribute named `bipartite` with values 0 or 1 to
|
| 16 |
+
identify the sets each node belongs to. This convention is not enforced in
|
| 17 |
+
the source code of bipartite functions, it's only a recommendation.
|
| 18 |
+
|
| 19 |
+
For example:
|
| 20 |
+
|
| 21 |
+
>>> B = nx.Graph()
|
| 22 |
+
>>> # Add nodes with the node attribute "bipartite"
|
| 23 |
+
>>> B.add_nodes_from([1, 2, 3, 4], bipartite=0)
|
| 24 |
+
>>> B.add_nodes_from(["a", "b", "c"], bipartite=1)
|
| 25 |
+
>>> # Add edges only between nodes of opposite node sets
|
| 26 |
+
>>> B.add_edges_from([(1, "a"), (1, "b"), (2, "b"), (2, "c"), (3, "c"), (4, "a")])
|
| 27 |
+
|
| 28 |
+
Many algorithms of the bipartite module of NetworkX require, as an argument, a
|
| 29 |
+
container with all the nodes that belong to one set, in addition to the bipartite
|
| 30 |
+
graph `B`. The functions in the bipartite package do not check that the node set
|
| 31 |
+
is actually correct nor that the input graph is actually bipartite.
|
| 32 |
+
If `B` is connected, you can find the two node sets using a two-coloring
|
| 33 |
+
algorithm:
|
| 34 |
+
|
| 35 |
+
>>> nx.is_connected(B)
|
| 36 |
+
True
|
| 37 |
+
>>> bottom_nodes, top_nodes = bipartite.sets(B)
|
| 38 |
+
|
| 39 |
+
However, if the input graph is not connected, there are more than one possible
|
| 40 |
+
colorations. This is the reason why we require the user to pass a container
|
| 41 |
+
with all nodes of one bipartite node set as an argument to most bipartite
|
| 42 |
+
functions. In the face of ambiguity, we refuse the temptation to guess and
|
| 43 |
+
raise an :exc:`AmbiguousSolution <networkx.AmbiguousSolution>`
|
| 44 |
+
Exception if the input graph for
|
| 45 |
+
:func:`bipartite.sets <networkx.algorithms.bipartite.basic.sets>`
|
| 46 |
+
is disconnected.
|
| 47 |
+
|
| 48 |
+
Using the `bipartite` node attribute, you can easily get the two node sets:
|
| 49 |
+
|
| 50 |
+
>>> top_nodes = {n for n, d in B.nodes(data=True) if d["bipartite"] == 0}
|
| 51 |
+
>>> bottom_nodes = set(B) - top_nodes
|
| 52 |
+
|
| 53 |
+
So you can easily use the bipartite algorithms that require, as an argument, a
|
| 54 |
+
container with all nodes that belong to one node set:
|
| 55 |
+
|
| 56 |
+
>>> print(round(bipartite.density(B, bottom_nodes), 2))
|
| 57 |
+
0.5
|
| 58 |
+
>>> G = bipartite.projected_graph(B, top_nodes)
|
| 59 |
+
|
| 60 |
+
All bipartite graph generators in NetworkX build bipartite graphs with the
|
| 61 |
+
`bipartite` node attribute. Thus, you can use the same approach:
|
| 62 |
+
|
| 63 |
+
>>> RB = bipartite.random_graph(5, 7, 0.2)
|
| 64 |
+
>>> RB_top = {n for n, d in RB.nodes(data=True) if d["bipartite"] == 0}
|
| 65 |
+
>>> RB_bottom = set(RB) - RB_top
|
| 66 |
+
>>> list(RB_top)
|
| 67 |
+
[0, 1, 2, 3, 4]
|
| 68 |
+
>>> list(RB_bottom)
|
| 69 |
+
[5, 6, 7, 8, 9, 10, 11]
|
| 70 |
+
|
| 71 |
+
For other bipartite graph generators see
|
| 72 |
+
:mod:`Generators <networkx.algorithms.bipartite.generators>`.
|
| 73 |
+
|
| 74 |
+
"""
|
| 75 |
+
|
| 76 |
+
from networkx.algorithms.bipartite.basic import *
|
| 77 |
+
from networkx.algorithms.bipartite.centrality import *
|
| 78 |
+
from networkx.algorithms.bipartite.cluster import *
|
| 79 |
+
from networkx.algorithms.bipartite.covering import *
|
| 80 |
+
from networkx.algorithms.bipartite.edgelist import *
|
| 81 |
+
from networkx.algorithms.bipartite.matching import *
|
| 82 |
+
from networkx.algorithms.bipartite.matrix import *
|
| 83 |
+
from networkx.algorithms.bipartite.projection import *
|
| 84 |
+
from networkx.algorithms.bipartite.redundancy import *
|
| 85 |
+
from networkx.algorithms.bipartite.spectral import *
|
| 86 |
+
from networkx.algorithms.bipartite.generators import *
|
| 87 |
+
from networkx.algorithms.bipartite.extendability import *
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/basic.cpython-311.pyc
ADDED
|
Binary file (11.3 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/cluster.cpython-311.pyc
ADDED
|
Binary file (9.63 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/covering.cpython-311.pyc
ADDED
|
Binary file (2.67 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/edgelist.cpython-311.pyc
ADDED
|
Binary file (13.9 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/generators.cpython-311.pyc
ADDED
|
Binary file (29.3 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/matching.cpython-311.pyc
ADDED
|
Binary file (21.7 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/matrix.cpython-311.pyc
ADDED
|
Binary file (8.48 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/projection.cpython-311.pyc
ADDED
|
Binary file (23.5 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/spectral.cpython-311.pyc
ADDED
|
Binary file (2.77 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/basic.py
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
==========================
|
| 3 |
+
Bipartite Graph Algorithms
|
| 4 |
+
==========================
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import networkx as nx
|
| 8 |
+
from networkx.algorithms.components import connected_components
|
| 9 |
+
from networkx.exception import AmbiguousSolution
|
| 10 |
+
|
| 11 |
+
__all__ = [
|
| 12 |
+
"is_bipartite",
|
| 13 |
+
"is_bipartite_node_set",
|
| 14 |
+
"color",
|
| 15 |
+
"sets",
|
| 16 |
+
"density",
|
| 17 |
+
"degrees",
|
| 18 |
+
]
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@nx._dispatchable
|
| 22 |
+
def color(G):
|
| 23 |
+
"""Returns a two-coloring of the graph.
|
| 24 |
+
|
| 25 |
+
Raises an exception if the graph is not bipartite.
|
| 26 |
+
|
| 27 |
+
Parameters
|
| 28 |
+
----------
|
| 29 |
+
G : NetworkX graph
|
| 30 |
+
|
| 31 |
+
Returns
|
| 32 |
+
-------
|
| 33 |
+
color : dictionary
|
| 34 |
+
A dictionary keyed by node with a 1 or 0 as data for each node color.
|
| 35 |
+
|
| 36 |
+
Raises
|
| 37 |
+
------
|
| 38 |
+
NetworkXError
|
| 39 |
+
If the graph is not two-colorable.
|
| 40 |
+
|
| 41 |
+
Examples
|
| 42 |
+
--------
|
| 43 |
+
>>> from networkx.algorithms import bipartite
|
| 44 |
+
>>> G = nx.path_graph(4)
|
| 45 |
+
>>> c = bipartite.color(G)
|
| 46 |
+
>>> print(c)
|
| 47 |
+
{0: 1, 1: 0, 2: 1, 3: 0}
|
| 48 |
+
|
| 49 |
+
You can use this to set a node attribute indicating the bipartite set:
|
| 50 |
+
|
| 51 |
+
>>> nx.set_node_attributes(G, c, "bipartite")
|
| 52 |
+
>>> print(G.nodes[0]["bipartite"])
|
| 53 |
+
1
|
| 54 |
+
>>> print(G.nodes[1]["bipartite"])
|
| 55 |
+
0
|
| 56 |
+
"""
|
| 57 |
+
if G.is_directed():
|
| 58 |
+
import itertools
|
| 59 |
+
|
| 60 |
+
def neighbors(v):
|
| 61 |
+
return itertools.chain.from_iterable([G.predecessors(v), G.successors(v)])
|
| 62 |
+
|
| 63 |
+
else:
|
| 64 |
+
neighbors = G.neighbors
|
| 65 |
+
|
| 66 |
+
color = {}
|
| 67 |
+
for n in G: # handle disconnected graphs
|
| 68 |
+
if n in color or len(G[n]) == 0: # skip isolates
|
| 69 |
+
continue
|
| 70 |
+
queue = [n]
|
| 71 |
+
color[n] = 1 # nodes seen with color (1 or 0)
|
| 72 |
+
while queue:
|
| 73 |
+
v = queue.pop()
|
| 74 |
+
c = 1 - color[v] # opposite color of node v
|
| 75 |
+
for w in neighbors(v):
|
| 76 |
+
if w in color:
|
| 77 |
+
if color[w] == color[v]:
|
| 78 |
+
raise nx.NetworkXError("Graph is not bipartite.")
|
| 79 |
+
else:
|
| 80 |
+
color[w] = c
|
| 81 |
+
queue.append(w)
|
| 82 |
+
# color isolates with 0
|
| 83 |
+
color.update(dict.fromkeys(nx.isolates(G), 0))
|
| 84 |
+
return color
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
@nx._dispatchable
|
| 88 |
+
def is_bipartite(G):
|
| 89 |
+
"""Returns True if graph G is bipartite, False if not.
|
| 90 |
+
|
| 91 |
+
Parameters
|
| 92 |
+
----------
|
| 93 |
+
G : NetworkX graph
|
| 94 |
+
|
| 95 |
+
Examples
|
| 96 |
+
--------
|
| 97 |
+
>>> from networkx.algorithms import bipartite
|
| 98 |
+
>>> G = nx.path_graph(4)
|
| 99 |
+
>>> print(bipartite.is_bipartite(G))
|
| 100 |
+
True
|
| 101 |
+
|
| 102 |
+
See Also
|
| 103 |
+
--------
|
| 104 |
+
color, is_bipartite_node_set
|
| 105 |
+
"""
|
| 106 |
+
try:
|
| 107 |
+
color(G)
|
| 108 |
+
return True
|
| 109 |
+
except nx.NetworkXError:
|
| 110 |
+
return False
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
@nx._dispatchable
|
| 114 |
+
def is_bipartite_node_set(G, nodes):
|
| 115 |
+
"""Returns True if nodes and G/nodes are a bipartition of G.
|
| 116 |
+
|
| 117 |
+
Parameters
|
| 118 |
+
----------
|
| 119 |
+
G : NetworkX graph
|
| 120 |
+
|
| 121 |
+
nodes: list or container
|
| 122 |
+
Check if nodes are a one of a bipartite set.
|
| 123 |
+
|
| 124 |
+
Examples
|
| 125 |
+
--------
|
| 126 |
+
>>> from networkx.algorithms import bipartite
|
| 127 |
+
>>> G = nx.path_graph(4)
|
| 128 |
+
>>> X = set([1, 3])
|
| 129 |
+
>>> bipartite.is_bipartite_node_set(G, X)
|
| 130 |
+
True
|
| 131 |
+
|
| 132 |
+
Notes
|
| 133 |
+
-----
|
| 134 |
+
An exception is raised if the input nodes are not distinct, because in this
|
| 135 |
+
case some bipartite algorithms will yield incorrect results.
|
| 136 |
+
For connected graphs the bipartite sets are unique. This function handles
|
| 137 |
+
disconnected graphs.
|
| 138 |
+
"""
|
| 139 |
+
S = set(nodes)
|
| 140 |
+
|
| 141 |
+
if len(S) < len(nodes):
|
| 142 |
+
# this should maybe just return False?
|
| 143 |
+
raise AmbiguousSolution(
|
| 144 |
+
"The input node set contains duplicates.\n"
|
| 145 |
+
"This may lead to incorrect results when using it in bipartite algorithms.\n"
|
| 146 |
+
"Consider using set(nodes) as the input"
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
for CC in (G.subgraph(c).copy() for c in connected_components(G)):
|
| 150 |
+
X, Y = sets(CC)
|
| 151 |
+
if not (
|
| 152 |
+
(X.issubset(S) and Y.isdisjoint(S)) or (Y.issubset(S) and X.isdisjoint(S))
|
| 153 |
+
):
|
| 154 |
+
return False
|
| 155 |
+
return True
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
@nx._dispatchable
|
| 159 |
+
def sets(G, top_nodes=None):
|
| 160 |
+
"""Returns bipartite node sets of graph G.
|
| 161 |
+
|
| 162 |
+
Raises an exception if the graph is not bipartite or if the input
|
| 163 |
+
graph is disconnected and thus more than one valid solution exists.
|
| 164 |
+
See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
|
| 165 |
+
for further details on how bipartite graphs are handled in NetworkX.
|
| 166 |
+
|
| 167 |
+
Parameters
|
| 168 |
+
----------
|
| 169 |
+
G : NetworkX graph
|
| 170 |
+
|
| 171 |
+
top_nodes : container, optional
|
| 172 |
+
Container with all nodes in one bipartite node set. If not supplied
|
| 173 |
+
it will be computed. But if more than one solution exists an exception
|
| 174 |
+
will be raised.
|
| 175 |
+
|
| 176 |
+
Returns
|
| 177 |
+
-------
|
| 178 |
+
X : set
|
| 179 |
+
Nodes from one side of the bipartite graph.
|
| 180 |
+
Y : set
|
| 181 |
+
Nodes from the other side.
|
| 182 |
+
|
| 183 |
+
Raises
|
| 184 |
+
------
|
| 185 |
+
AmbiguousSolution
|
| 186 |
+
Raised if the input bipartite graph is disconnected and no container
|
| 187 |
+
with all nodes in one bipartite set is provided. When determining
|
| 188 |
+
the nodes in each bipartite set more than one valid solution is
|
| 189 |
+
possible if the input graph is disconnected.
|
| 190 |
+
NetworkXError
|
| 191 |
+
Raised if the input graph is not bipartite.
|
| 192 |
+
|
| 193 |
+
Examples
|
| 194 |
+
--------
|
| 195 |
+
>>> from networkx.algorithms import bipartite
|
| 196 |
+
>>> G = nx.path_graph(4)
|
| 197 |
+
>>> X, Y = bipartite.sets(G)
|
| 198 |
+
>>> list(X)
|
| 199 |
+
[0, 2]
|
| 200 |
+
>>> list(Y)
|
| 201 |
+
[1, 3]
|
| 202 |
+
|
| 203 |
+
See Also
|
| 204 |
+
--------
|
| 205 |
+
color
|
| 206 |
+
|
| 207 |
+
"""
|
| 208 |
+
if G.is_directed():
|
| 209 |
+
is_connected = nx.is_weakly_connected
|
| 210 |
+
else:
|
| 211 |
+
is_connected = nx.is_connected
|
| 212 |
+
if top_nodes is not None:
|
| 213 |
+
X = set(top_nodes)
|
| 214 |
+
Y = set(G) - X
|
| 215 |
+
else:
|
| 216 |
+
if not is_connected(G):
|
| 217 |
+
msg = "Disconnected graph: Ambiguous solution for bipartite sets."
|
| 218 |
+
raise nx.AmbiguousSolution(msg)
|
| 219 |
+
c = color(G)
|
| 220 |
+
X = {n for n, is_top in c.items() if is_top}
|
| 221 |
+
Y = {n for n, is_top in c.items() if not is_top}
|
| 222 |
+
return (X, Y)
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
@nx._dispatchable(graphs="B")
|
| 226 |
+
def density(B, nodes):
|
| 227 |
+
"""Returns density of bipartite graph B.
|
| 228 |
+
|
| 229 |
+
Parameters
|
| 230 |
+
----------
|
| 231 |
+
B : NetworkX graph
|
| 232 |
+
|
| 233 |
+
nodes: list or container
|
| 234 |
+
Nodes in one node set of the bipartite graph.
|
| 235 |
+
|
| 236 |
+
Returns
|
| 237 |
+
-------
|
| 238 |
+
d : float
|
| 239 |
+
The bipartite density
|
| 240 |
+
|
| 241 |
+
Examples
|
| 242 |
+
--------
|
| 243 |
+
>>> from networkx.algorithms import bipartite
|
| 244 |
+
>>> G = nx.complete_bipartite_graph(3, 2)
|
| 245 |
+
>>> X = set([0, 1, 2])
|
| 246 |
+
>>> bipartite.density(G, X)
|
| 247 |
+
1.0
|
| 248 |
+
>>> Y = set([3, 4])
|
| 249 |
+
>>> bipartite.density(G, Y)
|
| 250 |
+
1.0
|
| 251 |
+
|
| 252 |
+
Notes
|
| 253 |
+
-----
|
| 254 |
+
The container of nodes passed as argument must contain all nodes
|
| 255 |
+
in one of the two bipartite node sets to avoid ambiguity in the
|
| 256 |
+
case of disconnected graphs.
|
| 257 |
+
See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
|
| 258 |
+
for further details on how bipartite graphs are handled in NetworkX.
|
| 259 |
+
|
| 260 |
+
See Also
|
| 261 |
+
--------
|
| 262 |
+
color
|
| 263 |
+
"""
|
| 264 |
+
n = len(B)
|
| 265 |
+
m = nx.number_of_edges(B)
|
| 266 |
+
nb = len(nodes)
|
| 267 |
+
nt = n - nb
|
| 268 |
+
if m == 0: # includes cases n==0 and n==1
|
| 269 |
+
d = 0.0
|
| 270 |
+
else:
|
| 271 |
+
if B.is_directed():
|
| 272 |
+
d = m / (2 * nb * nt)
|
| 273 |
+
else:
|
| 274 |
+
d = m / (nb * nt)
|
| 275 |
+
return d
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
@nx._dispatchable(graphs="B", edge_attrs="weight")
|
| 279 |
+
def degrees(B, nodes, weight=None):
|
| 280 |
+
"""Returns the degrees of the two node sets in the bipartite graph B.
|
| 281 |
+
|
| 282 |
+
Parameters
|
| 283 |
+
----------
|
| 284 |
+
B : NetworkX graph
|
| 285 |
+
|
| 286 |
+
nodes: list or container
|
| 287 |
+
Nodes in one node set of the bipartite graph.
|
| 288 |
+
|
| 289 |
+
weight : string or None, optional (default=None)
|
| 290 |
+
The edge attribute that holds the numerical value used as a weight.
|
| 291 |
+
If None, then each edge has weight 1.
|
| 292 |
+
The degree is the sum of the edge weights adjacent to the node.
|
| 293 |
+
|
| 294 |
+
Returns
|
| 295 |
+
-------
|
| 296 |
+
(degX,degY) : tuple of dictionaries
|
| 297 |
+
The degrees of the two bipartite sets as dictionaries keyed by node.
|
| 298 |
+
|
| 299 |
+
Examples
|
| 300 |
+
--------
|
| 301 |
+
>>> from networkx.algorithms import bipartite
|
| 302 |
+
>>> G = nx.complete_bipartite_graph(3, 2)
|
| 303 |
+
>>> Y = set([3, 4])
|
| 304 |
+
>>> degX, degY = bipartite.degrees(G, Y)
|
| 305 |
+
>>> dict(degX)
|
| 306 |
+
{0: 2, 1: 2, 2: 2}
|
| 307 |
+
|
| 308 |
+
Notes
|
| 309 |
+
-----
|
| 310 |
+
The container of nodes passed as argument must contain all nodes
|
| 311 |
+
in one of the two bipartite node sets to avoid ambiguity in the
|
| 312 |
+
case of disconnected graphs.
|
| 313 |
+
See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
|
| 314 |
+
for further details on how bipartite graphs are handled in NetworkX.
|
| 315 |
+
|
| 316 |
+
See Also
|
| 317 |
+
--------
|
| 318 |
+
color, density
|
| 319 |
+
"""
|
| 320 |
+
bottom = set(nodes)
|
| 321 |
+
top = set(B) - bottom
|
| 322 |
+
return (B.degree(top, weight), B.degree(bottom, weight))
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/centrality.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import networkx as nx
|
| 2 |
+
|
| 3 |
+
__all__ = ["degree_centrality", "betweenness_centrality", "closeness_centrality"]
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
@nx._dispatchable(name="bipartite_degree_centrality")
|
| 7 |
+
def degree_centrality(G, nodes):
|
| 8 |
+
r"""Compute the degree centrality for nodes in a bipartite network.
|
| 9 |
+
|
| 10 |
+
The degree centrality for a node `v` is the fraction of nodes
|
| 11 |
+
connected to it.
|
| 12 |
+
|
| 13 |
+
Parameters
|
| 14 |
+
----------
|
| 15 |
+
G : graph
|
| 16 |
+
A bipartite network
|
| 17 |
+
|
| 18 |
+
nodes : list or container
|
| 19 |
+
Container with all nodes in one bipartite node set.
|
| 20 |
+
|
| 21 |
+
Returns
|
| 22 |
+
-------
|
| 23 |
+
centrality : dictionary
|
| 24 |
+
Dictionary keyed by node with bipartite degree centrality as the value.
|
| 25 |
+
|
| 26 |
+
Examples
|
| 27 |
+
--------
|
| 28 |
+
>>> G = nx.wheel_graph(5)
|
| 29 |
+
>>> top_nodes = {0, 1, 2}
|
| 30 |
+
>>> nx.bipartite.degree_centrality(G, nodes=top_nodes)
|
| 31 |
+
{0: 2.0, 1: 1.5, 2: 1.5, 3: 1.0, 4: 1.0}
|
| 32 |
+
|
| 33 |
+
See Also
|
| 34 |
+
--------
|
| 35 |
+
betweenness_centrality
|
| 36 |
+
closeness_centrality
|
| 37 |
+
:func:`~networkx.algorithms.bipartite.basic.sets`
|
| 38 |
+
:func:`~networkx.algorithms.bipartite.basic.is_bipartite`
|
| 39 |
+
|
| 40 |
+
Notes
|
| 41 |
+
-----
|
| 42 |
+
The nodes input parameter must contain all nodes in one bipartite node set,
|
| 43 |
+
but the dictionary returned contains all nodes from both bipartite node
|
| 44 |
+
sets. See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
|
| 45 |
+
for further details on how bipartite graphs are handled in NetworkX.
|
| 46 |
+
|
| 47 |
+
For unipartite networks, the degree centrality values are
|
| 48 |
+
normalized by dividing by the maximum possible degree (which is
|
| 49 |
+
`n-1` where `n` is the number of nodes in G).
|
| 50 |
+
|
| 51 |
+
In the bipartite case, the maximum possible degree of a node in a
|
| 52 |
+
bipartite node set is the number of nodes in the opposite node set
|
| 53 |
+
[1]_. The degree centrality for a node `v` in the bipartite
|
| 54 |
+
sets `U` with `n` nodes and `V` with `m` nodes is
|
| 55 |
+
|
| 56 |
+
.. math::
|
| 57 |
+
|
| 58 |
+
d_{v} = \frac{deg(v)}{m}, \mbox{for} v \in U ,
|
| 59 |
+
|
| 60 |
+
d_{v} = \frac{deg(v)}{n}, \mbox{for} v \in V ,
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
where `deg(v)` is the degree of node `v`.
|
| 64 |
+
|
| 65 |
+
References
|
| 66 |
+
----------
|
| 67 |
+
.. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation
|
| 68 |
+
Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook
|
| 69 |
+
of Social Network Analysis. Sage Publications.
|
| 70 |
+
https://dx.doi.org/10.4135/9781446294413.n28
|
| 71 |
+
"""
|
| 72 |
+
top = set(nodes)
|
| 73 |
+
bottom = set(G) - top
|
| 74 |
+
s = 1.0 / len(bottom)
|
| 75 |
+
centrality = {n: d * s for n, d in G.degree(top)}
|
| 76 |
+
s = 1.0 / len(top)
|
| 77 |
+
centrality.update({n: d * s for n, d in G.degree(bottom)})
|
| 78 |
+
return centrality
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
@nx._dispatchable(name="bipartite_betweenness_centrality")
|
| 82 |
+
def betweenness_centrality(G, nodes):
|
| 83 |
+
r"""Compute betweenness centrality for nodes in a bipartite network.
|
| 84 |
+
|
| 85 |
+
Betweenness centrality of a node `v` is the sum of the
|
| 86 |
+
fraction of all-pairs shortest paths that pass through `v`.
|
| 87 |
+
|
| 88 |
+
Values of betweenness are normalized by the maximum possible
|
| 89 |
+
value which for bipartite graphs is limited by the relative size
|
| 90 |
+
of the two node sets [1]_.
|
| 91 |
+
|
| 92 |
+
Let `n` be the number of nodes in the node set `U` and
|
| 93 |
+
`m` be the number of nodes in the node set `V`, then
|
| 94 |
+
nodes in `U` are normalized by dividing by
|
| 95 |
+
|
| 96 |
+
.. math::
|
| 97 |
+
|
| 98 |
+
\frac{1}{2} [m^2 (s + 1)^2 + m (s + 1)(2t - s - 1) - t (2s - t + 3)] ,
|
| 99 |
+
|
| 100 |
+
where
|
| 101 |
+
|
| 102 |
+
.. math::
|
| 103 |
+
|
| 104 |
+
s = (n - 1) \div m , t = (n - 1) \mod m ,
|
| 105 |
+
|
| 106 |
+
and nodes in `V` are normalized by dividing by
|
| 107 |
+
|
| 108 |
+
.. math::
|
| 109 |
+
|
| 110 |
+
\frac{1}{2} [n^2 (p + 1)^2 + n (p + 1)(2r - p - 1) - r (2p - r + 3)] ,
|
| 111 |
+
|
| 112 |
+
where,
|
| 113 |
+
|
| 114 |
+
.. math::
|
| 115 |
+
|
| 116 |
+
p = (m - 1) \div n , r = (m - 1) \mod n .
|
| 117 |
+
|
| 118 |
+
Parameters
|
| 119 |
+
----------
|
| 120 |
+
G : graph
|
| 121 |
+
A bipartite graph
|
| 122 |
+
|
| 123 |
+
nodes : list or container
|
| 124 |
+
Container with all nodes in one bipartite node set.
|
| 125 |
+
|
| 126 |
+
Returns
|
| 127 |
+
-------
|
| 128 |
+
betweenness : dictionary
|
| 129 |
+
Dictionary keyed by node with bipartite betweenness centrality
|
| 130 |
+
as the value.
|
| 131 |
+
|
| 132 |
+
Examples
|
| 133 |
+
--------
|
| 134 |
+
>>> G = nx.cycle_graph(4)
|
| 135 |
+
>>> top_nodes = {1, 2}
|
| 136 |
+
>>> nx.bipartite.betweenness_centrality(G, nodes=top_nodes)
|
| 137 |
+
{0: 0.25, 1: 0.25, 2: 0.25, 3: 0.25}
|
| 138 |
+
|
| 139 |
+
See Also
|
| 140 |
+
--------
|
| 141 |
+
degree_centrality
|
| 142 |
+
closeness_centrality
|
| 143 |
+
:func:`~networkx.algorithms.bipartite.basic.sets`
|
| 144 |
+
:func:`~networkx.algorithms.bipartite.basic.is_bipartite`
|
| 145 |
+
|
| 146 |
+
Notes
|
| 147 |
+
-----
|
| 148 |
+
The nodes input parameter must contain all nodes in one bipartite node set,
|
| 149 |
+
but the dictionary returned contains all nodes from both node sets.
|
| 150 |
+
See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
|
| 151 |
+
for further details on how bipartite graphs are handled in NetworkX.
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
References
|
| 155 |
+
----------
|
| 156 |
+
.. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation
|
| 157 |
+
Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook
|
| 158 |
+
of Social Network Analysis. Sage Publications.
|
| 159 |
+
https://dx.doi.org/10.4135/9781446294413.n28
|
| 160 |
+
"""
|
| 161 |
+
top = set(nodes)
|
| 162 |
+
bottom = set(G) - top
|
| 163 |
+
n = len(top)
|
| 164 |
+
m = len(bottom)
|
| 165 |
+
s, t = divmod(n - 1, m)
|
| 166 |
+
bet_max_top = (
|
| 167 |
+
((m**2) * ((s + 1) ** 2))
|
| 168 |
+
+ (m * (s + 1) * (2 * t - s - 1))
|
| 169 |
+
- (t * ((2 * s) - t + 3))
|
| 170 |
+
) / 2.0
|
| 171 |
+
p, r = divmod(m - 1, n)
|
| 172 |
+
bet_max_bot = (
|
| 173 |
+
((n**2) * ((p + 1) ** 2))
|
| 174 |
+
+ (n * (p + 1) * (2 * r - p - 1))
|
| 175 |
+
- (r * ((2 * p) - r + 3))
|
| 176 |
+
) / 2.0
|
| 177 |
+
betweenness = nx.betweenness_centrality(G, normalized=False, weight=None)
|
| 178 |
+
for node in top:
|
| 179 |
+
betweenness[node] /= bet_max_top
|
| 180 |
+
for node in bottom:
|
| 181 |
+
betweenness[node] /= bet_max_bot
|
| 182 |
+
return betweenness
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
@nx._dispatchable(name="bipartite_closeness_centrality")
|
| 186 |
+
def closeness_centrality(G, nodes, normalized=True):
|
| 187 |
+
r"""Compute the closeness centrality for nodes in a bipartite network.
|
| 188 |
+
|
| 189 |
+
The closeness of a node is the distance to all other nodes in the
|
| 190 |
+
graph or in the case that the graph is not connected to all other nodes
|
| 191 |
+
in the connected component containing that node.
|
| 192 |
+
|
| 193 |
+
Parameters
|
| 194 |
+
----------
|
| 195 |
+
G : graph
|
| 196 |
+
A bipartite network
|
| 197 |
+
|
| 198 |
+
nodes : list or container
|
| 199 |
+
Container with all nodes in one bipartite node set.
|
| 200 |
+
|
| 201 |
+
normalized : bool, optional
|
| 202 |
+
If True (default) normalize by connected component size.
|
| 203 |
+
|
| 204 |
+
Returns
|
| 205 |
+
-------
|
| 206 |
+
closeness : dictionary
|
| 207 |
+
Dictionary keyed by node with bipartite closeness centrality
|
| 208 |
+
as the value.
|
| 209 |
+
|
| 210 |
+
Examples
|
| 211 |
+
--------
|
| 212 |
+
>>> G = nx.wheel_graph(5)
|
| 213 |
+
>>> top_nodes = {0, 1, 2}
|
| 214 |
+
>>> nx.bipartite.closeness_centrality(G, nodes=top_nodes)
|
| 215 |
+
{0: 1.5, 1: 1.2, 2: 1.2, 3: 1.0, 4: 1.0}
|
| 216 |
+
|
| 217 |
+
See Also
|
| 218 |
+
--------
|
| 219 |
+
betweenness_centrality
|
| 220 |
+
degree_centrality
|
| 221 |
+
:func:`~networkx.algorithms.bipartite.basic.sets`
|
| 222 |
+
:func:`~networkx.algorithms.bipartite.basic.is_bipartite`
|
| 223 |
+
|
| 224 |
+
Notes
|
| 225 |
+
-----
|
| 226 |
+
The nodes input parameter must contain all nodes in one bipartite node set,
|
| 227 |
+
but the dictionary returned contains all nodes from both node sets.
|
| 228 |
+
See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
|
| 229 |
+
for further details on how bipartite graphs are handled in NetworkX.
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
Closeness centrality is normalized by the minimum distance possible.
|
| 233 |
+
In the bipartite case the minimum distance for a node in one bipartite
|
| 234 |
+
node set is 1 from all nodes in the other node set and 2 from all
|
| 235 |
+
other nodes in its own set [1]_. Thus the closeness centrality
|
| 236 |
+
for node `v` in the two bipartite sets `U` with
|
| 237 |
+
`n` nodes and `V` with `m` nodes is
|
| 238 |
+
|
| 239 |
+
.. math::
|
| 240 |
+
|
| 241 |
+
c_{v} = \frac{m + 2(n - 1)}{d}, \mbox{for} v \in U,
|
| 242 |
+
|
| 243 |
+
c_{v} = \frac{n + 2(m - 1)}{d}, \mbox{for} v \in V,
|
| 244 |
+
|
| 245 |
+
where `d` is the sum of the distances from `v` to all
|
| 246 |
+
other nodes.
|
| 247 |
+
|
| 248 |
+
Higher values of closeness indicate higher centrality.
|
| 249 |
+
|
| 250 |
+
As in the unipartite case, setting normalized=True causes the
|
| 251 |
+
values to normalized further to n-1 / size(G)-1 where n is the
|
| 252 |
+
number of nodes in the connected part of graph containing the
|
| 253 |
+
node. If the graph is not completely connected, this algorithm
|
| 254 |
+
computes the closeness centrality for each connected part
|
| 255 |
+
separately.
|
| 256 |
+
|
| 257 |
+
References
|
| 258 |
+
----------
|
| 259 |
+
.. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation
|
| 260 |
+
Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook
|
| 261 |
+
of Social Network Analysis. Sage Publications.
|
| 262 |
+
https://dx.doi.org/10.4135/9781446294413.n28
|
| 263 |
+
"""
|
| 264 |
+
closeness = {}
|
| 265 |
+
path_length = nx.single_source_shortest_path_length
|
| 266 |
+
top = set(nodes)
|
| 267 |
+
bottom = set(G) - top
|
| 268 |
+
n = len(top)
|
| 269 |
+
m = len(bottom)
|
| 270 |
+
for node in top:
|
| 271 |
+
sp = dict(path_length(G, node))
|
| 272 |
+
totsp = sum(sp.values())
|
| 273 |
+
if totsp > 0.0 and len(G) > 1:
|
| 274 |
+
closeness[node] = (m + 2 * (n - 1)) / totsp
|
| 275 |
+
if normalized:
|
| 276 |
+
s = (len(sp) - 1) / (len(G) - 1)
|
| 277 |
+
closeness[node] *= s
|
| 278 |
+
else:
|
| 279 |
+
closeness[node] = 0.0
|
| 280 |
+
for node in bottom:
|
| 281 |
+
sp = dict(path_length(G, node))
|
| 282 |
+
totsp = sum(sp.values())
|
| 283 |
+
if totsp > 0.0 and len(G) > 1:
|
| 284 |
+
closeness[node] = (n + 2 * (m - 1)) / totsp
|
| 285 |
+
if normalized:
|
| 286 |
+
s = (len(sp) - 1) / (len(G) - 1)
|
| 287 |
+
closeness[node] *= s
|
| 288 |
+
else:
|
| 289 |
+
closeness[node] = 0.0
|
| 290 |
+
return closeness
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/cluster.py
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Functions for computing clustering of pairs"""
|
| 2 |
+
|
| 3 |
+
import itertools
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
|
| 7 |
+
__all__ = [
|
| 8 |
+
"clustering",
|
| 9 |
+
"average_clustering",
|
| 10 |
+
"latapy_clustering",
|
| 11 |
+
"robins_alexander_clustering",
|
| 12 |
+
]
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def cc_dot(nu, nv):
|
| 16 |
+
return len(nu & nv) / len(nu | nv)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def cc_max(nu, nv):
|
| 20 |
+
return len(nu & nv) / max(len(nu), len(nv))
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def cc_min(nu, nv):
|
| 24 |
+
return len(nu & nv) / min(len(nu), len(nv))
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
modes = {"dot": cc_dot, "min": cc_min, "max": cc_max}
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
@nx._dispatchable
|
| 31 |
+
def latapy_clustering(G, nodes=None, mode="dot"):
|
| 32 |
+
r"""Compute a bipartite clustering coefficient for nodes.
|
| 33 |
+
|
| 34 |
+
The bipartite clustering coefficient is a measure of local density
|
| 35 |
+
of connections defined as [1]_:
|
| 36 |
+
|
| 37 |
+
.. math::
|
| 38 |
+
|
| 39 |
+
c_u = \frac{\sum_{v \in N(N(u))} c_{uv} }{|N(N(u))|}
|
| 40 |
+
|
| 41 |
+
where `N(N(u))` are the second order neighbors of `u` in `G` excluding `u`,
|
| 42 |
+
and `c_{uv}` is the pairwise clustering coefficient between nodes
|
| 43 |
+
`u` and `v`.
|
| 44 |
+
|
| 45 |
+
The mode selects the function for `c_{uv}` which can be:
|
| 46 |
+
|
| 47 |
+
`dot`:
|
| 48 |
+
|
| 49 |
+
.. math::
|
| 50 |
+
|
| 51 |
+
c_{uv}=\frac{|N(u)\cap N(v)|}{|N(u) \cup N(v)|}
|
| 52 |
+
|
| 53 |
+
`min`:
|
| 54 |
+
|
| 55 |
+
.. math::
|
| 56 |
+
|
| 57 |
+
c_{uv}=\frac{|N(u)\cap N(v)|}{min(|N(u)|,|N(v)|)}
|
| 58 |
+
|
| 59 |
+
`max`:
|
| 60 |
+
|
| 61 |
+
.. math::
|
| 62 |
+
|
| 63 |
+
c_{uv}=\frac{|N(u)\cap N(v)|}{max(|N(u)|,|N(v)|)}
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
Parameters
|
| 67 |
+
----------
|
| 68 |
+
G : graph
|
| 69 |
+
A bipartite graph
|
| 70 |
+
|
| 71 |
+
nodes : list or iterable (optional)
|
| 72 |
+
Compute bipartite clustering for these nodes. The default
|
| 73 |
+
is all nodes in G.
|
| 74 |
+
|
| 75 |
+
mode : string
|
| 76 |
+
The pairwise bipartite clustering method to be used in the computation.
|
| 77 |
+
It must be "dot", "max", or "min".
|
| 78 |
+
|
| 79 |
+
Returns
|
| 80 |
+
-------
|
| 81 |
+
clustering : dictionary
|
| 82 |
+
A dictionary keyed by node with the clustering coefficient value.
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
Examples
|
| 86 |
+
--------
|
| 87 |
+
>>> from networkx.algorithms import bipartite
|
| 88 |
+
>>> G = nx.path_graph(4) # path graphs are bipartite
|
| 89 |
+
>>> c = bipartite.clustering(G)
|
| 90 |
+
>>> c[0]
|
| 91 |
+
0.5
|
| 92 |
+
>>> c = bipartite.clustering(G, mode="min")
|
| 93 |
+
>>> c[0]
|
| 94 |
+
1.0
|
| 95 |
+
|
| 96 |
+
See Also
|
| 97 |
+
--------
|
| 98 |
+
robins_alexander_clustering
|
| 99 |
+
average_clustering
|
| 100 |
+
networkx.algorithms.cluster.square_clustering
|
| 101 |
+
|
| 102 |
+
References
|
| 103 |
+
----------
|
| 104 |
+
.. [1] Latapy, Matthieu, Clémence Magnien, and Nathalie Del Vecchio (2008).
|
| 105 |
+
Basic notions for the analysis of large two-mode networks.
|
| 106 |
+
Social Networks 30(1), 31--48.
|
| 107 |
+
"""
|
| 108 |
+
if not nx.algorithms.bipartite.is_bipartite(G):
|
| 109 |
+
raise nx.NetworkXError("Graph is not bipartite")
|
| 110 |
+
|
| 111 |
+
try:
|
| 112 |
+
cc_func = modes[mode]
|
| 113 |
+
except KeyError as err:
|
| 114 |
+
raise nx.NetworkXError(
|
| 115 |
+
"Mode for bipartite clustering must be: dot, min or max"
|
| 116 |
+
) from err
|
| 117 |
+
|
| 118 |
+
if nodes is None:
|
| 119 |
+
nodes = G
|
| 120 |
+
ccs = {}
|
| 121 |
+
for v in nodes:
|
| 122 |
+
cc = 0.0
|
| 123 |
+
nbrs2 = {u for nbr in G[v] for u in G[nbr]} - {v}
|
| 124 |
+
for u in nbrs2:
|
| 125 |
+
cc += cc_func(set(G[u]), set(G[v]))
|
| 126 |
+
if cc > 0.0: # len(nbrs2)>0
|
| 127 |
+
cc /= len(nbrs2)
|
| 128 |
+
ccs[v] = cc
|
| 129 |
+
return ccs
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
clustering = latapy_clustering
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
@nx._dispatchable(name="bipartite_average_clustering")
|
| 136 |
+
def average_clustering(G, nodes=None, mode="dot"):
|
| 137 |
+
r"""Compute the average bipartite clustering coefficient.
|
| 138 |
+
|
| 139 |
+
A clustering coefficient for the whole graph is the average,
|
| 140 |
+
|
| 141 |
+
.. math::
|
| 142 |
+
|
| 143 |
+
C = \frac{1}{n}\sum_{v \in G} c_v,
|
| 144 |
+
|
| 145 |
+
where `n` is the number of nodes in `G`.
|
| 146 |
+
|
| 147 |
+
Similar measures for the two bipartite sets can be defined [1]_
|
| 148 |
+
|
| 149 |
+
.. math::
|
| 150 |
+
|
| 151 |
+
C_X = \frac{1}{|X|}\sum_{v \in X} c_v,
|
| 152 |
+
|
| 153 |
+
where `X` is a bipartite set of `G`.
|
| 154 |
+
|
| 155 |
+
Parameters
|
| 156 |
+
----------
|
| 157 |
+
G : graph
|
| 158 |
+
a bipartite graph
|
| 159 |
+
|
| 160 |
+
nodes : list or iterable, optional
|
| 161 |
+
A container of nodes to use in computing the average.
|
| 162 |
+
The nodes should be either the entire graph (the default) or one of the
|
| 163 |
+
bipartite sets.
|
| 164 |
+
|
| 165 |
+
mode : string
|
| 166 |
+
The pairwise bipartite clustering method.
|
| 167 |
+
It must be "dot", "max", or "min"
|
| 168 |
+
|
| 169 |
+
Returns
|
| 170 |
+
-------
|
| 171 |
+
clustering : float
|
| 172 |
+
The average bipartite clustering for the given set of nodes or the
|
| 173 |
+
entire graph if no nodes are specified.
|
| 174 |
+
|
| 175 |
+
Examples
|
| 176 |
+
--------
|
| 177 |
+
>>> from networkx.algorithms import bipartite
|
| 178 |
+
>>> G = nx.star_graph(3) # star graphs are bipartite
|
| 179 |
+
>>> bipartite.average_clustering(G)
|
| 180 |
+
0.75
|
| 181 |
+
>>> X, Y = bipartite.sets(G)
|
| 182 |
+
>>> bipartite.average_clustering(G, X)
|
| 183 |
+
0.0
|
| 184 |
+
>>> bipartite.average_clustering(G, Y)
|
| 185 |
+
1.0
|
| 186 |
+
|
| 187 |
+
See Also
|
| 188 |
+
--------
|
| 189 |
+
clustering
|
| 190 |
+
|
| 191 |
+
Notes
|
| 192 |
+
-----
|
| 193 |
+
The container of nodes passed to this function must contain all of the nodes
|
| 194 |
+
in one of the bipartite sets ("top" or "bottom") in order to compute
|
| 195 |
+
the correct average bipartite clustering coefficients.
|
| 196 |
+
See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
|
| 197 |
+
for further details on how bipartite graphs are handled in NetworkX.
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
References
|
| 201 |
+
----------
|
| 202 |
+
.. [1] Latapy, Matthieu, Clémence Magnien, and Nathalie Del Vecchio (2008).
|
| 203 |
+
Basic notions for the analysis of large two-mode networks.
|
| 204 |
+
Social Networks 30(1), 31--48.
|
| 205 |
+
"""
|
| 206 |
+
if nodes is None:
|
| 207 |
+
nodes = G
|
| 208 |
+
ccs = latapy_clustering(G, nodes=nodes, mode=mode)
|
| 209 |
+
return sum(ccs[v] for v in nodes) / len(nodes)
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
@nx._dispatchable
|
| 213 |
+
def robins_alexander_clustering(G):
|
| 214 |
+
r"""Compute the bipartite clustering of G.
|
| 215 |
+
|
| 216 |
+
Robins and Alexander [1]_ defined bipartite clustering coefficient as
|
| 217 |
+
four times the number of four cycles `C_4` divided by the number of
|
| 218 |
+
three paths `L_3` in a bipartite graph:
|
| 219 |
+
|
| 220 |
+
.. math::
|
| 221 |
+
|
| 222 |
+
CC_4 = \frac{4 * C_4}{L_3}
|
| 223 |
+
|
| 224 |
+
Parameters
|
| 225 |
+
----------
|
| 226 |
+
G : graph
|
| 227 |
+
a bipartite graph
|
| 228 |
+
|
| 229 |
+
Returns
|
| 230 |
+
-------
|
| 231 |
+
clustering : float
|
| 232 |
+
The Robins and Alexander bipartite clustering for the input graph.
|
| 233 |
+
|
| 234 |
+
Examples
|
| 235 |
+
--------
|
| 236 |
+
>>> from networkx.algorithms import bipartite
|
| 237 |
+
>>> G = nx.davis_southern_women_graph()
|
| 238 |
+
>>> print(round(bipartite.robins_alexander_clustering(G), 3))
|
| 239 |
+
0.468
|
| 240 |
+
|
| 241 |
+
See Also
|
| 242 |
+
--------
|
| 243 |
+
latapy_clustering
|
| 244 |
+
networkx.algorithms.cluster.square_clustering
|
| 245 |
+
|
| 246 |
+
References
|
| 247 |
+
----------
|
| 248 |
+
.. [1] Robins, G. and M. Alexander (2004). Small worlds among interlocking
|
| 249 |
+
directors: Network structure and distance in bipartite graphs.
|
| 250 |
+
Computational & Mathematical Organization Theory 10(1), 69–94.
|
| 251 |
+
|
| 252 |
+
"""
|
| 253 |
+
if G.order() < 4 or G.size() < 3:
|
| 254 |
+
return 0
|
| 255 |
+
L_3 = _threepaths(G)
|
| 256 |
+
if L_3 == 0:
|
| 257 |
+
return 0
|
| 258 |
+
C_4 = _four_cycles(G)
|
| 259 |
+
return (4.0 * C_4) / L_3
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
def _four_cycles(G):
|
| 263 |
+
cycles = 0
|
| 264 |
+
for v in G:
|
| 265 |
+
for u, w in itertools.combinations(G[v], 2):
|
| 266 |
+
cycles += len((set(G[u]) & set(G[w])) - {v})
|
| 267 |
+
return cycles / 4
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
def _threepaths(G):
|
| 271 |
+
paths = 0
|
| 272 |
+
for v in G:
|
| 273 |
+
for u in G[v]:
|
| 274 |
+
for w in set(G[u]) - {v}:
|
| 275 |
+
paths += len(set(G[w]) - {v, u})
|
| 276 |
+
# Divide by two because we count each three path twice
|
| 277 |
+
# one for each possible starting point
|
| 278 |
+
return paths / 2
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/covering.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Functions related to graph covers."""
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx.algorithms.bipartite.matching import hopcroft_karp_matching
|
| 5 |
+
from networkx.algorithms.covering import min_edge_cover as _min_edge_cover
|
| 6 |
+
from networkx.utils import not_implemented_for
|
| 7 |
+
|
| 8 |
+
__all__ = ["min_edge_cover"]
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@not_implemented_for("directed")
|
| 12 |
+
@not_implemented_for("multigraph")
|
| 13 |
+
@nx._dispatchable(name="bipartite_min_edge_cover")
|
| 14 |
+
def min_edge_cover(G, matching_algorithm=None):
|
| 15 |
+
"""Returns a set of edges which constitutes
|
| 16 |
+
the minimum edge cover of the graph.
|
| 17 |
+
|
| 18 |
+
The smallest edge cover can be found in polynomial time by finding
|
| 19 |
+
a maximum matching and extending it greedily so that all nodes
|
| 20 |
+
are covered.
|
| 21 |
+
|
| 22 |
+
Parameters
|
| 23 |
+
----------
|
| 24 |
+
G : NetworkX graph
|
| 25 |
+
An undirected bipartite graph.
|
| 26 |
+
|
| 27 |
+
matching_algorithm : function
|
| 28 |
+
A function that returns a maximum cardinality matching in a
|
| 29 |
+
given bipartite graph. The function must take one input, the
|
| 30 |
+
graph ``G``, and return a dictionary mapping each node to its
|
| 31 |
+
mate. If not specified,
|
| 32 |
+
:func:`~networkx.algorithms.bipartite.matching.hopcroft_karp_matching`
|
| 33 |
+
will be used. Other possibilities include
|
| 34 |
+
:func:`~networkx.algorithms.bipartite.matching.eppstein_matching`,
|
| 35 |
+
|
| 36 |
+
Returns
|
| 37 |
+
-------
|
| 38 |
+
set
|
| 39 |
+
A set of the edges in a minimum edge cover of the graph, given as
|
| 40 |
+
pairs of nodes. It contains both the edges `(u, v)` and `(v, u)`
|
| 41 |
+
for given nodes `u` and `v` among the edges of minimum edge cover.
|
| 42 |
+
|
| 43 |
+
Notes
|
| 44 |
+
-----
|
| 45 |
+
An edge cover of a graph is a set of edges such that every node of
|
| 46 |
+
the graph is incident to at least one edge of the set.
|
| 47 |
+
A minimum edge cover is an edge covering of smallest cardinality.
|
| 48 |
+
|
| 49 |
+
Due to its implementation, the worst-case running time of this algorithm
|
| 50 |
+
is bounded by the worst-case running time of the function
|
| 51 |
+
``matching_algorithm``.
|
| 52 |
+
"""
|
| 53 |
+
if G.order() == 0: # Special case for the empty graph
|
| 54 |
+
return set()
|
| 55 |
+
if matching_algorithm is None:
|
| 56 |
+
matching_algorithm = hopcroft_karp_matching
|
| 57 |
+
return _min_edge_cover(G, matching_algorithm=matching_algorithm)
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/edgelist.py
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
********************
|
| 3 |
+
Bipartite Edge Lists
|
| 4 |
+
********************
|
| 5 |
+
Read and write NetworkX graphs as bipartite edge lists.
|
| 6 |
+
|
| 7 |
+
Format
|
| 8 |
+
------
|
| 9 |
+
You can read or write three formats of edge lists with these functions.
|
| 10 |
+
|
| 11 |
+
Node pairs with no data::
|
| 12 |
+
|
| 13 |
+
1 2
|
| 14 |
+
|
| 15 |
+
Python dictionary as data::
|
| 16 |
+
|
| 17 |
+
1 2 {'weight':7, 'color':'green'}
|
| 18 |
+
|
| 19 |
+
Arbitrary data::
|
| 20 |
+
|
| 21 |
+
1 2 7 green
|
| 22 |
+
|
| 23 |
+
For each edge (u, v) the node u is assigned to part 0 and the node v to part 1.
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
__all__ = ["generate_edgelist", "write_edgelist", "parse_edgelist", "read_edgelist"]
|
| 27 |
+
|
| 28 |
+
import networkx as nx
|
| 29 |
+
from networkx.utils import not_implemented_for, open_file
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
@open_file(1, mode="wb")
|
| 33 |
+
def write_edgelist(G, path, comments="#", delimiter=" ", data=True, encoding="utf-8"):
|
| 34 |
+
"""Write a bipartite graph as a list of edges.
|
| 35 |
+
|
| 36 |
+
Parameters
|
| 37 |
+
----------
|
| 38 |
+
G : Graph
|
| 39 |
+
A NetworkX bipartite graph
|
| 40 |
+
path : file or string
|
| 41 |
+
File or filename to write. If a file is provided, it must be
|
| 42 |
+
opened in 'wb' mode. Filenames ending in .gz or .bz2 will be compressed.
|
| 43 |
+
comments : string, optional
|
| 44 |
+
The character used to indicate the start of a comment
|
| 45 |
+
delimiter : string, optional
|
| 46 |
+
The string used to separate values. The default is whitespace.
|
| 47 |
+
data : bool or list, optional
|
| 48 |
+
If False write no edge data.
|
| 49 |
+
If True write a string representation of the edge data dictionary..
|
| 50 |
+
If a list (or other iterable) is provided, write the keys specified
|
| 51 |
+
in the list.
|
| 52 |
+
encoding: string, optional
|
| 53 |
+
Specify which encoding to use when writing file.
|
| 54 |
+
|
| 55 |
+
Examples
|
| 56 |
+
--------
|
| 57 |
+
>>> G = nx.path_graph(4)
|
| 58 |
+
>>> G.add_nodes_from([0, 2], bipartite=0)
|
| 59 |
+
>>> G.add_nodes_from([1, 3], bipartite=1)
|
| 60 |
+
>>> nx.write_edgelist(G, "test.edgelist")
|
| 61 |
+
>>> fh = open("test.edgelist", "wb")
|
| 62 |
+
>>> nx.write_edgelist(G, fh)
|
| 63 |
+
>>> nx.write_edgelist(G, "test.edgelist.gz")
|
| 64 |
+
>>> nx.write_edgelist(G, "test.edgelist.gz", data=False)
|
| 65 |
+
|
| 66 |
+
>>> G = nx.Graph()
|
| 67 |
+
>>> G.add_edge(1, 2, weight=7, color="red")
|
| 68 |
+
>>> nx.write_edgelist(G, "test.edgelist", data=False)
|
| 69 |
+
>>> nx.write_edgelist(G, "test.edgelist", data=["color"])
|
| 70 |
+
>>> nx.write_edgelist(G, "test.edgelist", data=["color", "weight"])
|
| 71 |
+
|
| 72 |
+
See Also
|
| 73 |
+
--------
|
| 74 |
+
write_edgelist
|
| 75 |
+
generate_edgelist
|
| 76 |
+
"""
|
| 77 |
+
for line in generate_edgelist(G, delimiter, data):
|
| 78 |
+
line += "\n"
|
| 79 |
+
path.write(line.encode(encoding))
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
@not_implemented_for("directed")
|
| 83 |
+
def generate_edgelist(G, delimiter=" ", data=True):
|
| 84 |
+
"""Generate a single line of the bipartite graph G in edge list format.
|
| 85 |
+
|
| 86 |
+
Parameters
|
| 87 |
+
----------
|
| 88 |
+
G : NetworkX graph
|
| 89 |
+
The graph is assumed to have node attribute `part` set to 0,1 representing
|
| 90 |
+
the two graph parts
|
| 91 |
+
|
| 92 |
+
delimiter : string, optional
|
| 93 |
+
Separator for node labels
|
| 94 |
+
|
| 95 |
+
data : bool or list of keys
|
| 96 |
+
If False generate no edge data. If True use a dictionary
|
| 97 |
+
representation of edge data. If a list of keys use a list of data
|
| 98 |
+
values corresponding to the keys.
|
| 99 |
+
|
| 100 |
+
Returns
|
| 101 |
+
-------
|
| 102 |
+
lines : string
|
| 103 |
+
Lines of data in adjlist format.
|
| 104 |
+
|
| 105 |
+
Examples
|
| 106 |
+
--------
|
| 107 |
+
>>> from networkx.algorithms import bipartite
|
| 108 |
+
>>> G = nx.path_graph(4)
|
| 109 |
+
>>> G.add_nodes_from([0, 2], bipartite=0)
|
| 110 |
+
>>> G.add_nodes_from([1, 3], bipartite=1)
|
| 111 |
+
>>> G[1][2]["weight"] = 3
|
| 112 |
+
>>> G[2][3]["capacity"] = 12
|
| 113 |
+
>>> for line in bipartite.generate_edgelist(G, data=False):
|
| 114 |
+
... print(line)
|
| 115 |
+
0 1
|
| 116 |
+
2 1
|
| 117 |
+
2 3
|
| 118 |
+
|
| 119 |
+
>>> for line in bipartite.generate_edgelist(G):
|
| 120 |
+
... print(line)
|
| 121 |
+
0 1 {}
|
| 122 |
+
2 1 {'weight': 3}
|
| 123 |
+
2 3 {'capacity': 12}
|
| 124 |
+
|
| 125 |
+
>>> for line in bipartite.generate_edgelist(G, data=["weight"]):
|
| 126 |
+
... print(line)
|
| 127 |
+
0 1
|
| 128 |
+
2 1 3
|
| 129 |
+
2 3
|
| 130 |
+
"""
|
| 131 |
+
try:
|
| 132 |
+
part0 = [n for n, d in G.nodes.items() if d["bipartite"] == 0]
|
| 133 |
+
except BaseException as err:
|
| 134 |
+
raise AttributeError("Missing node attribute `bipartite`") from err
|
| 135 |
+
if data is True or data is False:
|
| 136 |
+
for n in part0:
|
| 137 |
+
for edge in G.edges(n, data=data):
|
| 138 |
+
yield delimiter.join(map(str, edge))
|
| 139 |
+
else:
|
| 140 |
+
for n in part0:
|
| 141 |
+
for u, v, d in G.edges(n, data=True):
|
| 142 |
+
edge = [u, v]
|
| 143 |
+
try:
|
| 144 |
+
edge.extend(d[k] for k in data)
|
| 145 |
+
except KeyError:
|
| 146 |
+
pass # missing data for this edge, should warn?
|
| 147 |
+
yield delimiter.join(map(str, edge))
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
@nx._dispatchable(name="bipartite_parse_edgelist", graphs=None, returns_graph=True)
|
| 151 |
+
def parse_edgelist(
|
| 152 |
+
lines, comments="#", delimiter=None, create_using=None, nodetype=None, data=True
|
| 153 |
+
):
|
| 154 |
+
"""Parse lines of an edge list representation of a bipartite graph.
|
| 155 |
+
|
| 156 |
+
Parameters
|
| 157 |
+
----------
|
| 158 |
+
lines : list or iterator of strings
|
| 159 |
+
Input data in edgelist format
|
| 160 |
+
comments : string, optional
|
| 161 |
+
Marker for comment lines
|
| 162 |
+
delimiter : string, optional
|
| 163 |
+
Separator for node labels
|
| 164 |
+
create_using: NetworkX graph container, optional
|
| 165 |
+
Use given NetworkX graph for holding nodes or edges.
|
| 166 |
+
nodetype : Python type, optional
|
| 167 |
+
Convert nodes to this type.
|
| 168 |
+
data : bool or list of (label,type) tuples
|
| 169 |
+
If False generate no edge data or if True use a dictionary
|
| 170 |
+
representation of edge data or a list tuples specifying dictionary
|
| 171 |
+
key names and types for edge data.
|
| 172 |
+
|
| 173 |
+
Returns
|
| 174 |
+
-------
|
| 175 |
+
G: NetworkX Graph
|
| 176 |
+
The bipartite graph corresponding to lines
|
| 177 |
+
|
| 178 |
+
Examples
|
| 179 |
+
--------
|
| 180 |
+
Edgelist with no data:
|
| 181 |
+
|
| 182 |
+
>>> from networkx.algorithms import bipartite
|
| 183 |
+
>>> lines = ["1 2", "2 3", "3 4"]
|
| 184 |
+
>>> G = bipartite.parse_edgelist(lines, nodetype=int)
|
| 185 |
+
>>> sorted(G.nodes())
|
| 186 |
+
[1, 2, 3, 4]
|
| 187 |
+
>>> sorted(G.nodes(data=True))
|
| 188 |
+
[(1, {'bipartite': 0}), (2, {'bipartite': 0}), (3, {'bipartite': 0}), (4, {'bipartite': 1})]
|
| 189 |
+
>>> sorted(G.edges())
|
| 190 |
+
[(1, 2), (2, 3), (3, 4)]
|
| 191 |
+
|
| 192 |
+
Edgelist with data in Python dictionary representation:
|
| 193 |
+
|
| 194 |
+
>>> lines = ["1 2 {'weight':3}", "2 3 {'weight':27}", "3 4 {'weight':3.0}"]
|
| 195 |
+
>>> G = bipartite.parse_edgelist(lines, nodetype=int)
|
| 196 |
+
>>> sorted(G.nodes())
|
| 197 |
+
[1, 2, 3, 4]
|
| 198 |
+
>>> sorted(G.edges(data=True))
|
| 199 |
+
[(1, 2, {'weight': 3}), (2, 3, {'weight': 27}), (3, 4, {'weight': 3.0})]
|
| 200 |
+
|
| 201 |
+
Edgelist with data in a list:
|
| 202 |
+
|
| 203 |
+
>>> lines = ["1 2 3", "2 3 27", "3 4 3.0"]
|
| 204 |
+
>>> G = bipartite.parse_edgelist(lines, nodetype=int, data=(("weight", float),))
|
| 205 |
+
>>> sorted(G.nodes())
|
| 206 |
+
[1, 2, 3, 4]
|
| 207 |
+
>>> sorted(G.edges(data=True))
|
| 208 |
+
[(1, 2, {'weight': 3.0}), (2, 3, {'weight': 27.0}), (3, 4, {'weight': 3.0})]
|
| 209 |
+
|
| 210 |
+
See Also
|
| 211 |
+
--------
|
| 212 |
+
"""
|
| 213 |
+
from ast import literal_eval
|
| 214 |
+
|
| 215 |
+
G = nx.empty_graph(0, create_using)
|
| 216 |
+
for line in lines:
|
| 217 |
+
p = line.find(comments)
|
| 218 |
+
if p >= 0:
|
| 219 |
+
line = line[:p]
|
| 220 |
+
if not len(line):
|
| 221 |
+
continue
|
| 222 |
+
# split line, should have 2 or more
|
| 223 |
+
s = line.rstrip("\n").split(delimiter)
|
| 224 |
+
if len(s) < 2:
|
| 225 |
+
continue
|
| 226 |
+
u = s.pop(0)
|
| 227 |
+
v = s.pop(0)
|
| 228 |
+
d = s
|
| 229 |
+
if nodetype is not None:
|
| 230 |
+
try:
|
| 231 |
+
u = nodetype(u)
|
| 232 |
+
v = nodetype(v)
|
| 233 |
+
except BaseException as err:
|
| 234 |
+
raise TypeError(
|
| 235 |
+
f"Failed to convert nodes {u},{v} to type {nodetype}."
|
| 236 |
+
) from err
|
| 237 |
+
|
| 238 |
+
if len(d) == 0 or data is False:
|
| 239 |
+
# no data or data type specified
|
| 240 |
+
edgedata = {}
|
| 241 |
+
elif data is True:
|
| 242 |
+
# no edge types specified
|
| 243 |
+
try: # try to evaluate as dictionary
|
| 244 |
+
edgedata = dict(literal_eval(" ".join(d)))
|
| 245 |
+
except BaseException as err:
|
| 246 |
+
raise TypeError(
|
| 247 |
+
f"Failed to convert edge data ({d}) to dictionary."
|
| 248 |
+
) from err
|
| 249 |
+
else:
|
| 250 |
+
# convert edge data to dictionary with specified keys and type
|
| 251 |
+
if len(d) != len(data):
|
| 252 |
+
raise IndexError(
|
| 253 |
+
f"Edge data {d} and data_keys {data} are not the same length"
|
| 254 |
+
)
|
| 255 |
+
edgedata = {}
|
| 256 |
+
for (edge_key, edge_type), edge_value in zip(data, d):
|
| 257 |
+
try:
|
| 258 |
+
edge_value = edge_type(edge_value)
|
| 259 |
+
except BaseException as err:
|
| 260 |
+
raise TypeError(
|
| 261 |
+
f"Failed to convert {edge_key} data "
|
| 262 |
+
f"{edge_value} to type {edge_type}."
|
| 263 |
+
) from err
|
| 264 |
+
edgedata.update({edge_key: edge_value})
|
| 265 |
+
G.add_node(u, bipartite=0)
|
| 266 |
+
G.add_node(v, bipartite=1)
|
| 267 |
+
G.add_edge(u, v, **edgedata)
|
| 268 |
+
return G
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
@open_file(0, mode="rb")
|
| 272 |
+
@nx._dispatchable(name="bipartite_read_edgelist", graphs=None, returns_graph=True)
|
| 273 |
+
def read_edgelist(
|
| 274 |
+
path,
|
| 275 |
+
comments="#",
|
| 276 |
+
delimiter=None,
|
| 277 |
+
create_using=None,
|
| 278 |
+
nodetype=None,
|
| 279 |
+
data=True,
|
| 280 |
+
edgetype=None,
|
| 281 |
+
encoding="utf-8",
|
| 282 |
+
):
|
| 283 |
+
"""Read a bipartite graph from a list of edges.
|
| 284 |
+
|
| 285 |
+
Parameters
|
| 286 |
+
----------
|
| 287 |
+
path : file or string
|
| 288 |
+
File or filename to read. If a file is provided, it must be
|
| 289 |
+
opened in 'rb' mode.
|
| 290 |
+
Filenames ending in .gz or .bz2 will be uncompressed.
|
| 291 |
+
comments : string, optional
|
| 292 |
+
The character used to indicate the start of a comment.
|
| 293 |
+
delimiter : string, optional
|
| 294 |
+
The string used to separate values. The default is whitespace.
|
| 295 |
+
create_using : Graph container, optional,
|
| 296 |
+
Use specified container to build graph. The default is networkx.Graph,
|
| 297 |
+
an undirected graph.
|
| 298 |
+
nodetype : int, float, str, Python type, optional
|
| 299 |
+
Convert node data from strings to specified type
|
| 300 |
+
data : bool or list of (label,type) tuples
|
| 301 |
+
Tuples specifying dictionary key names and types for edge data
|
| 302 |
+
edgetype : int, float, str, Python type, optional OBSOLETE
|
| 303 |
+
Convert edge data from strings to specified type and use as 'weight'
|
| 304 |
+
encoding: string, optional
|
| 305 |
+
Specify which encoding to use when reading file.
|
| 306 |
+
|
| 307 |
+
Returns
|
| 308 |
+
-------
|
| 309 |
+
G : graph
|
| 310 |
+
A networkx Graph or other type specified with create_using
|
| 311 |
+
|
| 312 |
+
Examples
|
| 313 |
+
--------
|
| 314 |
+
>>> from networkx.algorithms import bipartite
|
| 315 |
+
>>> G = nx.path_graph(4)
|
| 316 |
+
>>> G.add_nodes_from([0, 2], bipartite=0)
|
| 317 |
+
>>> G.add_nodes_from([1, 3], bipartite=1)
|
| 318 |
+
>>> bipartite.write_edgelist(G, "test.edgelist")
|
| 319 |
+
>>> G = bipartite.read_edgelist("test.edgelist")
|
| 320 |
+
|
| 321 |
+
>>> fh = open("test.edgelist", "rb")
|
| 322 |
+
>>> G = bipartite.read_edgelist(fh)
|
| 323 |
+
>>> fh.close()
|
| 324 |
+
|
| 325 |
+
>>> G = bipartite.read_edgelist("test.edgelist", nodetype=int)
|
| 326 |
+
|
| 327 |
+
Edgelist with data in a list:
|
| 328 |
+
|
| 329 |
+
>>> textline = "1 2 3"
|
| 330 |
+
>>> fh = open("test.edgelist", "w")
|
| 331 |
+
>>> d = fh.write(textline)
|
| 332 |
+
>>> fh.close()
|
| 333 |
+
>>> G = bipartite.read_edgelist(
|
| 334 |
+
... "test.edgelist", nodetype=int, data=(("weight", float),)
|
| 335 |
+
... )
|
| 336 |
+
>>> list(G)
|
| 337 |
+
[1, 2]
|
| 338 |
+
>>> list(G.edges(data=True))
|
| 339 |
+
[(1, 2, {'weight': 3.0})]
|
| 340 |
+
|
| 341 |
+
See parse_edgelist() for more examples of formatting.
|
| 342 |
+
|
| 343 |
+
See Also
|
| 344 |
+
--------
|
| 345 |
+
parse_edgelist
|
| 346 |
+
|
| 347 |
+
Notes
|
| 348 |
+
-----
|
| 349 |
+
Since nodes must be hashable, the function nodetype must return hashable
|
| 350 |
+
types (e.g. int, float, str, frozenset - or tuples of those, etc.)
|
| 351 |
+
"""
|
| 352 |
+
lines = (line.decode(encoding) for line in path)
|
| 353 |
+
return parse_edgelist(
|
| 354 |
+
lines,
|
| 355 |
+
comments=comments,
|
| 356 |
+
delimiter=delimiter,
|
| 357 |
+
create_using=create_using,
|
| 358 |
+
nodetype=nodetype,
|
| 359 |
+
data=data,
|
| 360 |
+
)
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/extendability.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Provides a function for computing the extendability of a graph which is
|
| 2 |
+
undirected, simple, connected and bipartite and contains at least one perfect matching."""
|
| 3 |
+
|
| 4 |
+
import networkx as nx
|
| 5 |
+
from networkx.utils import not_implemented_for
|
| 6 |
+
|
| 7 |
+
__all__ = ["maximal_extendability"]
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@not_implemented_for("directed")
|
| 11 |
+
@not_implemented_for("multigraph")
|
| 12 |
+
@nx._dispatchable
|
| 13 |
+
def maximal_extendability(G):
|
| 14 |
+
"""Computes the extendability of a graph.
|
| 15 |
+
|
| 16 |
+
The extendability of a graph is defined as the maximum $k$ for which `G`
|
| 17 |
+
is $k$-extendable. Graph `G` is $k$-extendable if and only if `G` has a
|
| 18 |
+
perfect matching and every set of $k$ independent edges can be extended
|
| 19 |
+
to a perfect matching in `G`.
|
| 20 |
+
|
| 21 |
+
Parameters
|
| 22 |
+
----------
|
| 23 |
+
G : NetworkX Graph
|
| 24 |
+
A fully-connected bipartite graph without self-loops
|
| 25 |
+
|
| 26 |
+
Returns
|
| 27 |
+
-------
|
| 28 |
+
extendability : int
|
| 29 |
+
|
| 30 |
+
Raises
|
| 31 |
+
------
|
| 32 |
+
NetworkXError
|
| 33 |
+
If the graph `G` is disconnected.
|
| 34 |
+
If the graph `G` is not bipartite.
|
| 35 |
+
If the graph `G` does not contain a perfect matching.
|
| 36 |
+
If the residual graph of `G` is not strongly connected.
|
| 37 |
+
|
| 38 |
+
Notes
|
| 39 |
+
-----
|
| 40 |
+
Definition:
|
| 41 |
+
Let `G` be a simple, connected, undirected and bipartite graph with a perfect
|
| 42 |
+
matching M and bipartition (U,V). The residual graph of `G`, denoted by $G_M$,
|
| 43 |
+
is the graph obtained from G by directing the edges of M from V to U and the
|
| 44 |
+
edges that do not belong to M from U to V.
|
| 45 |
+
|
| 46 |
+
Lemma [1]_ :
|
| 47 |
+
Let M be a perfect matching of `G`. `G` is $k$-extendable if and only if its residual
|
| 48 |
+
graph $G_M$ is strongly connected and there are $k$ vertex-disjoint directed
|
| 49 |
+
paths between every vertex of U and every vertex of V.
|
| 50 |
+
|
| 51 |
+
Assuming that input graph `G` is undirected, simple, connected, bipartite and contains
|
| 52 |
+
a perfect matching M, this function constructs the residual graph $G_M$ of G and
|
| 53 |
+
returns the minimum value among the maximum vertex-disjoint directed paths between
|
| 54 |
+
every vertex of U and every vertex of V in $G_M$. By combining the definitions
|
| 55 |
+
and the lemma, this value represents the extendability of the graph `G`.
|
| 56 |
+
|
| 57 |
+
Time complexity O($n^3$ $m^2$)) where $n$ is the number of vertices
|
| 58 |
+
and $m$ is the number of edges.
|
| 59 |
+
|
| 60 |
+
References
|
| 61 |
+
----------
|
| 62 |
+
.. [1] "A polynomial algorithm for the extendability problem in bipartite graphs",
|
| 63 |
+
J. Lakhal, L. Litzler, Information Processing Letters, 1998.
|
| 64 |
+
.. [2] "On n-extendible graphs", M. D. Plummer, Discrete Mathematics, 31:201–210, 1980
|
| 65 |
+
https://doi.org/10.1016/0012-365X(80)90037-0
|
| 66 |
+
|
| 67 |
+
"""
|
| 68 |
+
if not nx.is_connected(G):
|
| 69 |
+
raise nx.NetworkXError("Graph G is not connected")
|
| 70 |
+
|
| 71 |
+
if not nx.bipartite.is_bipartite(G):
|
| 72 |
+
raise nx.NetworkXError("Graph G is not bipartite")
|
| 73 |
+
|
| 74 |
+
U, V = nx.bipartite.sets(G)
|
| 75 |
+
|
| 76 |
+
maximum_matching = nx.bipartite.hopcroft_karp_matching(G)
|
| 77 |
+
|
| 78 |
+
if not nx.is_perfect_matching(G, maximum_matching):
|
| 79 |
+
raise nx.NetworkXError("Graph G does not contain a perfect matching")
|
| 80 |
+
|
| 81 |
+
# list of edges in perfect matching, directed from V to U
|
| 82 |
+
pm = [(node, maximum_matching[node]) for node in V & maximum_matching.keys()]
|
| 83 |
+
|
| 84 |
+
# Direct all the edges of G, from V to U if in matching, else from U to V
|
| 85 |
+
directed_edges = [
|
| 86 |
+
(x, y) if (x in V and (x, y) in pm) or (x in U and (y, x) not in pm) else (y, x)
|
| 87 |
+
for x, y in G.edges
|
| 88 |
+
]
|
| 89 |
+
|
| 90 |
+
# Construct the residual graph of G
|
| 91 |
+
residual_G = nx.DiGraph()
|
| 92 |
+
residual_G.add_nodes_from(G)
|
| 93 |
+
residual_G.add_edges_from(directed_edges)
|
| 94 |
+
|
| 95 |
+
if not nx.is_strongly_connected(residual_G):
|
| 96 |
+
raise nx.NetworkXError("The residual graph of G is not strongly connected")
|
| 97 |
+
|
| 98 |
+
# For node-pairs between V & U, keep min of max number of node-disjoint paths
|
| 99 |
+
# Variable $k$ stands for the extendability of graph G
|
| 100 |
+
k = float("inf")
|
| 101 |
+
for u in U:
|
| 102 |
+
for v in V:
|
| 103 |
+
num_paths = sum(1 for _ in nx.node_disjoint_paths(residual_G, u, v))
|
| 104 |
+
k = k if k < num_paths else num_paths
|
| 105 |
+
return k
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/generators.py
ADDED
|
@@ -0,0 +1,604 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Generators and functions for bipartite graphs.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import math
|
| 6 |
+
import numbers
|
| 7 |
+
from functools import reduce
|
| 8 |
+
|
| 9 |
+
import networkx as nx
|
| 10 |
+
from networkx.utils import nodes_or_number, py_random_state
|
| 11 |
+
|
| 12 |
+
__all__ = [
|
| 13 |
+
"configuration_model",
|
| 14 |
+
"havel_hakimi_graph",
|
| 15 |
+
"reverse_havel_hakimi_graph",
|
| 16 |
+
"alternating_havel_hakimi_graph",
|
| 17 |
+
"preferential_attachment_graph",
|
| 18 |
+
"random_graph",
|
| 19 |
+
"gnmk_random_graph",
|
| 20 |
+
"complete_bipartite_graph",
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 25 |
+
@nodes_or_number([0, 1])
|
| 26 |
+
def complete_bipartite_graph(n1, n2, create_using=None):
|
| 27 |
+
"""Returns the complete bipartite graph `K_{n_1,n_2}`.
|
| 28 |
+
|
| 29 |
+
The graph is composed of two partitions with nodes 0 to (n1 - 1)
|
| 30 |
+
in the first and nodes n1 to (n1 + n2 - 1) in the second.
|
| 31 |
+
Each node in the first is connected to each node in the second.
|
| 32 |
+
|
| 33 |
+
Parameters
|
| 34 |
+
----------
|
| 35 |
+
n1, n2 : integer or iterable container of nodes
|
| 36 |
+
If integers, nodes are from `range(n1)` and `range(n1, n1 + n2)`.
|
| 37 |
+
If a container, the elements are the nodes.
|
| 38 |
+
create_using : NetworkX graph instance, (default: nx.Graph)
|
| 39 |
+
Return graph of this type.
|
| 40 |
+
|
| 41 |
+
Notes
|
| 42 |
+
-----
|
| 43 |
+
Nodes are the integers 0 to `n1 + n2 - 1` unless either n1 or n2 are
|
| 44 |
+
containers of nodes. If only one of n1 or n2 are integers, that
|
| 45 |
+
integer is replaced by `range` of that integer.
|
| 46 |
+
|
| 47 |
+
The nodes are assigned the attribute 'bipartite' with the value 0 or 1
|
| 48 |
+
to indicate which bipartite set the node belongs to.
|
| 49 |
+
|
| 50 |
+
This function is not imported in the main namespace.
|
| 51 |
+
To use it use nx.bipartite.complete_bipartite_graph
|
| 52 |
+
"""
|
| 53 |
+
G = nx.empty_graph(0, create_using)
|
| 54 |
+
if G.is_directed():
|
| 55 |
+
raise nx.NetworkXError("Directed Graph not supported")
|
| 56 |
+
|
| 57 |
+
n1, top = n1
|
| 58 |
+
n2, bottom = n2
|
| 59 |
+
if isinstance(n1, numbers.Integral) and isinstance(n2, numbers.Integral):
|
| 60 |
+
bottom = [n1 + i for i in bottom]
|
| 61 |
+
G.add_nodes_from(top, bipartite=0)
|
| 62 |
+
G.add_nodes_from(bottom, bipartite=1)
|
| 63 |
+
if len(G) != len(top) + len(bottom):
|
| 64 |
+
raise nx.NetworkXError("Inputs n1 and n2 must contain distinct nodes")
|
| 65 |
+
G.add_edges_from((u, v) for u in top for v in bottom)
|
| 66 |
+
G.graph["name"] = f"complete_bipartite_graph({len(top)}, {len(bottom)})"
|
| 67 |
+
return G
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
@py_random_state(3)
|
| 71 |
+
@nx._dispatchable(name="bipartite_configuration_model", graphs=None, returns_graph=True)
|
| 72 |
+
def configuration_model(aseq, bseq, create_using=None, seed=None):
|
| 73 |
+
"""Returns a random bipartite graph from two given degree sequences.
|
| 74 |
+
|
| 75 |
+
Parameters
|
| 76 |
+
----------
|
| 77 |
+
aseq : list
|
| 78 |
+
Degree sequence for node set A.
|
| 79 |
+
bseq : list
|
| 80 |
+
Degree sequence for node set B.
|
| 81 |
+
create_using : NetworkX graph instance, optional
|
| 82 |
+
Return graph of this type.
|
| 83 |
+
seed : integer, random_state, or None (default)
|
| 84 |
+
Indicator of random number generation state.
|
| 85 |
+
See :ref:`Randomness<randomness>`.
|
| 86 |
+
|
| 87 |
+
The graph is composed of two partitions. Set A has nodes 0 to
|
| 88 |
+
(len(aseq) - 1) and set B has nodes len(aseq) to (len(bseq) - 1).
|
| 89 |
+
Nodes from set A are connected to nodes in set B by choosing
|
| 90 |
+
randomly from the possible free stubs, one in A and one in B.
|
| 91 |
+
|
| 92 |
+
Notes
|
| 93 |
+
-----
|
| 94 |
+
The sum of the two sequences must be equal: sum(aseq)=sum(bseq)
|
| 95 |
+
If no graph type is specified use MultiGraph with parallel edges.
|
| 96 |
+
If you want a graph with no parallel edges use create_using=Graph()
|
| 97 |
+
but then the resulting degree sequences might not be exact.
|
| 98 |
+
|
| 99 |
+
The nodes are assigned the attribute 'bipartite' with the value 0 or 1
|
| 100 |
+
to indicate which bipartite set the node belongs to.
|
| 101 |
+
|
| 102 |
+
This function is not imported in the main namespace.
|
| 103 |
+
To use it use nx.bipartite.configuration_model
|
| 104 |
+
"""
|
| 105 |
+
G = nx.empty_graph(0, create_using, default=nx.MultiGraph)
|
| 106 |
+
if G.is_directed():
|
| 107 |
+
raise nx.NetworkXError("Directed Graph not supported")
|
| 108 |
+
|
| 109 |
+
# length and sum of each sequence
|
| 110 |
+
lena = len(aseq)
|
| 111 |
+
lenb = len(bseq)
|
| 112 |
+
suma = sum(aseq)
|
| 113 |
+
sumb = sum(bseq)
|
| 114 |
+
|
| 115 |
+
if not suma == sumb:
|
| 116 |
+
raise nx.NetworkXError(
|
| 117 |
+
f"invalid degree sequences, sum(aseq)!=sum(bseq),{suma},{sumb}"
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
G = _add_nodes_with_bipartite_label(G, lena, lenb)
|
| 121 |
+
|
| 122 |
+
if len(aseq) == 0 or max(aseq) == 0:
|
| 123 |
+
return G # done if no edges
|
| 124 |
+
|
| 125 |
+
# build lists of degree-repeated vertex numbers
|
| 126 |
+
stubs = [[v] * aseq[v] for v in range(lena)]
|
| 127 |
+
astubs = [x for subseq in stubs for x in subseq]
|
| 128 |
+
|
| 129 |
+
stubs = [[v] * bseq[v - lena] for v in range(lena, lena + lenb)]
|
| 130 |
+
bstubs = [x for subseq in stubs for x in subseq]
|
| 131 |
+
|
| 132 |
+
# shuffle lists
|
| 133 |
+
seed.shuffle(astubs)
|
| 134 |
+
seed.shuffle(bstubs)
|
| 135 |
+
|
| 136 |
+
G.add_edges_from([astubs[i], bstubs[i]] for i in range(suma))
|
| 137 |
+
|
| 138 |
+
G.name = "bipartite_configuration_model"
|
| 139 |
+
return G
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
@nx._dispatchable(name="bipartite_havel_hakimi_graph", graphs=None, returns_graph=True)
|
| 143 |
+
def havel_hakimi_graph(aseq, bseq, create_using=None):
|
| 144 |
+
"""Returns a bipartite graph from two given degree sequences using a
|
| 145 |
+
Havel-Hakimi style construction.
|
| 146 |
+
|
| 147 |
+
The graph is composed of two partitions. Set A has nodes 0 to
|
| 148 |
+
(len(aseq) - 1) and set B has nodes len(aseq) to (len(bseq) - 1).
|
| 149 |
+
Nodes from the set A are connected to nodes in the set B by
|
| 150 |
+
connecting the highest degree nodes in set A to the highest degree
|
| 151 |
+
nodes in set B until all stubs are connected.
|
| 152 |
+
|
| 153 |
+
Parameters
|
| 154 |
+
----------
|
| 155 |
+
aseq : list
|
| 156 |
+
Degree sequence for node set A.
|
| 157 |
+
bseq : list
|
| 158 |
+
Degree sequence for node set B.
|
| 159 |
+
create_using : NetworkX graph instance, optional
|
| 160 |
+
Return graph of this type.
|
| 161 |
+
|
| 162 |
+
Notes
|
| 163 |
+
-----
|
| 164 |
+
The sum of the two sequences must be equal: sum(aseq)=sum(bseq)
|
| 165 |
+
If no graph type is specified use MultiGraph with parallel edges.
|
| 166 |
+
If you want a graph with no parallel edges use create_using=Graph()
|
| 167 |
+
but then the resulting degree sequences might not be exact.
|
| 168 |
+
|
| 169 |
+
The nodes are assigned the attribute 'bipartite' with the value 0 or 1
|
| 170 |
+
to indicate which bipartite set the node belongs to.
|
| 171 |
+
|
| 172 |
+
This function is not imported in the main namespace.
|
| 173 |
+
To use it use nx.bipartite.havel_hakimi_graph
|
| 174 |
+
"""
|
| 175 |
+
G = nx.empty_graph(0, create_using, default=nx.MultiGraph)
|
| 176 |
+
if G.is_directed():
|
| 177 |
+
raise nx.NetworkXError("Directed Graph not supported")
|
| 178 |
+
|
| 179 |
+
# length of the each sequence
|
| 180 |
+
naseq = len(aseq)
|
| 181 |
+
nbseq = len(bseq)
|
| 182 |
+
|
| 183 |
+
suma = sum(aseq)
|
| 184 |
+
sumb = sum(bseq)
|
| 185 |
+
|
| 186 |
+
if not suma == sumb:
|
| 187 |
+
raise nx.NetworkXError(
|
| 188 |
+
f"invalid degree sequences, sum(aseq)!=sum(bseq),{suma},{sumb}"
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
G = _add_nodes_with_bipartite_label(G, naseq, nbseq)
|
| 192 |
+
|
| 193 |
+
if len(aseq) == 0 or max(aseq) == 0:
|
| 194 |
+
return G # done if no edges
|
| 195 |
+
|
| 196 |
+
# build list of degree-repeated vertex numbers
|
| 197 |
+
astubs = [[aseq[v], v] for v in range(naseq)]
|
| 198 |
+
bstubs = [[bseq[v - naseq], v] for v in range(naseq, naseq + nbseq)]
|
| 199 |
+
astubs.sort()
|
| 200 |
+
while astubs:
|
| 201 |
+
(degree, u) = astubs.pop() # take of largest degree node in the a set
|
| 202 |
+
if degree == 0:
|
| 203 |
+
break # done, all are zero
|
| 204 |
+
# connect the source to largest degree nodes in the b set
|
| 205 |
+
bstubs.sort()
|
| 206 |
+
for target in bstubs[-degree:]:
|
| 207 |
+
v = target[1]
|
| 208 |
+
G.add_edge(u, v)
|
| 209 |
+
target[0] -= 1 # note this updates bstubs too.
|
| 210 |
+
if target[0] == 0:
|
| 211 |
+
bstubs.remove(target)
|
| 212 |
+
|
| 213 |
+
G.name = "bipartite_havel_hakimi_graph"
|
| 214 |
+
return G
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 218 |
+
def reverse_havel_hakimi_graph(aseq, bseq, create_using=None):
|
| 219 |
+
"""Returns a bipartite graph from two given degree sequences using a
|
| 220 |
+
Havel-Hakimi style construction.
|
| 221 |
+
|
| 222 |
+
The graph is composed of two partitions. Set A has nodes 0 to
|
| 223 |
+
(len(aseq) - 1) and set B has nodes len(aseq) to (len(bseq) - 1).
|
| 224 |
+
Nodes from set A are connected to nodes in the set B by connecting
|
| 225 |
+
the highest degree nodes in set A to the lowest degree nodes in
|
| 226 |
+
set B until all stubs are connected.
|
| 227 |
+
|
| 228 |
+
Parameters
|
| 229 |
+
----------
|
| 230 |
+
aseq : list
|
| 231 |
+
Degree sequence for node set A.
|
| 232 |
+
bseq : list
|
| 233 |
+
Degree sequence for node set B.
|
| 234 |
+
create_using : NetworkX graph instance, optional
|
| 235 |
+
Return graph of this type.
|
| 236 |
+
|
| 237 |
+
Notes
|
| 238 |
+
-----
|
| 239 |
+
The sum of the two sequences must be equal: sum(aseq)=sum(bseq)
|
| 240 |
+
If no graph type is specified use MultiGraph with parallel edges.
|
| 241 |
+
If you want a graph with no parallel edges use create_using=Graph()
|
| 242 |
+
but then the resulting degree sequences might not be exact.
|
| 243 |
+
|
| 244 |
+
The nodes are assigned the attribute 'bipartite' with the value 0 or 1
|
| 245 |
+
to indicate which bipartite set the node belongs to.
|
| 246 |
+
|
| 247 |
+
This function is not imported in the main namespace.
|
| 248 |
+
To use it use nx.bipartite.reverse_havel_hakimi_graph
|
| 249 |
+
"""
|
| 250 |
+
G = nx.empty_graph(0, create_using, default=nx.MultiGraph)
|
| 251 |
+
if G.is_directed():
|
| 252 |
+
raise nx.NetworkXError("Directed Graph not supported")
|
| 253 |
+
|
| 254 |
+
# length of the each sequence
|
| 255 |
+
lena = len(aseq)
|
| 256 |
+
lenb = len(bseq)
|
| 257 |
+
suma = sum(aseq)
|
| 258 |
+
sumb = sum(bseq)
|
| 259 |
+
|
| 260 |
+
if not suma == sumb:
|
| 261 |
+
raise nx.NetworkXError(
|
| 262 |
+
f"invalid degree sequences, sum(aseq)!=sum(bseq),{suma},{sumb}"
|
| 263 |
+
)
|
| 264 |
+
|
| 265 |
+
G = _add_nodes_with_bipartite_label(G, lena, lenb)
|
| 266 |
+
|
| 267 |
+
if len(aseq) == 0 or max(aseq) == 0:
|
| 268 |
+
return G # done if no edges
|
| 269 |
+
|
| 270 |
+
# build list of degree-repeated vertex numbers
|
| 271 |
+
astubs = [[aseq[v], v] for v in range(lena)]
|
| 272 |
+
bstubs = [[bseq[v - lena], v] for v in range(lena, lena + lenb)]
|
| 273 |
+
astubs.sort()
|
| 274 |
+
bstubs.sort()
|
| 275 |
+
while astubs:
|
| 276 |
+
(degree, u) = astubs.pop() # take of largest degree node in the a set
|
| 277 |
+
if degree == 0:
|
| 278 |
+
break # done, all are zero
|
| 279 |
+
# connect the source to the smallest degree nodes in the b set
|
| 280 |
+
for target in bstubs[0:degree]:
|
| 281 |
+
v = target[1]
|
| 282 |
+
G.add_edge(u, v)
|
| 283 |
+
target[0] -= 1 # note this updates bstubs too.
|
| 284 |
+
if target[0] == 0:
|
| 285 |
+
bstubs.remove(target)
|
| 286 |
+
|
| 287 |
+
G.name = "bipartite_reverse_havel_hakimi_graph"
|
| 288 |
+
return G
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 292 |
+
def alternating_havel_hakimi_graph(aseq, bseq, create_using=None):
|
| 293 |
+
"""Returns a bipartite graph from two given degree sequences using
|
| 294 |
+
an alternating Havel-Hakimi style construction.
|
| 295 |
+
|
| 296 |
+
The graph is composed of two partitions. Set A has nodes 0 to
|
| 297 |
+
(len(aseq) - 1) and set B has nodes len(aseq) to (len(bseq) - 1).
|
| 298 |
+
Nodes from the set A are connected to nodes in the set B by
|
| 299 |
+
connecting the highest degree nodes in set A to alternatively the
|
| 300 |
+
highest and the lowest degree nodes in set B until all stubs are
|
| 301 |
+
connected.
|
| 302 |
+
|
| 303 |
+
Parameters
|
| 304 |
+
----------
|
| 305 |
+
aseq : list
|
| 306 |
+
Degree sequence for node set A.
|
| 307 |
+
bseq : list
|
| 308 |
+
Degree sequence for node set B.
|
| 309 |
+
create_using : NetworkX graph instance, optional
|
| 310 |
+
Return graph of this type.
|
| 311 |
+
|
| 312 |
+
Notes
|
| 313 |
+
-----
|
| 314 |
+
The sum of the two sequences must be equal: sum(aseq)=sum(bseq)
|
| 315 |
+
If no graph type is specified use MultiGraph with parallel edges.
|
| 316 |
+
If you want a graph with no parallel edges use create_using=Graph()
|
| 317 |
+
but then the resulting degree sequences might not be exact.
|
| 318 |
+
|
| 319 |
+
The nodes are assigned the attribute 'bipartite' with the value 0 or 1
|
| 320 |
+
to indicate which bipartite set the node belongs to.
|
| 321 |
+
|
| 322 |
+
This function is not imported in the main namespace.
|
| 323 |
+
To use it use nx.bipartite.alternating_havel_hakimi_graph
|
| 324 |
+
"""
|
| 325 |
+
G = nx.empty_graph(0, create_using, default=nx.MultiGraph)
|
| 326 |
+
if G.is_directed():
|
| 327 |
+
raise nx.NetworkXError("Directed Graph not supported")
|
| 328 |
+
|
| 329 |
+
# length of the each sequence
|
| 330 |
+
naseq = len(aseq)
|
| 331 |
+
nbseq = len(bseq)
|
| 332 |
+
suma = sum(aseq)
|
| 333 |
+
sumb = sum(bseq)
|
| 334 |
+
|
| 335 |
+
if not suma == sumb:
|
| 336 |
+
raise nx.NetworkXError(
|
| 337 |
+
f"invalid degree sequences, sum(aseq)!=sum(bseq),{suma},{sumb}"
|
| 338 |
+
)
|
| 339 |
+
|
| 340 |
+
G = _add_nodes_with_bipartite_label(G, naseq, nbseq)
|
| 341 |
+
|
| 342 |
+
if len(aseq) == 0 or max(aseq) == 0:
|
| 343 |
+
return G # done if no edges
|
| 344 |
+
# build list of degree-repeated vertex numbers
|
| 345 |
+
astubs = [[aseq[v], v] for v in range(naseq)]
|
| 346 |
+
bstubs = [[bseq[v - naseq], v] for v in range(naseq, naseq + nbseq)]
|
| 347 |
+
while astubs:
|
| 348 |
+
astubs.sort()
|
| 349 |
+
(degree, u) = astubs.pop() # take of largest degree node in the a set
|
| 350 |
+
if degree == 0:
|
| 351 |
+
break # done, all are zero
|
| 352 |
+
bstubs.sort()
|
| 353 |
+
small = bstubs[0 : degree // 2] # add these low degree targets
|
| 354 |
+
large = bstubs[(-degree + degree // 2) :] # now high degree targets
|
| 355 |
+
stubs = [x for z in zip(large, small) for x in z] # combine, sorry
|
| 356 |
+
if len(stubs) < len(small) + len(large): # check for zip truncation
|
| 357 |
+
stubs.append(large.pop())
|
| 358 |
+
for target in stubs:
|
| 359 |
+
v = target[1]
|
| 360 |
+
G.add_edge(u, v)
|
| 361 |
+
target[0] -= 1 # note this updates bstubs too.
|
| 362 |
+
if target[0] == 0:
|
| 363 |
+
bstubs.remove(target)
|
| 364 |
+
|
| 365 |
+
G.name = "bipartite_alternating_havel_hakimi_graph"
|
| 366 |
+
return G
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
@py_random_state(3)
|
| 370 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 371 |
+
def preferential_attachment_graph(aseq, p, create_using=None, seed=None):
|
| 372 |
+
"""Create a bipartite graph with a preferential attachment model from
|
| 373 |
+
a given single degree sequence.
|
| 374 |
+
|
| 375 |
+
The graph is composed of two partitions. Set A has nodes 0 to
|
| 376 |
+
(len(aseq) - 1) and set B has nodes starting with node len(aseq).
|
| 377 |
+
The number of nodes in set B is random.
|
| 378 |
+
|
| 379 |
+
Parameters
|
| 380 |
+
----------
|
| 381 |
+
aseq : list
|
| 382 |
+
Degree sequence for node set A.
|
| 383 |
+
p : float
|
| 384 |
+
Probability that a new bottom node is added.
|
| 385 |
+
create_using : NetworkX graph instance, optional
|
| 386 |
+
Return graph of this type.
|
| 387 |
+
seed : integer, random_state, or None (default)
|
| 388 |
+
Indicator of random number generation state.
|
| 389 |
+
See :ref:`Randomness<randomness>`.
|
| 390 |
+
|
| 391 |
+
References
|
| 392 |
+
----------
|
| 393 |
+
.. [1] Guillaume, J.L. and Latapy, M.,
|
| 394 |
+
Bipartite graphs as models of complex networks.
|
| 395 |
+
Physica A: Statistical Mechanics and its Applications,
|
| 396 |
+
2006, 371(2), pp.795-813.
|
| 397 |
+
.. [2] Jean-Loup Guillaume and Matthieu Latapy,
|
| 398 |
+
Bipartite structure of all complex networks,
|
| 399 |
+
Inf. Process. Lett. 90, 2004, pg. 215-221
|
| 400 |
+
https://doi.org/10.1016/j.ipl.2004.03.007
|
| 401 |
+
|
| 402 |
+
Notes
|
| 403 |
+
-----
|
| 404 |
+
The nodes are assigned the attribute 'bipartite' with the value 0 or 1
|
| 405 |
+
to indicate which bipartite set the node belongs to.
|
| 406 |
+
|
| 407 |
+
This function is not imported in the main namespace.
|
| 408 |
+
To use it use nx.bipartite.preferential_attachment_graph
|
| 409 |
+
"""
|
| 410 |
+
G = nx.empty_graph(0, create_using, default=nx.MultiGraph)
|
| 411 |
+
if G.is_directed():
|
| 412 |
+
raise nx.NetworkXError("Directed Graph not supported")
|
| 413 |
+
|
| 414 |
+
if p > 1:
|
| 415 |
+
raise nx.NetworkXError(f"probability {p} > 1")
|
| 416 |
+
|
| 417 |
+
naseq = len(aseq)
|
| 418 |
+
G = _add_nodes_with_bipartite_label(G, naseq, 0)
|
| 419 |
+
vv = [[v] * aseq[v] for v in range(naseq)]
|
| 420 |
+
while vv:
|
| 421 |
+
while vv[0]:
|
| 422 |
+
source = vv[0][0]
|
| 423 |
+
vv[0].remove(source)
|
| 424 |
+
if seed.random() < p or len(G) == naseq:
|
| 425 |
+
target = len(G)
|
| 426 |
+
G.add_node(target, bipartite=1)
|
| 427 |
+
G.add_edge(source, target)
|
| 428 |
+
else:
|
| 429 |
+
bb = [[b] * G.degree(b) for b in range(naseq, len(G))]
|
| 430 |
+
# flatten the list of lists into a list.
|
| 431 |
+
bbstubs = reduce(lambda x, y: x + y, bb)
|
| 432 |
+
# choose preferentially a bottom node.
|
| 433 |
+
target = seed.choice(bbstubs)
|
| 434 |
+
G.add_node(target, bipartite=1)
|
| 435 |
+
G.add_edge(source, target)
|
| 436 |
+
vv.remove(vv[0])
|
| 437 |
+
G.name = "bipartite_preferential_attachment_model"
|
| 438 |
+
return G
|
| 439 |
+
|
| 440 |
+
|
| 441 |
+
@py_random_state(3)
|
| 442 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 443 |
+
def random_graph(n, m, p, seed=None, directed=False):
|
| 444 |
+
"""Returns a bipartite random graph.
|
| 445 |
+
|
| 446 |
+
This is a bipartite version of the binomial (Erdős-Rényi) graph.
|
| 447 |
+
The graph is composed of two partitions. Set A has nodes 0 to
|
| 448 |
+
(n - 1) and set B has nodes n to (n + m - 1).
|
| 449 |
+
|
| 450 |
+
Parameters
|
| 451 |
+
----------
|
| 452 |
+
n : int
|
| 453 |
+
The number of nodes in the first bipartite set.
|
| 454 |
+
m : int
|
| 455 |
+
The number of nodes in the second bipartite set.
|
| 456 |
+
p : float
|
| 457 |
+
Probability for edge creation.
|
| 458 |
+
seed : integer, random_state, or None (default)
|
| 459 |
+
Indicator of random number generation state.
|
| 460 |
+
See :ref:`Randomness<randomness>`.
|
| 461 |
+
directed : bool, optional (default=False)
|
| 462 |
+
If True return a directed graph
|
| 463 |
+
|
| 464 |
+
Notes
|
| 465 |
+
-----
|
| 466 |
+
The bipartite random graph algorithm chooses each of the n*m (undirected)
|
| 467 |
+
or 2*nm (directed) possible edges with probability p.
|
| 468 |
+
|
| 469 |
+
This algorithm is $O(n+m)$ where $m$ is the expected number of edges.
|
| 470 |
+
|
| 471 |
+
The nodes are assigned the attribute 'bipartite' with the value 0 or 1
|
| 472 |
+
to indicate which bipartite set the node belongs to.
|
| 473 |
+
|
| 474 |
+
This function is not imported in the main namespace.
|
| 475 |
+
To use it use nx.bipartite.random_graph
|
| 476 |
+
|
| 477 |
+
See Also
|
| 478 |
+
--------
|
| 479 |
+
gnp_random_graph, configuration_model
|
| 480 |
+
|
| 481 |
+
References
|
| 482 |
+
----------
|
| 483 |
+
.. [1] Vladimir Batagelj and Ulrik Brandes,
|
| 484 |
+
"Efficient generation of large random networks",
|
| 485 |
+
Phys. Rev. E, 71, 036113, 2005.
|
| 486 |
+
"""
|
| 487 |
+
G = nx.Graph()
|
| 488 |
+
G = _add_nodes_with_bipartite_label(G, n, m)
|
| 489 |
+
if directed:
|
| 490 |
+
G = nx.DiGraph(G)
|
| 491 |
+
G.name = f"fast_gnp_random_graph({n},{m},{p})"
|
| 492 |
+
|
| 493 |
+
if p <= 0:
|
| 494 |
+
return G
|
| 495 |
+
if p >= 1:
|
| 496 |
+
return nx.complete_bipartite_graph(n, m)
|
| 497 |
+
|
| 498 |
+
lp = math.log(1.0 - p)
|
| 499 |
+
|
| 500 |
+
v = 0
|
| 501 |
+
w = -1
|
| 502 |
+
while v < n:
|
| 503 |
+
lr = math.log(1.0 - seed.random())
|
| 504 |
+
w = w + 1 + int(lr / lp)
|
| 505 |
+
while w >= m and v < n:
|
| 506 |
+
w = w - m
|
| 507 |
+
v = v + 1
|
| 508 |
+
if v < n:
|
| 509 |
+
G.add_edge(v, n + w)
|
| 510 |
+
|
| 511 |
+
if directed:
|
| 512 |
+
# use the same algorithm to
|
| 513 |
+
# add edges from the "m" to "n" set
|
| 514 |
+
v = 0
|
| 515 |
+
w = -1
|
| 516 |
+
while v < n:
|
| 517 |
+
lr = math.log(1.0 - seed.random())
|
| 518 |
+
w = w + 1 + int(lr / lp)
|
| 519 |
+
while w >= m and v < n:
|
| 520 |
+
w = w - m
|
| 521 |
+
v = v + 1
|
| 522 |
+
if v < n:
|
| 523 |
+
G.add_edge(n + w, v)
|
| 524 |
+
|
| 525 |
+
return G
|
| 526 |
+
|
| 527 |
+
|
| 528 |
+
@py_random_state(3)
|
| 529 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 530 |
+
def gnmk_random_graph(n, m, k, seed=None, directed=False):
|
| 531 |
+
"""Returns a random bipartite graph G_{n,m,k}.
|
| 532 |
+
|
| 533 |
+
Produces a bipartite graph chosen randomly out of the set of all graphs
|
| 534 |
+
with n top nodes, m bottom nodes, and k edges.
|
| 535 |
+
The graph is composed of two sets of nodes.
|
| 536 |
+
Set A has nodes 0 to (n - 1) and set B has nodes n to (n + m - 1).
|
| 537 |
+
|
| 538 |
+
Parameters
|
| 539 |
+
----------
|
| 540 |
+
n : int
|
| 541 |
+
The number of nodes in the first bipartite set.
|
| 542 |
+
m : int
|
| 543 |
+
The number of nodes in the second bipartite set.
|
| 544 |
+
k : int
|
| 545 |
+
The number of edges
|
| 546 |
+
seed : integer, random_state, or None (default)
|
| 547 |
+
Indicator of random number generation state.
|
| 548 |
+
See :ref:`Randomness<randomness>`.
|
| 549 |
+
directed : bool, optional (default=False)
|
| 550 |
+
If True return a directed graph
|
| 551 |
+
|
| 552 |
+
Examples
|
| 553 |
+
--------
|
| 554 |
+
from nx.algorithms import bipartite
|
| 555 |
+
G = bipartite.gnmk_random_graph(10,20,50)
|
| 556 |
+
|
| 557 |
+
See Also
|
| 558 |
+
--------
|
| 559 |
+
gnm_random_graph
|
| 560 |
+
|
| 561 |
+
Notes
|
| 562 |
+
-----
|
| 563 |
+
If k > m * n then a complete bipartite graph is returned.
|
| 564 |
+
|
| 565 |
+
This graph is a bipartite version of the `G_{nm}` random graph model.
|
| 566 |
+
|
| 567 |
+
The nodes are assigned the attribute 'bipartite' with the value 0 or 1
|
| 568 |
+
to indicate which bipartite set the node belongs to.
|
| 569 |
+
|
| 570 |
+
This function is not imported in the main namespace.
|
| 571 |
+
To use it use nx.bipartite.gnmk_random_graph
|
| 572 |
+
"""
|
| 573 |
+
G = nx.Graph()
|
| 574 |
+
G = _add_nodes_with_bipartite_label(G, n, m)
|
| 575 |
+
if directed:
|
| 576 |
+
G = nx.DiGraph(G)
|
| 577 |
+
G.name = f"bipartite_gnm_random_graph({n},{m},{k})"
|
| 578 |
+
if n == 1 or m == 1:
|
| 579 |
+
return G
|
| 580 |
+
max_edges = n * m # max_edges for bipartite networks
|
| 581 |
+
if k >= max_edges: # Maybe we should raise an exception here
|
| 582 |
+
return nx.complete_bipartite_graph(n, m, create_using=G)
|
| 583 |
+
|
| 584 |
+
top = [n for n, d in G.nodes(data=True) if d["bipartite"] == 0]
|
| 585 |
+
bottom = list(set(G) - set(top))
|
| 586 |
+
edge_count = 0
|
| 587 |
+
while edge_count < k:
|
| 588 |
+
# generate random edge,u,v
|
| 589 |
+
u = seed.choice(top)
|
| 590 |
+
v = seed.choice(bottom)
|
| 591 |
+
if v in G[u]:
|
| 592 |
+
continue
|
| 593 |
+
else:
|
| 594 |
+
G.add_edge(u, v)
|
| 595 |
+
edge_count += 1
|
| 596 |
+
return G
|
| 597 |
+
|
| 598 |
+
|
| 599 |
+
def _add_nodes_with_bipartite_label(G, lena, lenb):
|
| 600 |
+
G.add_nodes_from(range(lena + lenb))
|
| 601 |
+
b = dict(zip(range(lena), [0] * lena))
|
| 602 |
+
b.update(dict(zip(range(lena, lena + lenb), [1] * lenb)))
|
| 603 |
+
nx.set_node_attributes(G, b, "bipartite")
|
| 604 |
+
return G
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/matching.py
ADDED
|
@@ -0,0 +1,590 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This module uses material from the Wikipedia article Hopcroft--Karp algorithm
|
| 2 |
+
# <https://en.wikipedia.org/wiki/Hopcroft%E2%80%93Karp_algorithm>, accessed on
|
| 3 |
+
# January 3, 2015, which is released under the Creative Commons
|
| 4 |
+
# Attribution-Share-Alike License 3.0
|
| 5 |
+
# <http://creativecommons.org/licenses/by-sa/3.0/>. That article includes
|
| 6 |
+
# pseudocode, which has been translated into the corresponding Python code.
|
| 7 |
+
#
|
| 8 |
+
# Portions of this module use code from David Eppstein's Python Algorithms and
|
| 9 |
+
# Data Structures (PADS) library, which is dedicated to the public domain (for
|
| 10 |
+
# proof, see <http://www.ics.uci.edu/~eppstein/PADS/ABOUT-PADS.txt>).
|
| 11 |
+
"""Provides functions for computing maximum cardinality matchings and minimum
|
| 12 |
+
weight full matchings in a bipartite graph.
|
| 13 |
+
|
| 14 |
+
If you don't care about the particular implementation of the maximum matching
|
| 15 |
+
algorithm, simply use the :func:`maximum_matching`. If you do care, you can
|
| 16 |
+
import one of the named maximum matching algorithms directly.
|
| 17 |
+
|
| 18 |
+
For example, to find a maximum matching in the complete bipartite graph with
|
| 19 |
+
two vertices on the left and three vertices on the right:
|
| 20 |
+
|
| 21 |
+
>>> G = nx.complete_bipartite_graph(2, 3)
|
| 22 |
+
>>> left, right = nx.bipartite.sets(G)
|
| 23 |
+
>>> list(left)
|
| 24 |
+
[0, 1]
|
| 25 |
+
>>> list(right)
|
| 26 |
+
[2, 3, 4]
|
| 27 |
+
>>> nx.bipartite.maximum_matching(G)
|
| 28 |
+
{0: 2, 1: 3, 2: 0, 3: 1}
|
| 29 |
+
|
| 30 |
+
The dictionary returned by :func:`maximum_matching` includes a mapping for
|
| 31 |
+
vertices in both the left and right vertex sets.
|
| 32 |
+
|
| 33 |
+
Similarly, :func:`minimum_weight_full_matching` produces, for a complete
|
| 34 |
+
weighted bipartite graph, a matching whose cardinality is the cardinality of
|
| 35 |
+
the smaller of the two partitions, and for which the sum of the weights of the
|
| 36 |
+
edges included in the matching is minimal.
|
| 37 |
+
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
import collections
|
| 41 |
+
import itertools
|
| 42 |
+
|
| 43 |
+
import networkx as nx
|
| 44 |
+
from networkx.algorithms.bipartite import sets as bipartite_sets
|
| 45 |
+
from networkx.algorithms.bipartite.matrix import biadjacency_matrix
|
| 46 |
+
|
| 47 |
+
__all__ = [
|
| 48 |
+
"maximum_matching",
|
| 49 |
+
"hopcroft_karp_matching",
|
| 50 |
+
"eppstein_matching",
|
| 51 |
+
"to_vertex_cover",
|
| 52 |
+
"minimum_weight_full_matching",
|
| 53 |
+
]
|
| 54 |
+
|
| 55 |
+
INFINITY = float("inf")
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
@nx._dispatchable
|
| 59 |
+
def hopcroft_karp_matching(G, top_nodes=None):
|
| 60 |
+
"""Returns the maximum cardinality matching of the bipartite graph `G`.
|
| 61 |
+
|
| 62 |
+
A matching is a set of edges that do not share any nodes. A maximum
|
| 63 |
+
cardinality matching is a matching with the most edges possible. It
|
| 64 |
+
is not always unique. Finding a matching in a bipartite graph can be
|
| 65 |
+
treated as a networkx flow problem.
|
| 66 |
+
|
| 67 |
+
The functions ``hopcroft_karp_matching`` and ``maximum_matching``
|
| 68 |
+
are aliases of the same function.
|
| 69 |
+
|
| 70 |
+
Parameters
|
| 71 |
+
----------
|
| 72 |
+
G : NetworkX graph
|
| 73 |
+
|
| 74 |
+
Undirected bipartite graph
|
| 75 |
+
|
| 76 |
+
top_nodes : container of nodes
|
| 77 |
+
|
| 78 |
+
Container with all nodes in one bipartite node set. If not supplied
|
| 79 |
+
it will be computed. But if more than one solution exists an exception
|
| 80 |
+
will be raised.
|
| 81 |
+
|
| 82 |
+
Returns
|
| 83 |
+
-------
|
| 84 |
+
matches : dictionary
|
| 85 |
+
|
| 86 |
+
The matching is returned as a dictionary, `matches`, such that
|
| 87 |
+
``matches[v] == w`` if node `v` is matched to node `w`. Unmatched
|
| 88 |
+
nodes do not occur as a key in `matches`.
|
| 89 |
+
|
| 90 |
+
Raises
|
| 91 |
+
------
|
| 92 |
+
AmbiguousSolution
|
| 93 |
+
Raised if the input bipartite graph is disconnected and no container
|
| 94 |
+
with all nodes in one bipartite set is provided. When determining
|
| 95 |
+
the nodes in each bipartite set more than one valid solution is
|
| 96 |
+
possible if the input graph is disconnected.
|
| 97 |
+
|
| 98 |
+
Notes
|
| 99 |
+
-----
|
| 100 |
+
This function is implemented with the `Hopcroft--Karp matching algorithm
|
| 101 |
+
<https://en.wikipedia.org/wiki/Hopcroft%E2%80%93Karp_algorithm>`_ for
|
| 102 |
+
bipartite graphs.
|
| 103 |
+
|
| 104 |
+
See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
|
| 105 |
+
for further details on how bipartite graphs are handled in NetworkX.
|
| 106 |
+
|
| 107 |
+
See Also
|
| 108 |
+
--------
|
| 109 |
+
maximum_matching
|
| 110 |
+
hopcroft_karp_matching
|
| 111 |
+
eppstein_matching
|
| 112 |
+
|
| 113 |
+
References
|
| 114 |
+
----------
|
| 115 |
+
.. [1] John E. Hopcroft and Richard M. Karp. "An n^{5 / 2} Algorithm for
|
| 116 |
+
Maximum Matchings in Bipartite Graphs" In: **SIAM Journal of Computing**
|
| 117 |
+
2.4 (1973), pp. 225--231. <https://doi.org/10.1137/0202019>.
|
| 118 |
+
|
| 119 |
+
"""
|
| 120 |
+
|
| 121 |
+
# First we define some auxiliary search functions.
|
| 122 |
+
#
|
| 123 |
+
# If you are a human reading these auxiliary search functions, the "global"
|
| 124 |
+
# variables `leftmatches`, `rightmatches`, `distances`, etc. are defined
|
| 125 |
+
# below the functions, so that they are initialized close to the initial
|
| 126 |
+
# invocation of the search functions.
|
| 127 |
+
def breadth_first_search():
|
| 128 |
+
for v in left:
|
| 129 |
+
if leftmatches[v] is None:
|
| 130 |
+
distances[v] = 0
|
| 131 |
+
queue.append(v)
|
| 132 |
+
else:
|
| 133 |
+
distances[v] = INFINITY
|
| 134 |
+
distances[None] = INFINITY
|
| 135 |
+
while queue:
|
| 136 |
+
v = queue.popleft()
|
| 137 |
+
if distances[v] < distances[None]:
|
| 138 |
+
for u in G[v]:
|
| 139 |
+
if distances[rightmatches[u]] is INFINITY:
|
| 140 |
+
distances[rightmatches[u]] = distances[v] + 1
|
| 141 |
+
queue.append(rightmatches[u])
|
| 142 |
+
return distances[None] is not INFINITY
|
| 143 |
+
|
| 144 |
+
def depth_first_search(v):
|
| 145 |
+
if v is not None:
|
| 146 |
+
for u in G[v]:
|
| 147 |
+
if distances[rightmatches[u]] == distances[v] + 1:
|
| 148 |
+
if depth_first_search(rightmatches[u]):
|
| 149 |
+
rightmatches[u] = v
|
| 150 |
+
leftmatches[v] = u
|
| 151 |
+
return True
|
| 152 |
+
distances[v] = INFINITY
|
| 153 |
+
return False
|
| 154 |
+
return True
|
| 155 |
+
|
| 156 |
+
# Initialize the "global" variables that maintain state during the search.
|
| 157 |
+
left, right = bipartite_sets(G, top_nodes)
|
| 158 |
+
leftmatches = {v: None for v in left}
|
| 159 |
+
rightmatches = {v: None for v in right}
|
| 160 |
+
distances = {}
|
| 161 |
+
queue = collections.deque()
|
| 162 |
+
|
| 163 |
+
# Implementation note: this counter is incremented as pairs are matched but
|
| 164 |
+
# it is currently not used elsewhere in the computation.
|
| 165 |
+
num_matched_pairs = 0
|
| 166 |
+
while breadth_first_search():
|
| 167 |
+
for v in left:
|
| 168 |
+
if leftmatches[v] is None:
|
| 169 |
+
if depth_first_search(v):
|
| 170 |
+
num_matched_pairs += 1
|
| 171 |
+
|
| 172 |
+
# Strip the entries matched to `None`.
|
| 173 |
+
leftmatches = {k: v for k, v in leftmatches.items() if v is not None}
|
| 174 |
+
rightmatches = {k: v for k, v in rightmatches.items() if v is not None}
|
| 175 |
+
|
| 176 |
+
# At this point, the left matches and the right matches are inverses of one
|
| 177 |
+
# another. In other words,
|
| 178 |
+
#
|
| 179 |
+
# leftmatches == {v, k for k, v in rightmatches.items()}
|
| 180 |
+
#
|
| 181 |
+
# Finally, we combine both the left matches and right matches.
|
| 182 |
+
return dict(itertools.chain(leftmatches.items(), rightmatches.items()))
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
@nx._dispatchable
|
| 186 |
+
def eppstein_matching(G, top_nodes=None):
|
| 187 |
+
"""Returns the maximum cardinality matching of the bipartite graph `G`.
|
| 188 |
+
|
| 189 |
+
Parameters
|
| 190 |
+
----------
|
| 191 |
+
G : NetworkX graph
|
| 192 |
+
|
| 193 |
+
Undirected bipartite graph
|
| 194 |
+
|
| 195 |
+
top_nodes : container
|
| 196 |
+
|
| 197 |
+
Container with all nodes in one bipartite node set. If not supplied
|
| 198 |
+
it will be computed. But if more than one solution exists an exception
|
| 199 |
+
will be raised.
|
| 200 |
+
|
| 201 |
+
Returns
|
| 202 |
+
-------
|
| 203 |
+
matches : dictionary
|
| 204 |
+
|
| 205 |
+
The matching is returned as a dictionary, `matching`, such that
|
| 206 |
+
``matching[v] == w`` if node `v` is matched to node `w`. Unmatched
|
| 207 |
+
nodes do not occur as a key in `matching`.
|
| 208 |
+
|
| 209 |
+
Raises
|
| 210 |
+
------
|
| 211 |
+
AmbiguousSolution
|
| 212 |
+
Raised if the input bipartite graph is disconnected and no container
|
| 213 |
+
with all nodes in one bipartite set is provided. When determining
|
| 214 |
+
the nodes in each bipartite set more than one valid solution is
|
| 215 |
+
possible if the input graph is disconnected.
|
| 216 |
+
|
| 217 |
+
Notes
|
| 218 |
+
-----
|
| 219 |
+
This function is implemented with David Eppstein's version of the algorithm
|
| 220 |
+
Hopcroft--Karp algorithm (see :func:`hopcroft_karp_matching`), which
|
| 221 |
+
originally appeared in the `Python Algorithms and Data Structures library
|
| 222 |
+
(PADS) <http://www.ics.uci.edu/~eppstein/PADS/ABOUT-PADS.txt>`_.
|
| 223 |
+
|
| 224 |
+
See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
|
| 225 |
+
for further details on how bipartite graphs are handled in NetworkX.
|
| 226 |
+
|
| 227 |
+
See Also
|
| 228 |
+
--------
|
| 229 |
+
|
| 230 |
+
hopcroft_karp_matching
|
| 231 |
+
|
| 232 |
+
"""
|
| 233 |
+
# Due to its original implementation, a directed graph is needed
|
| 234 |
+
# so that the two sets of bipartite nodes can be distinguished
|
| 235 |
+
left, right = bipartite_sets(G, top_nodes)
|
| 236 |
+
G = nx.DiGraph(G.edges(left))
|
| 237 |
+
# initialize greedy matching (redundant, but faster than full search)
|
| 238 |
+
matching = {}
|
| 239 |
+
for u in G:
|
| 240 |
+
for v in G[u]:
|
| 241 |
+
if v not in matching:
|
| 242 |
+
matching[v] = u
|
| 243 |
+
break
|
| 244 |
+
while True:
|
| 245 |
+
# structure residual graph into layers
|
| 246 |
+
# pred[u] gives the neighbor in the previous layer for u in U
|
| 247 |
+
# preds[v] gives a list of neighbors in the previous layer for v in V
|
| 248 |
+
# unmatched gives a list of unmatched vertices in final layer of V,
|
| 249 |
+
# and is also used as a flag value for pred[u] when u is in the first
|
| 250 |
+
# layer
|
| 251 |
+
preds = {}
|
| 252 |
+
unmatched = []
|
| 253 |
+
pred = {u: unmatched for u in G}
|
| 254 |
+
for v in matching:
|
| 255 |
+
del pred[matching[v]]
|
| 256 |
+
layer = list(pred)
|
| 257 |
+
|
| 258 |
+
# repeatedly extend layering structure by another pair of layers
|
| 259 |
+
while layer and not unmatched:
|
| 260 |
+
newLayer = {}
|
| 261 |
+
for u in layer:
|
| 262 |
+
for v in G[u]:
|
| 263 |
+
if v not in preds:
|
| 264 |
+
newLayer.setdefault(v, []).append(u)
|
| 265 |
+
layer = []
|
| 266 |
+
for v in newLayer:
|
| 267 |
+
preds[v] = newLayer[v]
|
| 268 |
+
if v in matching:
|
| 269 |
+
layer.append(matching[v])
|
| 270 |
+
pred[matching[v]] = v
|
| 271 |
+
else:
|
| 272 |
+
unmatched.append(v)
|
| 273 |
+
|
| 274 |
+
# did we finish layering without finding any alternating paths?
|
| 275 |
+
if not unmatched:
|
| 276 |
+
# TODO - The lines between --- were unused and were thus commented
|
| 277 |
+
# out. This whole commented chunk should be reviewed to determine
|
| 278 |
+
# whether it should be built upon or completely removed.
|
| 279 |
+
# ---
|
| 280 |
+
# unlayered = {}
|
| 281 |
+
# for u in G:
|
| 282 |
+
# # TODO Why is extra inner loop necessary?
|
| 283 |
+
# for v in G[u]:
|
| 284 |
+
# if v not in preds:
|
| 285 |
+
# unlayered[v] = None
|
| 286 |
+
# ---
|
| 287 |
+
# TODO Originally, this function returned a three-tuple:
|
| 288 |
+
#
|
| 289 |
+
# return (matching, list(pred), list(unlayered))
|
| 290 |
+
#
|
| 291 |
+
# For some reason, the documentation for this function
|
| 292 |
+
# indicated that the second and third elements of the returned
|
| 293 |
+
# three-tuple would be the vertices in the left and right vertex
|
| 294 |
+
# sets, respectively, that are also in the maximum independent set.
|
| 295 |
+
# However, what I think the author meant was that the second
|
| 296 |
+
# element is the list of vertices that were unmatched and the third
|
| 297 |
+
# element was the list of vertices that were matched. Since that
|
| 298 |
+
# seems to be the case, they don't really need to be returned,
|
| 299 |
+
# since that information can be inferred from the matching
|
| 300 |
+
# dictionary.
|
| 301 |
+
|
| 302 |
+
# All the matched nodes must be a key in the dictionary
|
| 303 |
+
for key in matching.copy():
|
| 304 |
+
matching[matching[key]] = key
|
| 305 |
+
return matching
|
| 306 |
+
|
| 307 |
+
# recursively search backward through layers to find alternating paths
|
| 308 |
+
# recursion returns true if found path, false otherwise
|
| 309 |
+
def recurse(v):
|
| 310 |
+
if v in preds:
|
| 311 |
+
L = preds.pop(v)
|
| 312 |
+
for u in L:
|
| 313 |
+
if u in pred:
|
| 314 |
+
pu = pred.pop(u)
|
| 315 |
+
if pu is unmatched or recurse(pu):
|
| 316 |
+
matching[v] = u
|
| 317 |
+
return True
|
| 318 |
+
return False
|
| 319 |
+
|
| 320 |
+
for v in unmatched:
|
| 321 |
+
recurse(v)
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
def _is_connected_by_alternating_path(G, v, matched_edges, unmatched_edges, targets):
|
| 325 |
+
"""Returns True if and only if the vertex `v` is connected to one of
|
| 326 |
+
the target vertices by an alternating path in `G`.
|
| 327 |
+
|
| 328 |
+
An *alternating path* is a path in which every other edge is in the
|
| 329 |
+
specified maximum matching (and the remaining edges in the path are not in
|
| 330 |
+
the matching). An alternating path may have matched edges in the even
|
| 331 |
+
positions or in the odd positions, as long as the edges alternate between
|
| 332 |
+
'matched' and 'unmatched'.
|
| 333 |
+
|
| 334 |
+
`G` is an undirected bipartite NetworkX graph.
|
| 335 |
+
|
| 336 |
+
`v` is a vertex in `G`.
|
| 337 |
+
|
| 338 |
+
`matched_edges` is a set of edges present in a maximum matching in `G`.
|
| 339 |
+
|
| 340 |
+
`unmatched_edges` is a set of edges not present in a maximum
|
| 341 |
+
matching in `G`.
|
| 342 |
+
|
| 343 |
+
`targets` is a set of vertices.
|
| 344 |
+
|
| 345 |
+
"""
|
| 346 |
+
|
| 347 |
+
def _alternating_dfs(u, along_matched=True):
|
| 348 |
+
"""Returns True if and only if `u` is connected to one of the
|
| 349 |
+
targets by an alternating path.
|
| 350 |
+
|
| 351 |
+
`u` is a vertex in the graph `G`.
|
| 352 |
+
|
| 353 |
+
If `along_matched` is True, this step of the depth-first search
|
| 354 |
+
will continue only through edges in the given matching. Otherwise, it
|
| 355 |
+
will continue only through edges *not* in the given matching.
|
| 356 |
+
|
| 357 |
+
"""
|
| 358 |
+
visited = set()
|
| 359 |
+
# Follow matched edges when depth is even,
|
| 360 |
+
# and follow unmatched edges when depth is odd.
|
| 361 |
+
initial_depth = 0 if along_matched else 1
|
| 362 |
+
stack = [(u, iter(G[u]), initial_depth)]
|
| 363 |
+
while stack:
|
| 364 |
+
parent, children, depth = stack[-1]
|
| 365 |
+
valid_edges = matched_edges if depth % 2 else unmatched_edges
|
| 366 |
+
try:
|
| 367 |
+
child = next(children)
|
| 368 |
+
if child not in visited:
|
| 369 |
+
if (parent, child) in valid_edges or (child, parent) in valid_edges:
|
| 370 |
+
if child in targets:
|
| 371 |
+
return True
|
| 372 |
+
visited.add(child)
|
| 373 |
+
stack.append((child, iter(G[child]), depth + 1))
|
| 374 |
+
except StopIteration:
|
| 375 |
+
stack.pop()
|
| 376 |
+
return False
|
| 377 |
+
|
| 378 |
+
# Check for alternating paths starting with edges in the matching, then
|
| 379 |
+
# check for alternating paths starting with edges not in the
|
| 380 |
+
# matching.
|
| 381 |
+
return _alternating_dfs(v, along_matched=True) or _alternating_dfs(
|
| 382 |
+
v, along_matched=False
|
| 383 |
+
)
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
def _connected_by_alternating_paths(G, matching, targets):
|
| 387 |
+
"""Returns the set of vertices that are connected to one of the target
|
| 388 |
+
vertices by an alternating path in `G` or are themselves a target.
|
| 389 |
+
|
| 390 |
+
An *alternating path* is a path in which every other edge is in the
|
| 391 |
+
specified maximum matching (and the remaining edges in the path are not in
|
| 392 |
+
the matching). An alternating path may have matched edges in the even
|
| 393 |
+
positions or in the odd positions, as long as the edges alternate between
|
| 394 |
+
'matched' and 'unmatched'.
|
| 395 |
+
|
| 396 |
+
`G` is an undirected bipartite NetworkX graph.
|
| 397 |
+
|
| 398 |
+
`matching` is a dictionary representing a maximum matching in `G`, as
|
| 399 |
+
returned by, for example, :func:`maximum_matching`.
|
| 400 |
+
|
| 401 |
+
`targets` is a set of vertices.
|
| 402 |
+
|
| 403 |
+
"""
|
| 404 |
+
# Get the set of matched edges and the set of unmatched edges. Only include
|
| 405 |
+
# one version of each undirected edge (for example, include edge (1, 2) but
|
| 406 |
+
# not edge (2, 1)). Using frozensets as an intermediary step we do not
|
| 407 |
+
# require nodes to be orderable.
|
| 408 |
+
edge_sets = {frozenset((u, v)) for u, v in matching.items()}
|
| 409 |
+
matched_edges = {tuple(edge) for edge in edge_sets}
|
| 410 |
+
unmatched_edges = {
|
| 411 |
+
(u, v) for (u, v) in G.edges() if frozenset((u, v)) not in edge_sets
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
return {
|
| 415 |
+
v
|
| 416 |
+
for v in G
|
| 417 |
+
if v in targets
|
| 418 |
+
or _is_connected_by_alternating_path(
|
| 419 |
+
G, v, matched_edges, unmatched_edges, targets
|
| 420 |
+
)
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
|
| 424 |
+
@nx._dispatchable
|
| 425 |
+
def to_vertex_cover(G, matching, top_nodes=None):
|
| 426 |
+
"""Returns the minimum vertex cover corresponding to the given maximum
|
| 427 |
+
matching of the bipartite graph `G`.
|
| 428 |
+
|
| 429 |
+
Parameters
|
| 430 |
+
----------
|
| 431 |
+
G : NetworkX graph
|
| 432 |
+
|
| 433 |
+
Undirected bipartite graph
|
| 434 |
+
|
| 435 |
+
matching : dictionary
|
| 436 |
+
|
| 437 |
+
A dictionary whose keys are vertices in `G` and whose values are the
|
| 438 |
+
distinct neighbors comprising the maximum matching for `G`, as returned
|
| 439 |
+
by, for example, :func:`maximum_matching`. The dictionary *must*
|
| 440 |
+
represent the maximum matching.
|
| 441 |
+
|
| 442 |
+
top_nodes : container
|
| 443 |
+
|
| 444 |
+
Container with all nodes in one bipartite node set. If not supplied
|
| 445 |
+
it will be computed. But if more than one solution exists an exception
|
| 446 |
+
will be raised.
|
| 447 |
+
|
| 448 |
+
Returns
|
| 449 |
+
-------
|
| 450 |
+
vertex_cover : :class:`set`
|
| 451 |
+
|
| 452 |
+
The minimum vertex cover in `G`.
|
| 453 |
+
|
| 454 |
+
Raises
|
| 455 |
+
------
|
| 456 |
+
AmbiguousSolution
|
| 457 |
+
Raised if the input bipartite graph is disconnected and no container
|
| 458 |
+
with all nodes in one bipartite set is provided. When determining
|
| 459 |
+
the nodes in each bipartite set more than one valid solution is
|
| 460 |
+
possible if the input graph is disconnected.
|
| 461 |
+
|
| 462 |
+
Notes
|
| 463 |
+
-----
|
| 464 |
+
This function is implemented using the procedure guaranteed by `Konig's
|
| 465 |
+
theorem
|
| 466 |
+
<https://en.wikipedia.org/wiki/K%C3%B6nig%27s_theorem_%28graph_theory%29>`_,
|
| 467 |
+
which proves an equivalence between a maximum matching and a minimum vertex
|
| 468 |
+
cover in bipartite graphs.
|
| 469 |
+
|
| 470 |
+
Since a minimum vertex cover is the complement of a maximum independent set
|
| 471 |
+
for any graph, one can compute the maximum independent set of a bipartite
|
| 472 |
+
graph this way:
|
| 473 |
+
|
| 474 |
+
>>> G = nx.complete_bipartite_graph(2, 3)
|
| 475 |
+
>>> matching = nx.bipartite.maximum_matching(G)
|
| 476 |
+
>>> vertex_cover = nx.bipartite.to_vertex_cover(G, matching)
|
| 477 |
+
>>> independent_set = set(G) - vertex_cover
|
| 478 |
+
>>> print(list(independent_set))
|
| 479 |
+
[2, 3, 4]
|
| 480 |
+
|
| 481 |
+
See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
|
| 482 |
+
for further details on how bipartite graphs are handled in NetworkX.
|
| 483 |
+
|
| 484 |
+
"""
|
| 485 |
+
# This is a Python implementation of the algorithm described at
|
| 486 |
+
# <https://en.wikipedia.org/wiki/K%C3%B6nig%27s_theorem_%28graph_theory%29#Proof>.
|
| 487 |
+
L, R = bipartite_sets(G, top_nodes)
|
| 488 |
+
# Let U be the set of unmatched vertices in the left vertex set.
|
| 489 |
+
unmatched_vertices = set(G) - set(matching)
|
| 490 |
+
U = unmatched_vertices & L
|
| 491 |
+
# Let Z be the set of vertices that are either in U or are connected to U
|
| 492 |
+
# by alternating paths.
|
| 493 |
+
Z = _connected_by_alternating_paths(G, matching, U)
|
| 494 |
+
# At this point, every edge either has a right endpoint in Z or a left
|
| 495 |
+
# endpoint not in Z. This gives us the vertex cover.
|
| 496 |
+
return (L - Z) | (R & Z)
|
| 497 |
+
|
| 498 |
+
|
| 499 |
+
#: Returns the maximum cardinality matching in the given bipartite graph.
|
| 500 |
+
#:
|
| 501 |
+
#: This function is simply an alias for :func:`hopcroft_karp_matching`.
|
| 502 |
+
maximum_matching = hopcroft_karp_matching
|
| 503 |
+
|
| 504 |
+
|
| 505 |
+
@nx._dispatchable(edge_attrs="weight")
|
| 506 |
+
def minimum_weight_full_matching(G, top_nodes=None, weight="weight"):
|
| 507 |
+
r"""Returns a minimum weight full matching of the bipartite graph `G`.
|
| 508 |
+
|
| 509 |
+
Let :math:`G = ((U, V), E)` be a weighted bipartite graph with real weights
|
| 510 |
+
:math:`w : E \to \mathbb{R}`. This function then produces a matching
|
| 511 |
+
:math:`M \subseteq E` with cardinality
|
| 512 |
+
|
| 513 |
+
.. math::
|
| 514 |
+
\lvert M \rvert = \min(\lvert U \rvert, \lvert V \rvert),
|
| 515 |
+
|
| 516 |
+
which minimizes the sum of the weights of the edges included in the
|
| 517 |
+
matching, :math:`\sum_{e \in M} w(e)`, or raises an error if no such
|
| 518 |
+
matching exists.
|
| 519 |
+
|
| 520 |
+
When :math:`\lvert U \rvert = \lvert V \rvert`, this is commonly
|
| 521 |
+
referred to as a perfect matching; here, since we allow
|
| 522 |
+
:math:`\lvert U \rvert` and :math:`\lvert V \rvert` to differ, we
|
| 523 |
+
follow Karp [1]_ and refer to the matching as *full*.
|
| 524 |
+
|
| 525 |
+
Parameters
|
| 526 |
+
----------
|
| 527 |
+
G : NetworkX graph
|
| 528 |
+
|
| 529 |
+
Undirected bipartite graph
|
| 530 |
+
|
| 531 |
+
top_nodes : container
|
| 532 |
+
|
| 533 |
+
Container with all nodes in one bipartite node set. If not supplied
|
| 534 |
+
it will be computed.
|
| 535 |
+
|
| 536 |
+
weight : string, optional (default='weight')
|
| 537 |
+
|
| 538 |
+
The edge data key used to provide each value in the matrix.
|
| 539 |
+
If None, then each edge has weight 1.
|
| 540 |
+
|
| 541 |
+
Returns
|
| 542 |
+
-------
|
| 543 |
+
matches : dictionary
|
| 544 |
+
|
| 545 |
+
The matching is returned as a dictionary, `matches`, such that
|
| 546 |
+
``matches[v] == w`` if node `v` is matched to node `w`. Unmatched
|
| 547 |
+
nodes do not occur as a key in `matches`.
|
| 548 |
+
|
| 549 |
+
Raises
|
| 550 |
+
------
|
| 551 |
+
ValueError
|
| 552 |
+
Raised if no full matching exists.
|
| 553 |
+
|
| 554 |
+
ImportError
|
| 555 |
+
Raised if SciPy is not available.
|
| 556 |
+
|
| 557 |
+
Notes
|
| 558 |
+
-----
|
| 559 |
+
The problem of determining a minimum weight full matching is also known as
|
| 560 |
+
the rectangular linear assignment problem. This implementation defers the
|
| 561 |
+
calculation of the assignment to SciPy.
|
| 562 |
+
|
| 563 |
+
References
|
| 564 |
+
----------
|
| 565 |
+
.. [1] Richard Manning Karp:
|
| 566 |
+
An algorithm to Solve the m x n Assignment Problem in Expected Time
|
| 567 |
+
O(mn log n).
|
| 568 |
+
Networks, 10(2):143–152, 1980.
|
| 569 |
+
|
| 570 |
+
"""
|
| 571 |
+
import numpy as np
|
| 572 |
+
import scipy as sp
|
| 573 |
+
|
| 574 |
+
left, right = nx.bipartite.sets(G, top_nodes)
|
| 575 |
+
U = list(left)
|
| 576 |
+
V = list(right)
|
| 577 |
+
# We explicitly create the biadjacency matrix having infinities
|
| 578 |
+
# where edges are missing (as opposed to zeros, which is what one would
|
| 579 |
+
# get by using toarray on the sparse matrix).
|
| 580 |
+
weights_sparse = biadjacency_matrix(
|
| 581 |
+
G, row_order=U, column_order=V, weight=weight, format="coo"
|
| 582 |
+
)
|
| 583 |
+
weights = np.full(weights_sparse.shape, np.inf)
|
| 584 |
+
weights[weights_sparse.row, weights_sparse.col] = weights_sparse.data
|
| 585 |
+
left_matches = sp.optimize.linear_sum_assignment(weights)
|
| 586 |
+
d = {U[u]: V[v] for u, v in zip(*left_matches)}
|
| 587 |
+
# d will contain the matching from edges in left to right; we need to
|
| 588 |
+
# add the ones from right to left as well.
|
| 589 |
+
d.update({v: u for u, v in d.items()})
|
| 590 |
+
return d
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/matrix.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
====================
|
| 3 |
+
Biadjacency matrices
|
| 4 |
+
====================
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import itertools
|
| 8 |
+
|
| 9 |
+
import networkx as nx
|
| 10 |
+
from networkx.convert_matrix import _generate_weighted_edges
|
| 11 |
+
|
| 12 |
+
__all__ = ["biadjacency_matrix", "from_biadjacency_matrix"]
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@nx._dispatchable(edge_attrs="weight")
|
| 16 |
+
def biadjacency_matrix(
|
| 17 |
+
G, row_order, column_order=None, dtype=None, weight="weight", format="csr"
|
| 18 |
+
):
|
| 19 |
+
r"""Returns the biadjacency matrix of the bipartite graph G.
|
| 20 |
+
|
| 21 |
+
Let `G = (U, V, E)` be a bipartite graph with node sets
|
| 22 |
+
`U = u_{1},...,u_{r}` and `V = v_{1},...,v_{s}`. The biadjacency
|
| 23 |
+
matrix [1]_ is the `r` x `s` matrix `B` in which `b_{i,j} = 1`
|
| 24 |
+
if, and only if, `(u_i, v_j) \in E`. If the parameter `weight` is
|
| 25 |
+
not `None` and matches the name of an edge attribute, its value is
|
| 26 |
+
used instead of 1.
|
| 27 |
+
|
| 28 |
+
Parameters
|
| 29 |
+
----------
|
| 30 |
+
G : graph
|
| 31 |
+
A NetworkX graph
|
| 32 |
+
|
| 33 |
+
row_order : list of nodes
|
| 34 |
+
The rows of the matrix are ordered according to the list of nodes.
|
| 35 |
+
|
| 36 |
+
column_order : list, optional
|
| 37 |
+
The columns of the matrix are ordered according to the list of nodes.
|
| 38 |
+
If column_order is None, then the ordering of columns is arbitrary.
|
| 39 |
+
|
| 40 |
+
dtype : NumPy data-type, optional
|
| 41 |
+
A valid NumPy dtype used to initialize the array. If None, then the
|
| 42 |
+
NumPy default is used.
|
| 43 |
+
|
| 44 |
+
weight : string or None, optional (default='weight')
|
| 45 |
+
The edge data key used to provide each value in the matrix.
|
| 46 |
+
If None, then each edge has weight 1.
|
| 47 |
+
|
| 48 |
+
format : str in {'bsr', 'csr', 'csc', 'coo', 'lil', 'dia', 'dok'}
|
| 49 |
+
The type of the matrix to be returned (default 'csr'). For
|
| 50 |
+
some algorithms different implementations of sparse matrices
|
| 51 |
+
can perform better. See [2]_ for details.
|
| 52 |
+
|
| 53 |
+
Returns
|
| 54 |
+
-------
|
| 55 |
+
M : SciPy sparse array
|
| 56 |
+
Biadjacency matrix representation of the bipartite graph G.
|
| 57 |
+
|
| 58 |
+
Notes
|
| 59 |
+
-----
|
| 60 |
+
No attempt is made to check that the input graph is bipartite.
|
| 61 |
+
|
| 62 |
+
For directed bipartite graphs only successors are considered as neighbors.
|
| 63 |
+
To obtain an adjacency matrix with ones (or weight values) for both
|
| 64 |
+
predecessors and successors you have to generate two biadjacency matrices
|
| 65 |
+
where the rows of one of them are the columns of the other, and then add
|
| 66 |
+
one to the transpose of the other.
|
| 67 |
+
|
| 68 |
+
See Also
|
| 69 |
+
--------
|
| 70 |
+
adjacency_matrix
|
| 71 |
+
from_biadjacency_matrix
|
| 72 |
+
|
| 73 |
+
References
|
| 74 |
+
----------
|
| 75 |
+
.. [1] https://en.wikipedia.org/wiki/Adjacency_matrix#Adjacency_matrix_of_a_bipartite_graph
|
| 76 |
+
.. [2] Scipy Dev. References, "Sparse Matrices",
|
| 77 |
+
https://docs.scipy.org/doc/scipy/reference/sparse.html
|
| 78 |
+
"""
|
| 79 |
+
import scipy as sp
|
| 80 |
+
|
| 81 |
+
nlen = len(row_order)
|
| 82 |
+
if nlen == 0:
|
| 83 |
+
raise nx.NetworkXError("row_order is empty list")
|
| 84 |
+
if len(row_order) != len(set(row_order)):
|
| 85 |
+
msg = "Ambiguous ordering: `row_order` contained duplicates."
|
| 86 |
+
raise nx.NetworkXError(msg)
|
| 87 |
+
if column_order is None:
|
| 88 |
+
column_order = list(set(G) - set(row_order))
|
| 89 |
+
mlen = len(column_order)
|
| 90 |
+
if len(column_order) != len(set(column_order)):
|
| 91 |
+
msg = "Ambiguous ordering: `column_order` contained duplicates."
|
| 92 |
+
raise nx.NetworkXError(msg)
|
| 93 |
+
|
| 94 |
+
row_index = dict(zip(row_order, itertools.count()))
|
| 95 |
+
col_index = dict(zip(column_order, itertools.count()))
|
| 96 |
+
|
| 97 |
+
if G.number_of_edges() == 0:
|
| 98 |
+
row, col, data = [], [], []
|
| 99 |
+
else:
|
| 100 |
+
row, col, data = zip(
|
| 101 |
+
*(
|
| 102 |
+
(row_index[u], col_index[v], d.get(weight, 1))
|
| 103 |
+
for u, v, d in G.edges(row_order, data=True)
|
| 104 |
+
if u in row_index and v in col_index
|
| 105 |
+
)
|
| 106 |
+
)
|
| 107 |
+
A = sp.sparse.coo_array((data, (row, col)), shape=(nlen, mlen), dtype=dtype)
|
| 108 |
+
try:
|
| 109 |
+
return A.asformat(format)
|
| 110 |
+
except ValueError as err:
|
| 111 |
+
raise nx.NetworkXError(f"Unknown sparse array format: {format}") from err
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 115 |
+
def from_biadjacency_matrix(A, create_using=None, edge_attribute="weight"):
|
| 116 |
+
r"""Creates a new bipartite graph from a biadjacency matrix given as a
|
| 117 |
+
SciPy sparse array.
|
| 118 |
+
|
| 119 |
+
Parameters
|
| 120 |
+
----------
|
| 121 |
+
A: scipy sparse array
|
| 122 |
+
A biadjacency matrix representation of a graph
|
| 123 |
+
|
| 124 |
+
create_using: NetworkX graph
|
| 125 |
+
Use specified graph for result. The default is Graph()
|
| 126 |
+
|
| 127 |
+
edge_attribute: string
|
| 128 |
+
Name of edge attribute to store matrix numeric value. The data will
|
| 129 |
+
have the same type as the matrix entry (int, float, (real,imag)).
|
| 130 |
+
|
| 131 |
+
Notes
|
| 132 |
+
-----
|
| 133 |
+
The nodes are labeled with the attribute `bipartite` set to an integer
|
| 134 |
+
0 or 1 representing membership in part 0 or part 1 of the bipartite graph.
|
| 135 |
+
|
| 136 |
+
If `create_using` is an instance of :class:`networkx.MultiGraph` or
|
| 137 |
+
:class:`networkx.MultiDiGraph` and the entries of `A` are of
|
| 138 |
+
type :class:`int`, then this function returns a multigraph (of the same
|
| 139 |
+
type as `create_using`) with parallel edges. In this case, `edge_attribute`
|
| 140 |
+
will be ignored.
|
| 141 |
+
|
| 142 |
+
See Also
|
| 143 |
+
--------
|
| 144 |
+
biadjacency_matrix
|
| 145 |
+
from_numpy_array
|
| 146 |
+
|
| 147 |
+
References
|
| 148 |
+
----------
|
| 149 |
+
[1] https://en.wikipedia.org/wiki/Adjacency_matrix#Adjacency_matrix_of_a_bipartite_graph
|
| 150 |
+
"""
|
| 151 |
+
G = nx.empty_graph(0, create_using)
|
| 152 |
+
n, m = A.shape
|
| 153 |
+
# Make sure we get even the isolated nodes of the graph.
|
| 154 |
+
G.add_nodes_from(range(n), bipartite=0)
|
| 155 |
+
G.add_nodes_from(range(n, n + m), bipartite=1)
|
| 156 |
+
# Create an iterable over (u, v, w) triples and for each triple, add an
|
| 157 |
+
# edge from u to v with weight w.
|
| 158 |
+
triples = ((u, n + v, d) for (u, v, d) in _generate_weighted_edges(A))
|
| 159 |
+
# If the entries in the adjacency matrix are integers and the graph is a
|
| 160 |
+
# multigraph, then create parallel edges, each with weight 1, for each
|
| 161 |
+
# entry in the adjacency matrix. Otherwise, create one edge for each
|
| 162 |
+
# positive entry in the adjacency matrix and set the weight of that edge to
|
| 163 |
+
# be the entry in the matrix.
|
| 164 |
+
if A.dtype.kind in ("i", "u") and G.is_multigraph():
|
| 165 |
+
chain = itertools.chain.from_iterable
|
| 166 |
+
triples = chain(((u, v, 1) for d in range(w)) for (u, v, w) in triples)
|
| 167 |
+
G.add_weighted_edges_from(triples, weight=edge_attribute)
|
| 168 |
+
return G
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/projection.py
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""One-mode (unipartite) projections of bipartite graphs."""
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx.exception import NetworkXAlgorithmError
|
| 5 |
+
from networkx.utils import not_implemented_for
|
| 6 |
+
|
| 7 |
+
__all__ = [
|
| 8 |
+
"projected_graph",
|
| 9 |
+
"weighted_projected_graph",
|
| 10 |
+
"collaboration_weighted_projected_graph",
|
| 11 |
+
"overlap_weighted_projected_graph",
|
| 12 |
+
"generic_weighted_projected_graph",
|
| 13 |
+
]
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@nx._dispatchable(
|
| 17 |
+
graphs="B", preserve_node_attrs=True, preserve_graph_attrs=True, returns_graph=True
|
| 18 |
+
)
|
| 19 |
+
def projected_graph(B, nodes, multigraph=False):
|
| 20 |
+
r"""Returns the projection of B onto one of its node sets.
|
| 21 |
+
|
| 22 |
+
Returns the graph G that is the projection of the bipartite graph B
|
| 23 |
+
onto the specified nodes. They retain their attributes and are connected
|
| 24 |
+
in G if they have a common neighbor in B.
|
| 25 |
+
|
| 26 |
+
Parameters
|
| 27 |
+
----------
|
| 28 |
+
B : NetworkX graph
|
| 29 |
+
The input graph should be bipartite.
|
| 30 |
+
|
| 31 |
+
nodes : list or iterable
|
| 32 |
+
Nodes to project onto (the "bottom" nodes).
|
| 33 |
+
|
| 34 |
+
multigraph: bool (default=False)
|
| 35 |
+
If True return a multigraph where the multiple edges represent multiple
|
| 36 |
+
shared neighbors. They edge key in the multigraph is assigned to the
|
| 37 |
+
label of the neighbor.
|
| 38 |
+
|
| 39 |
+
Returns
|
| 40 |
+
-------
|
| 41 |
+
Graph : NetworkX graph or multigraph
|
| 42 |
+
A graph that is the projection onto the given nodes.
|
| 43 |
+
|
| 44 |
+
Examples
|
| 45 |
+
--------
|
| 46 |
+
>>> from networkx.algorithms import bipartite
|
| 47 |
+
>>> B = nx.path_graph(4)
|
| 48 |
+
>>> G = bipartite.projected_graph(B, [1, 3])
|
| 49 |
+
>>> list(G)
|
| 50 |
+
[1, 3]
|
| 51 |
+
>>> list(G.edges())
|
| 52 |
+
[(1, 3)]
|
| 53 |
+
|
| 54 |
+
If nodes `a`, and `b` are connected through both nodes 1 and 2 then
|
| 55 |
+
building a multigraph results in two edges in the projection onto
|
| 56 |
+
[`a`, `b`]:
|
| 57 |
+
|
| 58 |
+
>>> B = nx.Graph()
|
| 59 |
+
>>> B.add_edges_from([("a", 1), ("b", 1), ("a", 2), ("b", 2)])
|
| 60 |
+
>>> G = bipartite.projected_graph(B, ["a", "b"], multigraph=True)
|
| 61 |
+
>>> print([sorted((u, v)) for u, v in G.edges()])
|
| 62 |
+
[['a', 'b'], ['a', 'b']]
|
| 63 |
+
|
| 64 |
+
Notes
|
| 65 |
+
-----
|
| 66 |
+
No attempt is made to verify that the input graph B is bipartite.
|
| 67 |
+
Returns a simple graph that is the projection of the bipartite graph B
|
| 68 |
+
onto the set of nodes given in list nodes. If multigraph=True then
|
| 69 |
+
a multigraph is returned with an edge for every shared neighbor.
|
| 70 |
+
|
| 71 |
+
Directed graphs are allowed as input. The output will also then
|
| 72 |
+
be a directed graph with edges if there is a directed path between
|
| 73 |
+
the nodes.
|
| 74 |
+
|
| 75 |
+
The graph and node properties are (shallow) copied to the projected graph.
|
| 76 |
+
|
| 77 |
+
See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
|
| 78 |
+
for further details on how bipartite graphs are handled in NetworkX.
|
| 79 |
+
|
| 80 |
+
See Also
|
| 81 |
+
--------
|
| 82 |
+
is_bipartite,
|
| 83 |
+
is_bipartite_node_set,
|
| 84 |
+
sets,
|
| 85 |
+
weighted_projected_graph,
|
| 86 |
+
collaboration_weighted_projected_graph,
|
| 87 |
+
overlap_weighted_projected_graph,
|
| 88 |
+
generic_weighted_projected_graph
|
| 89 |
+
"""
|
| 90 |
+
if B.is_multigraph():
|
| 91 |
+
raise nx.NetworkXError("not defined for multigraphs")
|
| 92 |
+
if B.is_directed():
|
| 93 |
+
directed = True
|
| 94 |
+
if multigraph:
|
| 95 |
+
G = nx.MultiDiGraph()
|
| 96 |
+
else:
|
| 97 |
+
G = nx.DiGraph()
|
| 98 |
+
else:
|
| 99 |
+
directed = False
|
| 100 |
+
if multigraph:
|
| 101 |
+
G = nx.MultiGraph()
|
| 102 |
+
else:
|
| 103 |
+
G = nx.Graph()
|
| 104 |
+
G.graph.update(B.graph)
|
| 105 |
+
G.add_nodes_from((n, B.nodes[n]) for n in nodes)
|
| 106 |
+
for u in nodes:
|
| 107 |
+
nbrs2 = {v for nbr in B[u] for v in B[nbr] if v != u}
|
| 108 |
+
if multigraph:
|
| 109 |
+
for n in nbrs2:
|
| 110 |
+
if directed:
|
| 111 |
+
links = set(B[u]) & set(B.pred[n])
|
| 112 |
+
else:
|
| 113 |
+
links = set(B[u]) & set(B[n])
|
| 114 |
+
for l in links:
|
| 115 |
+
if not G.has_edge(u, n, l):
|
| 116 |
+
G.add_edge(u, n, key=l)
|
| 117 |
+
else:
|
| 118 |
+
G.add_edges_from((u, n) for n in nbrs2)
|
| 119 |
+
return G
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
@not_implemented_for("multigraph")
|
| 123 |
+
@nx._dispatchable(graphs="B", returns_graph=True)
|
| 124 |
+
def weighted_projected_graph(B, nodes, ratio=False):
|
| 125 |
+
r"""Returns a weighted projection of B onto one of its node sets.
|
| 126 |
+
|
| 127 |
+
The weighted projected graph is the projection of the bipartite
|
| 128 |
+
network B onto the specified nodes with weights representing the
|
| 129 |
+
number of shared neighbors or the ratio between actual shared
|
| 130 |
+
neighbors and possible shared neighbors if ``ratio is True`` [1]_.
|
| 131 |
+
The nodes retain their attributes and are connected in the resulting
|
| 132 |
+
graph if they have an edge to a common node in the original graph.
|
| 133 |
+
|
| 134 |
+
Parameters
|
| 135 |
+
----------
|
| 136 |
+
B : NetworkX graph
|
| 137 |
+
The input graph should be bipartite.
|
| 138 |
+
|
| 139 |
+
nodes : list or iterable
|
| 140 |
+
Distinct nodes to project onto (the "bottom" nodes).
|
| 141 |
+
|
| 142 |
+
ratio: Bool (default=False)
|
| 143 |
+
If True, edge weight is the ratio between actual shared neighbors
|
| 144 |
+
and maximum possible shared neighbors (i.e., the size of the other
|
| 145 |
+
node set). If False, edges weight is the number of shared neighbors.
|
| 146 |
+
|
| 147 |
+
Returns
|
| 148 |
+
-------
|
| 149 |
+
Graph : NetworkX graph
|
| 150 |
+
A graph that is the projection onto the given nodes.
|
| 151 |
+
|
| 152 |
+
Examples
|
| 153 |
+
--------
|
| 154 |
+
>>> from networkx.algorithms import bipartite
|
| 155 |
+
>>> B = nx.path_graph(4)
|
| 156 |
+
>>> G = bipartite.weighted_projected_graph(B, [1, 3])
|
| 157 |
+
>>> list(G)
|
| 158 |
+
[1, 3]
|
| 159 |
+
>>> list(G.edges(data=True))
|
| 160 |
+
[(1, 3, {'weight': 1})]
|
| 161 |
+
>>> G = bipartite.weighted_projected_graph(B, [1, 3], ratio=True)
|
| 162 |
+
>>> list(G.edges(data=True))
|
| 163 |
+
[(1, 3, {'weight': 0.5})]
|
| 164 |
+
|
| 165 |
+
Notes
|
| 166 |
+
-----
|
| 167 |
+
No attempt is made to verify that the input graph B is bipartite, or that
|
| 168 |
+
the input nodes are distinct. However, if the length of the input nodes is
|
| 169 |
+
greater than or equal to the nodes in the graph B, an exception is raised.
|
| 170 |
+
If the nodes are not distinct but don't raise this error, the output weights
|
| 171 |
+
will be incorrect.
|
| 172 |
+
The graph and node properties are (shallow) copied to the projected graph.
|
| 173 |
+
|
| 174 |
+
See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
|
| 175 |
+
for further details on how bipartite graphs are handled in NetworkX.
|
| 176 |
+
|
| 177 |
+
See Also
|
| 178 |
+
--------
|
| 179 |
+
is_bipartite,
|
| 180 |
+
is_bipartite_node_set,
|
| 181 |
+
sets,
|
| 182 |
+
collaboration_weighted_projected_graph,
|
| 183 |
+
overlap_weighted_projected_graph,
|
| 184 |
+
generic_weighted_projected_graph
|
| 185 |
+
projected_graph
|
| 186 |
+
|
| 187 |
+
References
|
| 188 |
+
----------
|
| 189 |
+
.. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation
|
| 190 |
+
Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook
|
| 191 |
+
of Social Network Analysis. Sage Publications.
|
| 192 |
+
"""
|
| 193 |
+
if B.is_directed():
|
| 194 |
+
pred = B.pred
|
| 195 |
+
G = nx.DiGraph()
|
| 196 |
+
else:
|
| 197 |
+
pred = B.adj
|
| 198 |
+
G = nx.Graph()
|
| 199 |
+
G.graph.update(B.graph)
|
| 200 |
+
G.add_nodes_from((n, B.nodes[n]) for n in nodes)
|
| 201 |
+
n_top = len(B) - len(nodes)
|
| 202 |
+
|
| 203 |
+
if n_top < 1:
|
| 204 |
+
raise NetworkXAlgorithmError(
|
| 205 |
+
f"the size of the nodes to project onto ({len(nodes)}) is >= the graph size ({len(B)}).\n"
|
| 206 |
+
"They are either not a valid bipartite partition or contain duplicates"
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
for u in nodes:
|
| 210 |
+
unbrs = set(B[u])
|
| 211 |
+
nbrs2 = {n for nbr in unbrs for n in B[nbr]} - {u}
|
| 212 |
+
for v in nbrs2:
|
| 213 |
+
vnbrs = set(pred[v])
|
| 214 |
+
common = unbrs & vnbrs
|
| 215 |
+
if not ratio:
|
| 216 |
+
weight = len(common)
|
| 217 |
+
else:
|
| 218 |
+
weight = len(common) / n_top
|
| 219 |
+
G.add_edge(u, v, weight=weight)
|
| 220 |
+
return G
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
@not_implemented_for("multigraph")
|
| 224 |
+
@nx._dispatchable(graphs="B", returns_graph=True)
|
| 225 |
+
def collaboration_weighted_projected_graph(B, nodes):
|
| 226 |
+
r"""Newman's weighted projection of B onto one of its node sets.
|
| 227 |
+
|
| 228 |
+
The collaboration weighted projection is the projection of the
|
| 229 |
+
bipartite network B onto the specified nodes with weights assigned
|
| 230 |
+
using Newman's collaboration model [1]_:
|
| 231 |
+
|
| 232 |
+
.. math::
|
| 233 |
+
|
| 234 |
+
w_{u, v} = \sum_k \frac{\delta_{u}^{k} \delta_{v}^{k}}{d_k - 1}
|
| 235 |
+
|
| 236 |
+
where `u` and `v` are nodes from the bottom bipartite node set,
|
| 237 |
+
and `k` is a node of the top node set.
|
| 238 |
+
The value `d_k` is the degree of node `k` in the bipartite
|
| 239 |
+
network and `\delta_{u}^{k}` is 1 if node `u` is
|
| 240 |
+
linked to node `k` in the original bipartite graph or 0 otherwise.
|
| 241 |
+
|
| 242 |
+
The nodes retain their attributes and are connected in the resulting
|
| 243 |
+
graph if have an edge to a common node in the original bipartite
|
| 244 |
+
graph.
|
| 245 |
+
|
| 246 |
+
Parameters
|
| 247 |
+
----------
|
| 248 |
+
B : NetworkX graph
|
| 249 |
+
The input graph should be bipartite.
|
| 250 |
+
|
| 251 |
+
nodes : list or iterable
|
| 252 |
+
Nodes to project onto (the "bottom" nodes).
|
| 253 |
+
|
| 254 |
+
Returns
|
| 255 |
+
-------
|
| 256 |
+
Graph : NetworkX graph
|
| 257 |
+
A graph that is the projection onto the given nodes.
|
| 258 |
+
|
| 259 |
+
Examples
|
| 260 |
+
--------
|
| 261 |
+
>>> from networkx.algorithms import bipartite
|
| 262 |
+
>>> B = nx.path_graph(5)
|
| 263 |
+
>>> B.add_edge(1, 5)
|
| 264 |
+
>>> G = bipartite.collaboration_weighted_projected_graph(B, [0, 2, 4, 5])
|
| 265 |
+
>>> list(G)
|
| 266 |
+
[0, 2, 4, 5]
|
| 267 |
+
>>> for edge in sorted(G.edges(data=True)):
|
| 268 |
+
... print(edge)
|
| 269 |
+
(0, 2, {'weight': 0.5})
|
| 270 |
+
(0, 5, {'weight': 0.5})
|
| 271 |
+
(2, 4, {'weight': 1.0})
|
| 272 |
+
(2, 5, {'weight': 0.5})
|
| 273 |
+
|
| 274 |
+
Notes
|
| 275 |
+
-----
|
| 276 |
+
No attempt is made to verify that the input graph B is bipartite.
|
| 277 |
+
The graph and node properties are (shallow) copied to the projected graph.
|
| 278 |
+
|
| 279 |
+
See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
|
| 280 |
+
for further details on how bipartite graphs are handled in NetworkX.
|
| 281 |
+
|
| 282 |
+
See Also
|
| 283 |
+
--------
|
| 284 |
+
is_bipartite,
|
| 285 |
+
is_bipartite_node_set,
|
| 286 |
+
sets,
|
| 287 |
+
weighted_projected_graph,
|
| 288 |
+
overlap_weighted_projected_graph,
|
| 289 |
+
generic_weighted_projected_graph,
|
| 290 |
+
projected_graph
|
| 291 |
+
|
| 292 |
+
References
|
| 293 |
+
----------
|
| 294 |
+
.. [1] Scientific collaboration networks: II.
|
| 295 |
+
Shortest paths, weighted networks, and centrality,
|
| 296 |
+
M. E. J. Newman, Phys. Rev. E 64, 016132 (2001).
|
| 297 |
+
"""
|
| 298 |
+
if B.is_directed():
|
| 299 |
+
pred = B.pred
|
| 300 |
+
G = nx.DiGraph()
|
| 301 |
+
else:
|
| 302 |
+
pred = B.adj
|
| 303 |
+
G = nx.Graph()
|
| 304 |
+
G.graph.update(B.graph)
|
| 305 |
+
G.add_nodes_from((n, B.nodes[n]) for n in nodes)
|
| 306 |
+
for u in nodes:
|
| 307 |
+
unbrs = set(B[u])
|
| 308 |
+
nbrs2 = {n for nbr in unbrs for n in B[nbr] if n != u}
|
| 309 |
+
for v in nbrs2:
|
| 310 |
+
vnbrs = set(pred[v])
|
| 311 |
+
common_degree = (len(B[n]) for n in unbrs & vnbrs)
|
| 312 |
+
weight = sum(1.0 / (deg - 1) for deg in common_degree if deg > 1)
|
| 313 |
+
G.add_edge(u, v, weight=weight)
|
| 314 |
+
return G
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
@not_implemented_for("multigraph")
|
| 318 |
+
@nx._dispatchable(graphs="B", returns_graph=True)
|
| 319 |
+
def overlap_weighted_projected_graph(B, nodes, jaccard=True):
|
| 320 |
+
r"""Overlap weighted projection of B onto one of its node sets.
|
| 321 |
+
|
| 322 |
+
The overlap weighted projection is the projection of the bipartite
|
| 323 |
+
network B onto the specified nodes with weights representing
|
| 324 |
+
the Jaccard index between the neighborhoods of the two nodes in the
|
| 325 |
+
original bipartite network [1]_:
|
| 326 |
+
|
| 327 |
+
.. math::
|
| 328 |
+
|
| 329 |
+
w_{v, u} = \frac{|N(u) \cap N(v)|}{|N(u) \cup N(v)|}
|
| 330 |
+
|
| 331 |
+
or if the parameter 'jaccard' is False, the fraction of common
|
| 332 |
+
neighbors by minimum of both nodes degree in the original
|
| 333 |
+
bipartite graph [1]_:
|
| 334 |
+
|
| 335 |
+
.. math::
|
| 336 |
+
|
| 337 |
+
w_{v, u} = \frac{|N(u) \cap N(v)|}{min(|N(u)|, |N(v)|)}
|
| 338 |
+
|
| 339 |
+
The nodes retain their attributes and are connected in the resulting
|
| 340 |
+
graph if have an edge to a common node in the original bipartite graph.
|
| 341 |
+
|
| 342 |
+
Parameters
|
| 343 |
+
----------
|
| 344 |
+
B : NetworkX graph
|
| 345 |
+
The input graph should be bipartite.
|
| 346 |
+
|
| 347 |
+
nodes : list or iterable
|
| 348 |
+
Nodes to project onto (the "bottom" nodes).
|
| 349 |
+
|
| 350 |
+
jaccard: Bool (default=True)
|
| 351 |
+
|
| 352 |
+
Returns
|
| 353 |
+
-------
|
| 354 |
+
Graph : NetworkX graph
|
| 355 |
+
A graph that is the projection onto the given nodes.
|
| 356 |
+
|
| 357 |
+
Examples
|
| 358 |
+
--------
|
| 359 |
+
>>> from networkx.algorithms import bipartite
|
| 360 |
+
>>> B = nx.path_graph(5)
|
| 361 |
+
>>> nodes = [0, 2, 4]
|
| 362 |
+
>>> G = bipartite.overlap_weighted_projected_graph(B, nodes)
|
| 363 |
+
>>> list(G)
|
| 364 |
+
[0, 2, 4]
|
| 365 |
+
>>> list(G.edges(data=True))
|
| 366 |
+
[(0, 2, {'weight': 0.5}), (2, 4, {'weight': 0.5})]
|
| 367 |
+
>>> G = bipartite.overlap_weighted_projected_graph(B, nodes, jaccard=False)
|
| 368 |
+
>>> list(G.edges(data=True))
|
| 369 |
+
[(0, 2, {'weight': 1.0}), (2, 4, {'weight': 1.0})]
|
| 370 |
+
|
| 371 |
+
Notes
|
| 372 |
+
-----
|
| 373 |
+
No attempt is made to verify that the input graph B is bipartite.
|
| 374 |
+
The graph and node properties are (shallow) copied to the projected graph.
|
| 375 |
+
|
| 376 |
+
See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
|
| 377 |
+
for further details on how bipartite graphs are handled in NetworkX.
|
| 378 |
+
|
| 379 |
+
See Also
|
| 380 |
+
--------
|
| 381 |
+
is_bipartite,
|
| 382 |
+
is_bipartite_node_set,
|
| 383 |
+
sets,
|
| 384 |
+
weighted_projected_graph,
|
| 385 |
+
collaboration_weighted_projected_graph,
|
| 386 |
+
generic_weighted_projected_graph,
|
| 387 |
+
projected_graph
|
| 388 |
+
|
| 389 |
+
References
|
| 390 |
+
----------
|
| 391 |
+
.. [1] Borgatti, S.P. and Halgin, D. In press. Analyzing Affiliation
|
| 392 |
+
Networks. In Carrington, P. and Scott, J. (eds) The Sage Handbook
|
| 393 |
+
of Social Network Analysis. Sage Publications.
|
| 394 |
+
|
| 395 |
+
"""
|
| 396 |
+
if B.is_directed():
|
| 397 |
+
pred = B.pred
|
| 398 |
+
G = nx.DiGraph()
|
| 399 |
+
else:
|
| 400 |
+
pred = B.adj
|
| 401 |
+
G = nx.Graph()
|
| 402 |
+
G.graph.update(B.graph)
|
| 403 |
+
G.add_nodes_from((n, B.nodes[n]) for n in nodes)
|
| 404 |
+
for u in nodes:
|
| 405 |
+
unbrs = set(B[u])
|
| 406 |
+
nbrs2 = {n for nbr in unbrs for n in B[nbr]} - {u}
|
| 407 |
+
for v in nbrs2:
|
| 408 |
+
vnbrs = set(pred[v])
|
| 409 |
+
if jaccard:
|
| 410 |
+
wt = len(unbrs & vnbrs) / len(unbrs | vnbrs)
|
| 411 |
+
else:
|
| 412 |
+
wt = len(unbrs & vnbrs) / min(len(unbrs), len(vnbrs))
|
| 413 |
+
G.add_edge(u, v, weight=wt)
|
| 414 |
+
return G
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
@not_implemented_for("multigraph")
|
| 418 |
+
@nx._dispatchable(graphs="B", preserve_all_attrs=True, returns_graph=True)
|
| 419 |
+
def generic_weighted_projected_graph(B, nodes, weight_function=None):
|
| 420 |
+
r"""Weighted projection of B with a user-specified weight function.
|
| 421 |
+
|
| 422 |
+
The bipartite network B is projected on to the specified nodes
|
| 423 |
+
with weights computed by a user-specified function. This function
|
| 424 |
+
must accept as a parameter the neighborhood sets of two nodes and
|
| 425 |
+
return an integer or a float.
|
| 426 |
+
|
| 427 |
+
The nodes retain their attributes and are connected in the resulting graph
|
| 428 |
+
if they have an edge to a common node in the original graph.
|
| 429 |
+
|
| 430 |
+
Parameters
|
| 431 |
+
----------
|
| 432 |
+
B : NetworkX graph
|
| 433 |
+
The input graph should be bipartite.
|
| 434 |
+
|
| 435 |
+
nodes : list or iterable
|
| 436 |
+
Nodes to project onto (the "bottom" nodes).
|
| 437 |
+
|
| 438 |
+
weight_function : function
|
| 439 |
+
This function must accept as parameters the same input graph
|
| 440 |
+
that this function, and two nodes; and return an integer or a float.
|
| 441 |
+
The default function computes the number of shared neighbors.
|
| 442 |
+
|
| 443 |
+
Returns
|
| 444 |
+
-------
|
| 445 |
+
Graph : NetworkX graph
|
| 446 |
+
A graph that is the projection onto the given nodes.
|
| 447 |
+
|
| 448 |
+
Examples
|
| 449 |
+
--------
|
| 450 |
+
>>> from networkx.algorithms import bipartite
|
| 451 |
+
>>> # Define some custom weight functions
|
| 452 |
+
>>> def jaccard(G, u, v):
|
| 453 |
+
... unbrs = set(G[u])
|
| 454 |
+
... vnbrs = set(G[v])
|
| 455 |
+
... return float(len(unbrs & vnbrs)) / len(unbrs | vnbrs)
|
| 456 |
+
>>> def my_weight(G, u, v, weight="weight"):
|
| 457 |
+
... w = 0
|
| 458 |
+
... for nbr in set(G[u]) & set(G[v]):
|
| 459 |
+
... w += G[u][nbr].get(weight, 1) + G[v][nbr].get(weight, 1)
|
| 460 |
+
... return w
|
| 461 |
+
>>> # A complete bipartite graph with 4 nodes and 4 edges
|
| 462 |
+
>>> B = nx.complete_bipartite_graph(2, 2)
|
| 463 |
+
>>> # Add some arbitrary weight to the edges
|
| 464 |
+
>>> for i, (u, v) in enumerate(B.edges()):
|
| 465 |
+
... B.edges[u, v]["weight"] = i + 1
|
| 466 |
+
>>> for edge in B.edges(data=True):
|
| 467 |
+
... print(edge)
|
| 468 |
+
(0, 2, {'weight': 1})
|
| 469 |
+
(0, 3, {'weight': 2})
|
| 470 |
+
(1, 2, {'weight': 3})
|
| 471 |
+
(1, 3, {'weight': 4})
|
| 472 |
+
>>> # By default, the weight is the number of shared neighbors
|
| 473 |
+
>>> G = bipartite.generic_weighted_projected_graph(B, [0, 1])
|
| 474 |
+
>>> print(list(G.edges(data=True)))
|
| 475 |
+
[(0, 1, {'weight': 2})]
|
| 476 |
+
>>> # To specify a custom weight function use the weight_function parameter
|
| 477 |
+
>>> G = bipartite.generic_weighted_projected_graph(
|
| 478 |
+
... B, [0, 1], weight_function=jaccard
|
| 479 |
+
... )
|
| 480 |
+
>>> print(list(G.edges(data=True)))
|
| 481 |
+
[(0, 1, {'weight': 1.0})]
|
| 482 |
+
>>> G = bipartite.generic_weighted_projected_graph(
|
| 483 |
+
... B, [0, 1], weight_function=my_weight
|
| 484 |
+
... )
|
| 485 |
+
>>> print(list(G.edges(data=True)))
|
| 486 |
+
[(0, 1, {'weight': 10})]
|
| 487 |
+
|
| 488 |
+
Notes
|
| 489 |
+
-----
|
| 490 |
+
No attempt is made to verify that the input graph B is bipartite.
|
| 491 |
+
The graph and node properties are (shallow) copied to the projected graph.
|
| 492 |
+
|
| 493 |
+
See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
|
| 494 |
+
for further details on how bipartite graphs are handled in NetworkX.
|
| 495 |
+
|
| 496 |
+
See Also
|
| 497 |
+
--------
|
| 498 |
+
is_bipartite,
|
| 499 |
+
is_bipartite_node_set,
|
| 500 |
+
sets,
|
| 501 |
+
weighted_projected_graph,
|
| 502 |
+
collaboration_weighted_projected_graph,
|
| 503 |
+
overlap_weighted_projected_graph,
|
| 504 |
+
projected_graph
|
| 505 |
+
|
| 506 |
+
"""
|
| 507 |
+
if B.is_directed():
|
| 508 |
+
pred = B.pred
|
| 509 |
+
G = nx.DiGraph()
|
| 510 |
+
else:
|
| 511 |
+
pred = B.adj
|
| 512 |
+
G = nx.Graph()
|
| 513 |
+
if weight_function is None:
|
| 514 |
+
|
| 515 |
+
def weight_function(G, u, v):
|
| 516 |
+
# Notice that we use set(pred[v]) for handling the directed case.
|
| 517 |
+
return len(set(G[u]) & set(pred[v]))
|
| 518 |
+
|
| 519 |
+
G.graph.update(B.graph)
|
| 520 |
+
G.add_nodes_from((n, B.nodes[n]) for n in nodes)
|
| 521 |
+
for u in nodes:
|
| 522 |
+
nbrs2 = {n for nbr in set(B[u]) for n in B[nbr]} - {u}
|
| 523 |
+
for v in nbrs2:
|
| 524 |
+
weight = weight_function(B, u, v)
|
| 525 |
+
G.add_edge(u, v, weight=weight)
|
| 526 |
+
return G
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/redundancy.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Node redundancy for bipartite graphs."""
|
| 2 |
+
|
| 3 |
+
from itertools import combinations
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
from networkx import NetworkXError
|
| 7 |
+
|
| 8 |
+
__all__ = ["node_redundancy"]
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@nx._dispatchable
|
| 12 |
+
def node_redundancy(G, nodes=None):
|
| 13 |
+
r"""Computes the node redundancy coefficients for the nodes in the bipartite
|
| 14 |
+
graph `G`.
|
| 15 |
+
|
| 16 |
+
The redundancy coefficient of a node `v` is the fraction of pairs of
|
| 17 |
+
neighbors of `v` that are both linked to other nodes. In a one-mode
|
| 18 |
+
projection these nodes would be linked together even if `v` were
|
| 19 |
+
not there.
|
| 20 |
+
|
| 21 |
+
More formally, for any vertex `v`, the *redundancy coefficient of `v`* is
|
| 22 |
+
defined by
|
| 23 |
+
|
| 24 |
+
.. math::
|
| 25 |
+
|
| 26 |
+
rc(v) = \frac{|\{\{u, w\} \subseteq N(v),
|
| 27 |
+
\: \exists v' \neq v,\: (v',u) \in E\:
|
| 28 |
+
\mathrm{and}\: (v',w) \in E\}|}{ \frac{|N(v)|(|N(v)|-1)}{2}},
|
| 29 |
+
|
| 30 |
+
where `N(v)` is the set of neighbors of `v` in `G`.
|
| 31 |
+
|
| 32 |
+
Parameters
|
| 33 |
+
----------
|
| 34 |
+
G : graph
|
| 35 |
+
A bipartite graph
|
| 36 |
+
|
| 37 |
+
nodes : list or iterable (optional)
|
| 38 |
+
Compute redundancy for these nodes. The default is all nodes in G.
|
| 39 |
+
|
| 40 |
+
Returns
|
| 41 |
+
-------
|
| 42 |
+
redundancy : dictionary
|
| 43 |
+
A dictionary keyed by node with the node redundancy value.
|
| 44 |
+
|
| 45 |
+
Examples
|
| 46 |
+
--------
|
| 47 |
+
Compute the redundancy coefficient of each node in a graph::
|
| 48 |
+
|
| 49 |
+
>>> from networkx.algorithms import bipartite
|
| 50 |
+
>>> G = nx.cycle_graph(4)
|
| 51 |
+
>>> rc = bipartite.node_redundancy(G)
|
| 52 |
+
>>> rc[0]
|
| 53 |
+
1.0
|
| 54 |
+
|
| 55 |
+
Compute the average redundancy for the graph::
|
| 56 |
+
|
| 57 |
+
>>> from networkx.algorithms import bipartite
|
| 58 |
+
>>> G = nx.cycle_graph(4)
|
| 59 |
+
>>> rc = bipartite.node_redundancy(G)
|
| 60 |
+
>>> sum(rc.values()) / len(G)
|
| 61 |
+
1.0
|
| 62 |
+
|
| 63 |
+
Compute the average redundancy for a set of nodes::
|
| 64 |
+
|
| 65 |
+
>>> from networkx.algorithms import bipartite
|
| 66 |
+
>>> G = nx.cycle_graph(4)
|
| 67 |
+
>>> rc = bipartite.node_redundancy(G)
|
| 68 |
+
>>> nodes = [0, 2]
|
| 69 |
+
>>> sum(rc[n] for n in nodes) / len(nodes)
|
| 70 |
+
1.0
|
| 71 |
+
|
| 72 |
+
Raises
|
| 73 |
+
------
|
| 74 |
+
NetworkXError
|
| 75 |
+
If any of the nodes in the graph (or in `nodes`, if specified) has
|
| 76 |
+
(out-)degree less than two (which would result in division by zero,
|
| 77 |
+
according to the definition of the redundancy coefficient).
|
| 78 |
+
|
| 79 |
+
References
|
| 80 |
+
----------
|
| 81 |
+
.. [1] Latapy, Matthieu, Clémence Magnien, and Nathalie Del Vecchio (2008).
|
| 82 |
+
Basic notions for the analysis of large two-mode networks.
|
| 83 |
+
Social Networks 30(1), 31--48.
|
| 84 |
+
|
| 85 |
+
"""
|
| 86 |
+
if nodes is None:
|
| 87 |
+
nodes = G
|
| 88 |
+
if any(len(G[v]) < 2 for v in nodes):
|
| 89 |
+
raise NetworkXError(
|
| 90 |
+
"Cannot compute redundancy coefficient for a node"
|
| 91 |
+
" that has fewer than two neighbors."
|
| 92 |
+
)
|
| 93 |
+
# TODO This can be trivially parallelized.
|
| 94 |
+
return {v: _node_redundancy(G, v) for v in nodes}
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def _node_redundancy(G, v):
|
| 98 |
+
"""Returns the redundancy of the node `v` in the bipartite graph `G`.
|
| 99 |
+
|
| 100 |
+
If `G` is a graph with `n` nodes, the redundancy of a node is the ratio
|
| 101 |
+
of the "overlap" of `v` to the maximum possible overlap of `v`
|
| 102 |
+
according to its degree. The overlap of `v` is the number of pairs of
|
| 103 |
+
neighbors that have mutual neighbors themselves, other than `v`.
|
| 104 |
+
|
| 105 |
+
`v` must have at least two neighbors in `G`.
|
| 106 |
+
|
| 107 |
+
"""
|
| 108 |
+
n = len(G[v])
|
| 109 |
+
overlap = sum(
|
| 110 |
+
1 for (u, w) in combinations(G[v], 2) if (set(G[u]) & set(G[w])) - {v}
|
| 111 |
+
)
|
| 112 |
+
return (2 * overlap) / (n * (n - 1))
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/spectral.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Spectral bipartivity measure.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
|
| 7 |
+
__all__ = ["spectral_bipartivity"]
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@nx._dispatchable(edge_attrs="weight")
|
| 11 |
+
def spectral_bipartivity(G, nodes=None, weight="weight"):
|
| 12 |
+
"""Returns the spectral bipartivity.
|
| 13 |
+
|
| 14 |
+
Parameters
|
| 15 |
+
----------
|
| 16 |
+
G : NetworkX graph
|
| 17 |
+
|
| 18 |
+
nodes : list or container optional(default is all nodes)
|
| 19 |
+
Nodes to return value of spectral bipartivity contribution.
|
| 20 |
+
|
| 21 |
+
weight : string or None optional (default = 'weight')
|
| 22 |
+
Edge data key to use for edge weights. If None, weights set to 1.
|
| 23 |
+
|
| 24 |
+
Returns
|
| 25 |
+
-------
|
| 26 |
+
sb : float or dict
|
| 27 |
+
A single number if the keyword nodes is not specified, or
|
| 28 |
+
a dictionary keyed by node with the spectral bipartivity contribution
|
| 29 |
+
of that node as the value.
|
| 30 |
+
|
| 31 |
+
Examples
|
| 32 |
+
--------
|
| 33 |
+
>>> from networkx.algorithms import bipartite
|
| 34 |
+
>>> G = nx.path_graph(4)
|
| 35 |
+
>>> bipartite.spectral_bipartivity(G)
|
| 36 |
+
1.0
|
| 37 |
+
|
| 38 |
+
Notes
|
| 39 |
+
-----
|
| 40 |
+
This implementation uses Numpy (dense) matrices which are not efficient
|
| 41 |
+
for storing large sparse graphs.
|
| 42 |
+
|
| 43 |
+
See Also
|
| 44 |
+
--------
|
| 45 |
+
color
|
| 46 |
+
|
| 47 |
+
References
|
| 48 |
+
----------
|
| 49 |
+
.. [1] E. Estrada and J. A. Rodríguez-Velázquez, "Spectral measures of
|
| 50 |
+
bipartivity in complex networks", PhysRev E 72, 046105 (2005)
|
| 51 |
+
"""
|
| 52 |
+
import scipy as sp
|
| 53 |
+
|
| 54 |
+
nodelist = list(G) # ordering of nodes in matrix
|
| 55 |
+
A = nx.to_numpy_array(G, nodelist, weight=weight)
|
| 56 |
+
expA = sp.linalg.expm(A)
|
| 57 |
+
expmA = sp.linalg.expm(-A)
|
| 58 |
+
coshA = 0.5 * (expA + expmA)
|
| 59 |
+
if nodes is None:
|
| 60 |
+
# return single number for entire graph
|
| 61 |
+
return float(coshA.diagonal().sum() / expA.diagonal().sum())
|
| 62 |
+
else:
|
| 63 |
+
# contribution for individual nodes
|
| 64 |
+
index = dict(zip(nodelist, range(len(nodelist))))
|
| 65 |
+
sb = {}
|
| 66 |
+
for n in nodes:
|
| 67 |
+
i = index[n]
|
| 68 |
+
sb[n] = coshA.item(i, i) / expA.item(i, i)
|
| 69 |
+
return sb
|
.venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_betweenness_centrality.cpython-311.pyc
ADDED
|
Binary file (38.5 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_betweenness_centrality.cpython-311.pyc
ADDED
|
Binary file (15.3 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_betweenness_centrality_subset.cpython-311.pyc
ADDED
|
Binary file (10.7 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_closeness.cpython-311.pyc
ADDED
|
Binary file (3.54 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_group.cpython-311.pyc
ADDED
|
Binary file (16.4 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_load_centrality.cpython-311.pyc
ADDED
|
Binary file (16.5 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_voterank.cpython-311.pyc
ADDED
|
Binary file (3.33 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .beamsearch import *
|
| 2 |
+
from .breadth_first_search import *
|
| 3 |
+
from .depth_first_search import *
|
| 4 |
+
from .edgedfs import *
|
| 5 |
+
from .edgebfs import *
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (377 Bytes). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/beamsearch.cpython-311.pyc
ADDED
|
Binary file (3.54 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/breadth_first_search.cpython-311.pyc
ADDED
|
Binary file (20.1 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/depth_first_search.cpython-311.pyc
ADDED
|
Binary file (18.5 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/edgebfs.cpython-311.pyc
ADDED
|
Binary file (8.31 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/__pycache__/edgedfs.cpython-311.pyc
ADDED
|
Binary file (7.45 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/beamsearch.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Basic algorithms for breadth-first searching the nodes of a graph."""
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
|
| 5 |
+
__all__ = ["bfs_beam_edges"]
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@nx._dispatchable
|
| 9 |
+
def bfs_beam_edges(G, source, value, width=None):
|
| 10 |
+
"""Iterates over edges in a beam search.
|
| 11 |
+
|
| 12 |
+
The beam search is a generalized breadth-first search in which only
|
| 13 |
+
the "best" *w* neighbors of the current node are enqueued, where *w*
|
| 14 |
+
is the beam width and "best" is an application-specific
|
| 15 |
+
heuristic. In general, a beam search with a small beam width might
|
| 16 |
+
not visit each node in the graph.
|
| 17 |
+
|
| 18 |
+
.. note::
|
| 19 |
+
|
| 20 |
+
With the default value of ``width=None`` or `width` greater than the
|
| 21 |
+
maximum degree of the graph, this function equates to a slower
|
| 22 |
+
version of `~networkx.algorithms.traversal.breadth_first_search.bfs_edges`.
|
| 23 |
+
All nodes will be visited, though the order of the reported edges may
|
| 24 |
+
vary. In such cases, `value` has no effect - consider using `bfs_edges`
|
| 25 |
+
directly instead.
|
| 26 |
+
|
| 27 |
+
Parameters
|
| 28 |
+
----------
|
| 29 |
+
G : NetworkX graph
|
| 30 |
+
|
| 31 |
+
source : node
|
| 32 |
+
Starting node for the breadth-first search; this function
|
| 33 |
+
iterates over only those edges in the component reachable from
|
| 34 |
+
this node.
|
| 35 |
+
|
| 36 |
+
value : function
|
| 37 |
+
A function that takes a node of the graph as input and returns a
|
| 38 |
+
real number indicating how "good" it is. A higher value means it
|
| 39 |
+
is more likely to be visited sooner during the search. When
|
| 40 |
+
visiting a new node, only the `width` neighbors with the highest
|
| 41 |
+
`value` are enqueued (in decreasing order of `value`).
|
| 42 |
+
|
| 43 |
+
width : int (default = None)
|
| 44 |
+
The beam width for the search. This is the number of neighbors
|
| 45 |
+
(ordered by `value`) to enqueue when visiting each new node.
|
| 46 |
+
|
| 47 |
+
Yields
|
| 48 |
+
------
|
| 49 |
+
edge
|
| 50 |
+
Edges in the beam search starting from `source`, given as a pair
|
| 51 |
+
of nodes.
|
| 52 |
+
|
| 53 |
+
Examples
|
| 54 |
+
--------
|
| 55 |
+
To give nodes with, for example, a higher centrality precedence
|
| 56 |
+
during the search, set the `value` function to return the centrality
|
| 57 |
+
value of the node:
|
| 58 |
+
|
| 59 |
+
>>> G = nx.karate_club_graph()
|
| 60 |
+
>>> centrality = nx.eigenvector_centrality(G)
|
| 61 |
+
>>> list(nx.bfs_beam_edges(G, source=0, value=centrality.get, width=3))
|
| 62 |
+
[(0, 2), (0, 1), (0, 8), (2, 32), (1, 13), (8, 33)]
|
| 63 |
+
"""
|
| 64 |
+
|
| 65 |
+
if width is None:
|
| 66 |
+
width = len(G)
|
| 67 |
+
|
| 68 |
+
def successors(v):
|
| 69 |
+
"""Returns a list of the best neighbors of a node.
|
| 70 |
+
|
| 71 |
+
`v` is a node in the graph `G`.
|
| 72 |
+
|
| 73 |
+
The "best" neighbors are chosen according to the `value`
|
| 74 |
+
function (higher is better). Only the `width` best neighbors of
|
| 75 |
+
`v` are returned.
|
| 76 |
+
"""
|
| 77 |
+
# TODO The Python documentation states that for small values, it
|
| 78 |
+
# is better to use `heapq.nlargest`. We should determine the
|
| 79 |
+
# threshold at which its better to use `heapq.nlargest()`
|
| 80 |
+
# instead of `sorted()[:]` and apply that optimization here.
|
| 81 |
+
#
|
| 82 |
+
# If `width` is greater than the number of neighbors of `v`, all
|
| 83 |
+
# neighbors are returned by the semantics of slicing in
|
| 84 |
+
# Python. This occurs in the special case that the user did not
|
| 85 |
+
# specify a `width`: in this case all neighbors are always
|
| 86 |
+
# returned, so this is just a (slower) implementation of
|
| 87 |
+
# `bfs_edges(G, source)` but with a sorted enqueue step.
|
| 88 |
+
return iter(sorted(G.neighbors(v), key=value, reverse=True)[:width])
|
| 89 |
+
|
| 90 |
+
yield from nx.generic_bfs_edges(G, source, successors)
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/breadth_first_search.py
ADDED
|
@@ -0,0 +1,575 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Basic algorithms for breadth-first searching the nodes of a graph."""
|
| 2 |
+
|
| 3 |
+
from collections import deque
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
|
| 7 |
+
__all__ = [
|
| 8 |
+
"bfs_edges",
|
| 9 |
+
"bfs_tree",
|
| 10 |
+
"bfs_predecessors",
|
| 11 |
+
"bfs_successors",
|
| 12 |
+
"descendants_at_distance",
|
| 13 |
+
"bfs_layers",
|
| 14 |
+
"bfs_labeled_edges",
|
| 15 |
+
"generic_bfs_edges",
|
| 16 |
+
]
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@nx._dispatchable
|
| 20 |
+
def generic_bfs_edges(G, source, neighbors=None, depth_limit=None):
|
| 21 |
+
"""Iterate over edges in a breadth-first search.
|
| 22 |
+
|
| 23 |
+
The breadth-first search begins at `source` and enqueues the
|
| 24 |
+
neighbors of newly visited nodes specified by the `neighbors`
|
| 25 |
+
function.
|
| 26 |
+
|
| 27 |
+
Parameters
|
| 28 |
+
----------
|
| 29 |
+
G : NetworkX graph
|
| 30 |
+
|
| 31 |
+
source : node
|
| 32 |
+
Starting node for the breadth-first search; this function
|
| 33 |
+
iterates over only those edges in the component reachable from
|
| 34 |
+
this node.
|
| 35 |
+
|
| 36 |
+
neighbors : function
|
| 37 |
+
A function that takes a newly visited node of the graph as input
|
| 38 |
+
and returns an *iterator* (not just a list) of nodes that are
|
| 39 |
+
neighbors of that node with custom ordering. If not specified, this is
|
| 40 |
+
just the ``G.neighbors`` method, but in general it can be any function
|
| 41 |
+
that returns an iterator over some or all of the neighbors of a
|
| 42 |
+
given node, in any order.
|
| 43 |
+
|
| 44 |
+
depth_limit : int, optional(default=len(G))
|
| 45 |
+
Specify the maximum search depth.
|
| 46 |
+
|
| 47 |
+
Yields
|
| 48 |
+
------
|
| 49 |
+
edge
|
| 50 |
+
Edges in the breadth-first search starting from `source`.
|
| 51 |
+
|
| 52 |
+
Examples
|
| 53 |
+
--------
|
| 54 |
+
>>> G = nx.path_graph(7)
|
| 55 |
+
>>> list(nx.generic_bfs_edges(G, source=0))
|
| 56 |
+
[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]
|
| 57 |
+
>>> list(nx.generic_bfs_edges(G, source=2))
|
| 58 |
+
[(2, 1), (2, 3), (1, 0), (3, 4), (4, 5), (5, 6)]
|
| 59 |
+
>>> list(nx.generic_bfs_edges(G, source=2, depth_limit=2))
|
| 60 |
+
[(2, 1), (2, 3), (1, 0), (3, 4)]
|
| 61 |
+
|
| 62 |
+
The `neighbors` param can be used to specify the visitation order of each
|
| 63 |
+
node's neighbors generically. In the following example, we modify the default
|
| 64 |
+
neighbor to return *odd* nodes first:
|
| 65 |
+
|
| 66 |
+
>>> def odd_first(n):
|
| 67 |
+
... return sorted(G.neighbors(n), key=lambda x: x % 2, reverse=True)
|
| 68 |
+
|
| 69 |
+
>>> G = nx.star_graph(5)
|
| 70 |
+
>>> list(nx.generic_bfs_edges(G, source=0)) # Default neighbor ordering
|
| 71 |
+
[(0, 1), (0, 2), (0, 3), (0, 4), (0, 5)]
|
| 72 |
+
>>> list(nx.generic_bfs_edges(G, source=0, neighbors=odd_first))
|
| 73 |
+
[(0, 1), (0, 3), (0, 5), (0, 2), (0, 4)]
|
| 74 |
+
|
| 75 |
+
Notes
|
| 76 |
+
-----
|
| 77 |
+
This implementation is from `PADS`_, which was in the public domain
|
| 78 |
+
when it was first accessed in July, 2004. The modifications
|
| 79 |
+
to allow depth limits are based on the Wikipedia article
|
| 80 |
+
"`Depth-limited-search`_".
|
| 81 |
+
|
| 82 |
+
.. _PADS: http://www.ics.uci.edu/~eppstein/PADS/BFS.py
|
| 83 |
+
.. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
|
| 84 |
+
"""
|
| 85 |
+
if neighbors is None:
|
| 86 |
+
neighbors = G.neighbors
|
| 87 |
+
if depth_limit is None:
|
| 88 |
+
depth_limit = len(G)
|
| 89 |
+
|
| 90 |
+
seen = {source}
|
| 91 |
+
n = len(G)
|
| 92 |
+
depth = 0
|
| 93 |
+
next_parents_children = [(source, neighbors(source))]
|
| 94 |
+
while next_parents_children and depth < depth_limit:
|
| 95 |
+
this_parents_children = next_parents_children
|
| 96 |
+
next_parents_children = []
|
| 97 |
+
for parent, children in this_parents_children:
|
| 98 |
+
for child in children:
|
| 99 |
+
if child not in seen:
|
| 100 |
+
seen.add(child)
|
| 101 |
+
next_parents_children.append((child, neighbors(child)))
|
| 102 |
+
yield parent, child
|
| 103 |
+
if len(seen) == n:
|
| 104 |
+
return
|
| 105 |
+
depth += 1
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
@nx._dispatchable
|
| 109 |
+
def bfs_edges(G, source, reverse=False, depth_limit=None, sort_neighbors=None):
|
| 110 |
+
"""Iterate over edges in a breadth-first-search starting at source.
|
| 111 |
+
|
| 112 |
+
Parameters
|
| 113 |
+
----------
|
| 114 |
+
G : NetworkX graph
|
| 115 |
+
|
| 116 |
+
source : node
|
| 117 |
+
Specify starting node for breadth-first search; this function
|
| 118 |
+
iterates over only those edges in the component reachable from
|
| 119 |
+
this node.
|
| 120 |
+
|
| 121 |
+
reverse : bool, optional
|
| 122 |
+
If True traverse a directed graph in the reverse direction
|
| 123 |
+
|
| 124 |
+
depth_limit : int, optional(default=len(G))
|
| 125 |
+
Specify the maximum search depth
|
| 126 |
+
|
| 127 |
+
sort_neighbors : function (default=None)
|
| 128 |
+
A function that takes an iterator over nodes as the input, and
|
| 129 |
+
returns an iterable of the same nodes with a custom ordering.
|
| 130 |
+
For example, `sorted` will sort the nodes in increasing order.
|
| 131 |
+
|
| 132 |
+
Yields
|
| 133 |
+
------
|
| 134 |
+
edge: 2-tuple of nodes
|
| 135 |
+
Yields edges resulting from the breadth-first search.
|
| 136 |
+
|
| 137 |
+
Examples
|
| 138 |
+
--------
|
| 139 |
+
To get the edges in a breadth-first search::
|
| 140 |
+
|
| 141 |
+
>>> G = nx.path_graph(3)
|
| 142 |
+
>>> list(nx.bfs_edges(G, 0))
|
| 143 |
+
[(0, 1), (1, 2)]
|
| 144 |
+
>>> list(nx.bfs_edges(G, source=0, depth_limit=1))
|
| 145 |
+
[(0, 1)]
|
| 146 |
+
|
| 147 |
+
To get the nodes in a breadth-first search order::
|
| 148 |
+
|
| 149 |
+
>>> G = nx.path_graph(3)
|
| 150 |
+
>>> root = 2
|
| 151 |
+
>>> edges = nx.bfs_edges(G, root)
|
| 152 |
+
>>> nodes = [root] + [v for u, v in edges]
|
| 153 |
+
>>> nodes
|
| 154 |
+
[2, 1, 0]
|
| 155 |
+
|
| 156 |
+
Notes
|
| 157 |
+
-----
|
| 158 |
+
The naming of this function is very similar to
|
| 159 |
+
:func:`~networkx.algorithms.traversal.edgebfs.edge_bfs`. The difference
|
| 160 |
+
is that ``edge_bfs`` yields edges even if they extend back to an already
|
| 161 |
+
explored node while this generator yields the edges of the tree that results
|
| 162 |
+
from a breadth-first-search (BFS) so no edges are reported if they extend
|
| 163 |
+
to already explored nodes. That means ``edge_bfs`` reports all edges while
|
| 164 |
+
``bfs_edges`` only reports those traversed by a node-based BFS. Yet another
|
| 165 |
+
description is that ``bfs_edges`` reports the edges traversed during BFS
|
| 166 |
+
while ``edge_bfs`` reports all edges in the order they are explored.
|
| 167 |
+
|
| 168 |
+
Based on the breadth-first search implementation in PADS [1]_
|
| 169 |
+
by D. Eppstein, July 2004; with modifications to allow depth limits
|
| 170 |
+
as described in [2]_.
|
| 171 |
+
|
| 172 |
+
References
|
| 173 |
+
----------
|
| 174 |
+
.. [1] http://www.ics.uci.edu/~eppstein/PADS/BFS.py.
|
| 175 |
+
.. [2] https://en.wikipedia.org/wiki/Depth-limited_search
|
| 176 |
+
|
| 177 |
+
See Also
|
| 178 |
+
--------
|
| 179 |
+
bfs_tree
|
| 180 |
+
:func:`~networkx.algorithms.traversal.depth_first_search.dfs_edges`
|
| 181 |
+
:func:`~networkx.algorithms.traversal.edgebfs.edge_bfs`
|
| 182 |
+
|
| 183 |
+
"""
|
| 184 |
+
if reverse and G.is_directed():
|
| 185 |
+
successors = G.predecessors
|
| 186 |
+
else:
|
| 187 |
+
successors = G.neighbors
|
| 188 |
+
|
| 189 |
+
if sort_neighbors is not None:
|
| 190 |
+
yield from generic_bfs_edges(
|
| 191 |
+
G, source, lambda node: iter(sort_neighbors(successors(node))), depth_limit
|
| 192 |
+
)
|
| 193 |
+
else:
|
| 194 |
+
yield from generic_bfs_edges(G, source, successors, depth_limit)
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
@nx._dispatchable(returns_graph=True)
|
| 198 |
+
def bfs_tree(G, source, reverse=False, depth_limit=None, sort_neighbors=None):
|
| 199 |
+
"""Returns an oriented tree constructed from of a breadth-first-search
|
| 200 |
+
starting at source.
|
| 201 |
+
|
| 202 |
+
Parameters
|
| 203 |
+
----------
|
| 204 |
+
G : NetworkX graph
|
| 205 |
+
|
| 206 |
+
source : node
|
| 207 |
+
Specify starting node for breadth-first search
|
| 208 |
+
|
| 209 |
+
reverse : bool, optional
|
| 210 |
+
If True traverse a directed graph in the reverse direction
|
| 211 |
+
|
| 212 |
+
depth_limit : int, optional(default=len(G))
|
| 213 |
+
Specify the maximum search depth
|
| 214 |
+
|
| 215 |
+
sort_neighbors : function (default=None)
|
| 216 |
+
A function that takes an iterator over nodes as the input, and
|
| 217 |
+
returns an iterable of the same nodes with a custom ordering.
|
| 218 |
+
For example, `sorted` will sort the nodes in increasing order.
|
| 219 |
+
|
| 220 |
+
Returns
|
| 221 |
+
-------
|
| 222 |
+
T: NetworkX DiGraph
|
| 223 |
+
An oriented tree
|
| 224 |
+
|
| 225 |
+
Examples
|
| 226 |
+
--------
|
| 227 |
+
>>> G = nx.path_graph(3)
|
| 228 |
+
>>> list(nx.bfs_tree(G, 1).edges())
|
| 229 |
+
[(1, 0), (1, 2)]
|
| 230 |
+
>>> H = nx.Graph()
|
| 231 |
+
>>> nx.add_path(H, [0, 1, 2, 3, 4, 5, 6])
|
| 232 |
+
>>> nx.add_path(H, [2, 7, 8, 9, 10])
|
| 233 |
+
>>> sorted(list(nx.bfs_tree(H, source=3, depth_limit=3).edges()))
|
| 234 |
+
[(1, 0), (2, 1), (2, 7), (3, 2), (3, 4), (4, 5), (5, 6), (7, 8)]
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
Notes
|
| 238 |
+
-----
|
| 239 |
+
Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
|
| 240 |
+
by D. Eppstein, July 2004. The modifications
|
| 241 |
+
to allow depth limits based on the Wikipedia article
|
| 242 |
+
"`Depth-limited-search`_".
|
| 243 |
+
|
| 244 |
+
.. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
|
| 245 |
+
|
| 246 |
+
See Also
|
| 247 |
+
--------
|
| 248 |
+
dfs_tree
|
| 249 |
+
bfs_edges
|
| 250 |
+
edge_bfs
|
| 251 |
+
"""
|
| 252 |
+
T = nx.DiGraph()
|
| 253 |
+
T.add_node(source)
|
| 254 |
+
edges_gen = bfs_edges(
|
| 255 |
+
G,
|
| 256 |
+
source,
|
| 257 |
+
reverse=reverse,
|
| 258 |
+
depth_limit=depth_limit,
|
| 259 |
+
sort_neighbors=sort_neighbors,
|
| 260 |
+
)
|
| 261 |
+
T.add_edges_from(edges_gen)
|
| 262 |
+
return T
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
@nx._dispatchable
|
| 266 |
+
def bfs_predecessors(G, source, depth_limit=None, sort_neighbors=None):
|
| 267 |
+
"""Returns an iterator of predecessors in breadth-first-search from source.
|
| 268 |
+
|
| 269 |
+
Parameters
|
| 270 |
+
----------
|
| 271 |
+
G : NetworkX graph
|
| 272 |
+
|
| 273 |
+
source : node
|
| 274 |
+
Specify starting node for breadth-first search
|
| 275 |
+
|
| 276 |
+
depth_limit : int, optional(default=len(G))
|
| 277 |
+
Specify the maximum search depth
|
| 278 |
+
|
| 279 |
+
sort_neighbors : function (default=None)
|
| 280 |
+
A function that takes an iterator over nodes as the input, and
|
| 281 |
+
returns an iterable of the same nodes with a custom ordering.
|
| 282 |
+
For example, `sorted` will sort the nodes in increasing order.
|
| 283 |
+
|
| 284 |
+
Returns
|
| 285 |
+
-------
|
| 286 |
+
pred: iterator
|
| 287 |
+
(node, predecessor) iterator where `predecessor` is the predecessor of
|
| 288 |
+
`node` in a breadth first search starting from `source`.
|
| 289 |
+
|
| 290 |
+
Examples
|
| 291 |
+
--------
|
| 292 |
+
>>> G = nx.path_graph(3)
|
| 293 |
+
>>> dict(nx.bfs_predecessors(G, 0))
|
| 294 |
+
{1: 0, 2: 1}
|
| 295 |
+
>>> H = nx.Graph()
|
| 296 |
+
>>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
|
| 297 |
+
>>> dict(nx.bfs_predecessors(H, 0))
|
| 298 |
+
{1: 0, 2: 0, 3: 1, 4: 1, 5: 2, 6: 2}
|
| 299 |
+
>>> M = nx.Graph()
|
| 300 |
+
>>> nx.add_path(M, [0, 1, 2, 3, 4, 5, 6])
|
| 301 |
+
>>> nx.add_path(M, [2, 7, 8, 9, 10])
|
| 302 |
+
>>> sorted(nx.bfs_predecessors(M, source=1, depth_limit=3))
|
| 303 |
+
[(0, 1), (2, 1), (3, 2), (4, 3), (7, 2), (8, 7)]
|
| 304 |
+
>>> N = nx.DiGraph()
|
| 305 |
+
>>> nx.add_path(N, [0, 1, 2, 3, 4, 7])
|
| 306 |
+
>>> nx.add_path(N, [3, 5, 6, 7])
|
| 307 |
+
>>> sorted(nx.bfs_predecessors(N, source=2))
|
| 308 |
+
[(3, 2), (4, 3), (5, 3), (6, 5), (7, 4)]
|
| 309 |
+
|
| 310 |
+
Notes
|
| 311 |
+
-----
|
| 312 |
+
Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
|
| 313 |
+
by D. Eppstein, July 2004. The modifications
|
| 314 |
+
to allow depth limits based on the Wikipedia article
|
| 315 |
+
"`Depth-limited-search`_".
|
| 316 |
+
|
| 317 |
+
.. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
|
| 318 |
+
|
| 319 |
+
See Also
|
| 320 |
+
--------
|
| 321 |
+
bfs_tree
|
| 322 |
+
bfs_edges
|
| 323 |
+
edge_bfs
|
| 324 |
+
"""
|
| 325 |
+
for s, t in bfs_edges(
|
| 326 |
+
G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
|
| 327 |
+
):
|
| 328 |
+
yield (t, s)
|
| 329 |
+
|
| 330 |
+
|
| 331 |
+
@nx._dispatchable
|
| 332 |
+
def bfs_successors(G, source, depth_limit=None, sort_neighbors=None):
|
| 333 |
+
"""Returns an iterator of successors in breadth-first-search from source.
|
| 334 |
+
|
| 335 |
+
Parameters
|
| 336 |
+
----------
|
| 337 |
+
G : NetworkX graph
|
| 338 |
+
|
| 339 |
+
source : node
|
| 340 |
+
Specify starting node for breadth-first search
|
| 341 |
+
|
| 342 |
+
depth_limit : int, optional(default=len(G))
|
| 343 |
+
Specify the maximum search depth
|
| 344 |
+
|
| 345 |
+
sort_neighbors : function (default=None)
|
| 346 |
+
A function that takes an iterator over nodes as the input, and
|
| 347 |
+
returns an iterable of the same nodes with a custom ordering.
|
| 348 |
+
For example, `sorted` will sort the nodes in increasing order.
|
| 349 |
+
|
| 350 |
+
Returns
|
| 351 |
+
-------
|
| 352 |
+
succ: iterator
|
| 353 |
+
(node, successors) iterator where `successors` is the non-empty list of
|
| 354 |
+
successors of `node` in a breadth first search from `source`.
|
| 355 |
+
To appear in the iterator, `node` must have successors.
|
| 356 |
+
|
| 357 |
+
Examples
|
| 358 |
+
--------
|
| 359 |
+
>>> G = nx.path_graph(3)
|
| 360 |
+
>>> dict(nx.bfs_successors(G, 0))
|
| 361 |
+
{0: [1], 1: [2]}
|
| 362 |
+
>>> H = nx.Graph()
|
| 363 |
+
>>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
|
| 364 |
+
>>> dict(nx.bfs_successors(H, 0))
|
| 365 |
+
{0: [1, 2], 1: [3, 4], 2: [5, 6]}
|
| 366 |
+
>>> G = nx.Graph()
|
| 367 |
+
>>> nx.add_path(G, [0, 1, 2, 3, 4, 5, 6])
|
| 368 |
+
>>> nx.add_path(G, [2, 7, 8, 9, 10])
|
| 369 |
+
>>> dict(nx.bfs_successors(G, source=1, depth_limit=3))
|
| 370 |
+
{1: [0, 2], 2: [3, 7], 3: [4], 7: [8]}
|
| 371 |
+
>>> G = nx.DiGraph()
|
| 372 |
+
>>> nx.add_path(G, [0, 1, 2, 3, 4, 5])
|
| 373 |
+
>>> dict(nx.bfs_successors(G, source=3))
|
| 374 |
+
{3: [4], 4: [5]}
|
| 375 |
+
|
| 376 |
+
Notes
|
| 377 |
+
-----
|
| 378 |
+
Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py
|
| 379 |
+
by D. Eppstein, July 2004.The modifications
|
| 380 |
+
to allow depth limits based on the Wikipedia article
|
| 381 |
+
"`Depth-limited-search`_".
|
| 382 |
+
|
| 383 |
+
.. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search
|
| 384 |
+
|
| 385 |
+
See Also
|
| 386 |
+
--------
|
| 387 |
+
bfs_tree
|
| 388 |
+
bfs_edges
|
| 389 |
+
edge_bfs
|
| 390 |
+
"""
|
| 391 |
+
parent = source
|
| 392 |
+
children = []
|
| 393 |
+
for p, c in bfs_edges(
|
| 394 |
+
G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
|
| 395 |
+
):
|
| 396 |
+
if p == parent:
|
| 397 |
+
children.append(c)
|
| 398 |
+
continue
|
| 399 |
+
yield (parent, children)
|
| 400 |
+
children = [c]
|
| 401 |
+
parent = p
|
| 402 |
+
yield (parent, children)
|
| 403 |
+
|
| 404 |
+
|
| 405 |
+
@nx._dispatchable
|
| 406 |
+
def bfs_layers(G, sources):
|
| 407 |
+
"""Returns an iterator of all the layers in breadth-first search traversal.
|
| 408 |
+
|
| 409 |
+
Parameters
|
| 410 |
+
----------
|
| 411 |
+
G : NetworkX graph
|
| 412 |
+
A graph over which to find the layers using breadth-first search.
|
| 413 |
+
|
| 414 |
+
sources : node in `G` or list of nodes in `G`
|
| 415 |
+
Specify starting nodes for single source or multiple sources breadth-first search
|
| 416 |
+
|
| 417 |
+
Yields
|
| 418 |
+
------
|
| 419 |
+
layer: list of nodes
|
| 420 |
+
Yields list of nodes at the same distance from sources
|
| 421 |
+
|
| 422 |
+
Examples
|
| 423 |
+
--------
|
| 424 |
+
>>> G = nx.path_graph(5)
|
| 425 |
+
>>> dict(enumerate(nx.bfs_layers(G, [0, 4])))
|
| 426 |
+
{0: [0, 4], 1: [1, 3], 2: [2]}
|
| 427 |
+
>>> H = nx.Graph()
|
| 428 |
+
>>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
|
| 429 |
+
>>> dict(enumerate(nx.bfs_layers(H, [1])))
|
| 430 |
+
{0: [1], 1: [0, 3, 4], 2: [2], 3: [5, 6]}
|
| 431 |
+
>>> dict(enumerate(nx.bfs_layers(H, [1, 6])))
|
| 432 |
+
{0: [1, 6], 1: [0, 3, 4, 2], 2: [5]}
|
| 433 |
+
"""
|
| 434 |
+
if sources in G:
|
| 435 |
+
sources = [sources]
|
| 436 |
+
|
| 437 |
+
current_layer = list(sources)
|
| 438 |
+
visited = set(sources)
|
| 439 |
+
|
| 440 |
+
for source in current_layer:
|
| 441 |
+
if source not in G:
|
| 442 |
+
raise nx.NetworkXError(f"The node {source} is not in the graph.")
|
| 443 |
+
|
| 444 |
+
# this is basically BFS, except that the current layer only stores the nodes at
|
| 445 |
+
# same distance from sources at each iteration
|
| 446 |
+
while current_layer:
|
| 447 |
+
yield current_layer
|
| 448 |
+
next_layer = []
|
| 449 |
+
for node in current_layer:
|
| 450 |
+
for child in G[node]:
|
| 451 |
+
if child not in visited:
|
| 452 |
+
visited.add(child)
|
| 453 |
+
next_layer.append(child)
|
| 454 |
+
current_layer = next_layer
|
| 455 |
+
|
| 456 |
+
|
| 457 |
+
REVERSE_EDGE = "reverse"
|
| 458 |
+
TREE_EDGE = "tree"
|
| 459 |
+
FORWARD_EDGE = "forward"
|
| 460 |
+
LEVEL_EDGE = "level"
|
| 461 |
+
|
| 462 |
+
|
| 463 |
+
@nx._dispatchable
|
| 464 |
+
def bfs_labeled_edges(G, sources):
|
| 465 |
+
"""Iterate over edges in a breadth-first search (BFS) labeled by type.
|
| 466 |
+
|
| 467 |
+
We generate triple of the form (*u*, *v*, *d*), where (*u*, *v*) is the
|
| 468 |
+
edge being explored in the breadth-first search and *d* is one of the
|
| 469 |
+
strings 'tree', 'forward', 'level', or 'reverse'. A 'tree' edge is one in
|
| 470 |
+
which *v* is first discovered and placed into the layer below *u*. A
|
| 471 |
+
'forward' edge is one in which *u* is on the layer above *v* and *v* has
|
| 472 |
+
already been discovered. A 'level' edge is one in which both *u* and *v*
|
| 473 |
+
occur on the same layer. A 'reverse' edge is one in which *u* is on a layer
|
| 474 |
+
below *v*.
|
| 475 |
+
|
| 476 |
+
We emit each edge exactly once. In an undirected graph, 'reverse' edges do
|
| 477 |
+
not occur, because each is discovered either as a 'tree' or 'forward' edge.
|
| 478 |
+
|
| 479 |
+
Parameters
|
| 480 |
+
----------
|
| 481 |
+
G : NetworkX graph
|
| 482 |
+
A graph over which to find the layers using breadth-first search.
|
| 483 |
+
|
| 484 |
+
sources : node in `G` or list of nodes in `G`
|
| 485 |
+
Starting nodes for single source or multiple sources breadth-first search
|
| 486 |
+
|
| 487 |
+
Yields
|
| 488 |
+
------
|
| 489 |
+
edges: generator
|
| 490 |
+
A generator of triples (*u*, *v*, *d*) where (*u*, *v*) is the edge being
|
| 491 |
+
explored and *d* is described above.
|
| 492 |
+
|
| 493 |
+
Examples
|
| 494 |
+
--------
|
| 495 |
+
>>> G = nx.cycle_graph(4, create_using=nx.DiGraph)
|
| 496 |
+
>>> list(nx.bfs_labeled_edges(G, 0))
|
| 497 |
+
[(0, 1, 'tree'), (1, 2, 'tree'), (2, 3, 'tree'), (3, 0, 'reverse')]
|
| 498 |
+
>>> G = nx.complete_graph(3)
|
| 499 |
+
>>> list(nx.bfs_labeled_edges(G, 0))
|
| 500 |
+
[(0, 1, 'tree'), (0, 2, 'tree'), (1, 2, 'level')]
|
| 501 |
+
>>> list(nx.bfs_labeled_edges(G, [0, 1]))
|
| 502 |
+
[(0, 1, 'level'), (0, 2, 'tree'), (1, 2, 'forward')]
|
| 503 |
+
"""
|
| 504 |
+
if sources in G:
|
| 505 |
+
sources = [sources]
|
| 506 |
+
|
| 507 |
+
neighbors = G._adj
|
| 508 |
+
directed = G.is_directed()
|
| 509 |
+
visited = set()
|
| 510 |
+
visit = visited.discard if directed else visited.add
|
| 511 |
+
# We use visited in a negative sense, so the visited set stays empty for the
|
| 512 |
+
# directed case and level edges are reported on their first occurrence in
|
| 513 |
+
# the undirected case. Note our use of visited.discard -- this is built-in
|
| 514 |
+
# thus somewhat faster than a python-defined def nop(x): pass
|
| 515 |
+
depth = {s: 0 for s in sources}
|
| 516 |
+
queue = deque(depth.items())
|
| 517 |
+
push = queue.append
|
| 518 |
+
pop = queue.popleft
|
| 519 |
+
while queue:
|
| 520 |
+
u, du = pop()
|
| 521 |
+
for v in neighbors[u]:
|
| 522 |
+
if v not in depth:
|
| 523 |
+
depth[v] = dv = du + 1
|
| 524 |
+
push((v, dv))
|
| 525 |
+
yield u, v, TREE_EDGE
|
| 526 |
+
else:
|
| 527 |
+
dv = depth[v]
|
| 528 |
+
if du == dv:
|
| 529 |
+
if v not in visited:
|
| 530 |
+
yield u, v, LEVEL_EDGE
|
| 531 |
+
elif du < dv:
|
| 532 |
+
yield u, v, FORWARD_EDGE
|
| 533 |
+
elif directed:
|
| 534 |
+
yield u, v, REVERSE_EDGE
|
| 535 |
+
visit(u)
|
| 536 |
+
|
| 537 |
+
|
| 538 |
+
@nx._dispatchable
|
| 539 |
+
def descendants_at_distance(G, source, distance):
|
| 540 |
+
"""Returns all nodes at a fixed `distance` from `source` in `G`.
|
| 541 |
+
|
| 542 |
+
Parameters
|
| 543 |
+
----------
|
| 544 |
+
G : NetworkX graph
|
| 545 |
+
A graph
|
| 546 |
+
source : node in `G`
|
| 547 |
+
distance : the distance of the wanted nodes from `source`
|
| 548 |
+
|
| 549 |
+
Returns
|
| 550 |
+
-------
|
| 551 |
+
set()
|
| 552 |
+
The descendants of `source` in `G` at the given `distance` from `source`
|
| 553 |
+
|
| 554 |
+
Examples
|
| 555 |
+
--------
|
| 556 |
+
>>> G = nx.path_graph(5)
|
| 557 |
+
>>> nx.descendants_at_distance(G, 2, 2)
|
| 558 |
+
{0, 4}
|
| 559 |
+
>>> H = nx.DiGraph()
|
| 560 |
+
>>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)])
|
| 561 |
+
>>> nx.descendants_at_distance(H, 0, 2)
|
| 562 |
+
{3, 4, 5, 6}
|
| 563 |
+
>>> nx.descendants_at_distance(H, 5, 0)
|
| 564 |
+
{5}
|
| 565 |
+
>>> nx.descendants_at_distance(H, 5, 1)
|
| 566 |
+
set()
|
| 567 |
+
"""
|
| 568 |
+
if source not in G:
|
| 569 |
+
raise nx.NetworkXError(f"The node {source} is not in the graph.")
|
| 570 |
+
|
| 571 |
+
bfs_generator = nx.bfs_layers(G, source)
|
| 572 |
+
for i, layer in enumerate(bfs_generator):
|
| 573 |
+
if i == distance:
|
| 574 |
+
return set(layer)
|
| 575 |
+
return set()
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/depth_first_search.py
ADDED
|
@@ -0,0 +1,529 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Basic algorithms for depth-first searching the nodes of a graph."""
|
| 2 |
+
|
| 3 |
+
from collections import defaultdict
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
|
| 7 |
+
__all__ = [
|
| 8 |
+
"dfs_edges",
|
| 9 |
+
"dfs_tree",
|
| 10 |
+
"dfs_predecessors",
|
| 11 |
+
"dfs_successors",
|
| 12 |
+
"dfs_preorder_nodes",
|
| 13 |
+
"dfs_postorder_nodes",
|
| 14 |
+
"dfs_labeled_edges",
|
| 15 |
+
]
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
@nx._dispatchable
|
| 19 |
+
def dfs_edges(G, source=None, depth_limit=None, *, sort_neighbors=None):
|
| 20 |
+
"""Iterate over edges in a depth-first-search (DFS).
|
| 21 |
+
|
| 22 |
+
Perform a depth-first-search over the nodes of `G` and yield
|
| 23 |
+
the edges in order. This may not generate all edges in `G`
|
| 24 |
+
(see `~networkx.algorithms.traversal.edgedfs.edge_dfs`).
|
| 25 |
+
|
| 26 |
+
Parameters
|
| 27 |
+
----------
|
| 28 |
+
G : NetworkX graph
|
| 29 |
+
|
| 30 |
+
source : node, optional
|
| 31 |
+
Specify starting node for depth-first search and yield edges in
|
| 32 |
+
the component reachable from source.
|
| 33 |
+
|
| 34 |
+
depth_limit : int, optional (default=len(G))
|
| 35 |
+
Specify the maximum search depth.
|
| 36 |
+
|
| 37 |
+
sort_neighbors : function (default=None)
|
| 38 |
+
A function that takes an iterator over nodes as the input, and
|
| 39 |
+
returns an iterable of the same nodes with a custom ordering.
|
| 40 |
+
For example, `sorted` will sort the nodes in increasing order.
|
| 41 |
+
|
| 42 |
+
Yields
|
| 43 |
+
------
|
| 44 |
+
edge: 2-tuple of nodes
|
| 45 |
+
Yields edges resulting from the depth-first-search.
|
| 46 |
+
|
| 47 |
+
Examples
|
| 48 |
+
--------
|
| 49 |
+
>>> G = nx.path_graph(5)
|
| 50 |
+
>>> list(nx.dfs_edges(G, source=0))
|
| 51 |
+
[(0, 1), (1, 2), (2, 3), (3, 4)]
|
| 52 |
+
>>> list(nx.dfs_edges(G, source=0, depth_limit=2))
|
| 53 |
+
[(0, 1), (1, 2)]
|
| 54 |
+
|
| 55 |
+
Notes
|
| 56 |
+
-----
|
| 57 |
+
If a source is not specified then a source is chosen arbitrarily and
|
| 58 |
+
repeatedly until all components in the graph are searched.
|
| 59 |
+
|
| 60 |
+
The implementation of this function is adapted from David Eppstein's
|
| 61 |
+
depth-first search function in PADS [1]_, with modifications
|
| 62 |
+
to allow depth limits based on the Wikipedia article
|
| 63 |
+
"Depth-limited search" [2]_.
|
| 64 |
+
|
| 65 |
+
See Also
|
| 66 |
+
--------
|
| 67 |
+
dfs_preorder_nodes
|
| 68 |
+
dfs_postorder_nodes
|
| 69 |
+
dfs_labeled_edges
|
| 70 |
+
:func:`~networkx.algorithms.traversal.edgedfs.edge_dfs`
|
| 71 |
+
:func:`~networkx.algorithms.traversal.breadth_first_search.bfs_edges`
|
| 72 |
+
|
| 73 |
+
References
|
| 74 |
+
----------
|
| 75 |
+
.. [1] http://www.ics.uci.edu/~eppstein/PADS
|
| 76 |
+
.. [2] https://en.wikipedia.org/wiki/Depth-limited_search
|
| 77 |
+
"""
|
| 78 |
+
if source is None:
|
| 79 |
+
# edges for all components
|
| 80 |
+
nodes = G
|
| 81 |
+
else:
|
| 82 |
+
# edges for components with source
|
| 83 |
+
nodes = [source]
|
| 84 |
+
if depth_limit is None:
|
| 85 |
+
depth_limit = len(G)
|
| 86 |
+
|
| 87 |
+
get_children = (
|
| 88 |
+
G.neighbors
|
| 89 |
+
if sort_neighbors is None
|
| 90 |
+
else lambda n: iter(sort_neighbors(G.neighbors(n)))
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
visited = set()
|
| 94 |
+
for start in nodes:
|
| 95 |
+
if start in visited:
|
| 96 |
+
continue
|
| 97 |
+
visited.add(start)
|
| 98 |
+
stack = [(start, get_children(start))]
|
| 99 |
+
depth_now = 1
|
| 100 |
+
while stack:
|
| 101 |
+
parent, children = stack[-1]
|
| 102 |
+
for child in children:
|
| 103 |
+
if child not in visited:
|
| 104 |
+
yield parent, child
|
| 105 |
+
visited.add(child)
|
| 106 |
+
if depth_now < depth_limit:
|
| 107 |
+
stack.append((child, get_children(child)))
|
| 108 |
+
depth_now += 1
|
| 109 |
+
break
|
| 110 |
+
else:
|
| 111 |
+
stack.pop()
|
| 112 |
+
depth_now -= 1
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
@nx._dispatchable(returns_graph=True)
|
| 116 |
+
def dfs_tree(G, source=None, depth_limit=None, *, sort_neighbors=None):
|
| 117 |
+
"""Returns oriented tree constructed from a depth-first-search from source.
|
| 118 |
+
|
| 119 |
+
Parameters
|
| 120 |
+
----------
|
| 121 |
+
G : NetworkX graph
|
| 122 |
+
|
| 123 |
+
source : node, optional
|
| 124 |
+
Specify starting node for depth-first search.
|
| 125 |
+
|
| 126 |
+
depth_limit : int, optional (default=len(G))
|
| 127 |
+
Specify the maximum search depth.
|
| 128 |
+
|
| 129 |
+
sort_neighbors : function (default=None)
|
| 130 |
+
A function that takes an iterator over nodes as the input, and
|
| 131 |
+
returns an iterable of the same nodes with a custom ordering.
|
| 132 |
+
For example, `sorted` will sort the nodes in increasing order.
|
| 133 |
+
|
| 134 |
+
Returns
|
| 135 |
+
-------
|
| 136 |
+
T : NetworkX DiGraph
|
| 137 |
+
An oriented tree
|
| 138 |
+
|
| 139 |
+
Examples
|
| 140 |
+
--------
|
| 141 |
+
>>> G = nx.path_graph(5)
|
| 142 |
+
>>> T = nx.dfs_tree(G, source=0, depth_limit=2)
|
| 143 |
+
>>> list(T.edges())
|
| 144 |
+
[(0, 1), (1, 2)]
|
| 145 |
+
>>> T = nx.dfs_tree(G, source=0)
|
| 146 |
+
>>> list(T.edges())
|
| 147 |
+
[(0, 1), (1, 2), (2, 3), (3, 4)]
|
| 148 |
+
|
| 149 |
+
See Also
|
| 150 |
+
--------
|
| 151 |
+
dfs_preorder_nodes
|
| 152 |
+
dfs_postorder_nodes
|
| 153 |
+
dfs_labeled_edges
|
| 154 |
+
:func:`~networkx.algorithms.traversal.edgedfs.edge_dfs`
|
| 155 |
+
:func:`~networkx.algorithms.traversal.breadth_first_search.bfs_tree`
|
| 156 |
+
"""
|
| 157 |
+
T = nx.DiGraph()
|
| 158 |
+
if source is None:
|
| 159 |
+
T.add_nodes_from(G)
|
| 160 |
+
else:
|
| 161 |
+
T.add_node(source)
|
| 162 |
+
T.add_edges_from(dfs_edges(G, source, depth_limit, sort_neighbors=sort_neighbors))
|
| 163 |
+
return T
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
@nx._dispatchable
|
| 167 |
+
def dfs_predecessors(G, source=None, depth_limit=None, *, sort_neighbors=None):
|
| 168 |
+
"""Returns dictionary of predecessors in depth-first-search from source.
|
| 169 |
+
|
| 170 |
+
Parameters
|
| 171 |
+
----------
|
| 172 |
+
G : NetworkX graph
|
| 173 |
+
|
| 174 |
+
source : node, optional
|
| 175 |
+
Specify starting node for depth-first search.
|
| 176 |
+
Note that you will get predecessors for all nodes in the
|
| 177 |
+
component containing `source`. This input only specifies
|
| 178 |
+
where the DFS starts.
|
| 179 |
+
|
| 180 |
+
depth_limit : int, optional (default=len(G))
|
| 181 |
+
Specify the maximum search depth.
|
| 182 |
+
|
| 183 |
+
sort_neighbors : function (default=None)
|
| 184 |
+
A function that takes an iterator over nodes as the input, and
|
| 185 |
+
returns an iterable of the same nodes with a custom ordering.
|
| 186 |
+
For example, `sorted` will sort the nodes in increasing order.
|
| 187 |
+
|
| 188 |
+
Returns
|
| 189 |
+
-------
|
| 190 |
+
pred: dict
|
| 191 |
+
A dictionary with nodes as keys and predecessor nodes as values.
|
| 192 |
+
|
| 193 |
+
Examples
|
| 194 |
+
--------
|
| 195 |
+
>>> G = nx.path_graph(4)
|
| 196 |
+
>>> nx.dfs_predecessors(G, source=0)
|
| 197 |
+
{1: 0, 2: 1, 3: 2}
|
| 198 |
+
>>> nx.dfs_predecessors(G, source=0, depth_limit=2)
|
| 199 |
+
{1: 0, 2: 1}
|
| 200 |
+
|
| 201 |
+
Notes
|
| 202 |
+
-----
|
| 203 |
+
If a source is not specified then a source is chosen arbitrarily and
|
| 204 |
+
repeatedly until all components in the graph are searched.
|
| 205 |
+
|
| 206 |
+
The implementation of this function is adapted from David Eppstein's
|
| 207 |
+
depth-first search function in `PADS`_, with modifications
|
| 208 |
+
to allow depth limits based on the Wikipedia article
|
| 209 |
+
"`Depth-limited search`_".
|
| 210 |
+
|
| 211 |
+
.. _PADS: http://www.ics.uci.edu/~eppstein/PADS
|
| 212 |
+
.. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search
|
| 213 |
+
|
| 214 |
+
See Also
|
| 215 |
+
--------
|
| 216 |
+
dfs_preorder_nodes
|
| 217 |
+
dfs_postorder_nodes
|
| 218 |
+
dfs_labeled_edges
|
| 219 |
+
:func:`~networkx.algorithms.traversal.edgedfs.edge_dfs`
|
| 220 |
+
:func:`~networkx.algorithms.traversal.breadth_first_search.bfs_tree`
|
| 221 |
+
"""
|
| 222 |
+
return {
|
| 223 |
+
t: s
|
| 224 |
+
for s, t in dfs_edges(G, source, depth_limit, sort_neighbors=sort_neighbors)
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
@nx._dispatchable
|
| 229 |
+
def dfs_successors(G, source=None, depth_limit=None, *, sort_neighbors=None):
|
| 230 |
+
"""Returns dictionary of successors in depth-first-search from source.
|
| 231 |
+
|
| 232 |
+
Parameters
|
| 233 |
+
----------
|
| 234 |
+
G : NetworkX graph
|
| 235 |
+
|
| 236 |
+
source : node, optional
|
| 237 |
+
Specify starting node for depth-first search.
|
| 238 |
+
Note that you will get successors for all nodes in the
|
| 239 |
+
component containing `source`. This input only specifies
|
| 240 |
+
where the DFS starts.
|
| 241 |
+
|
| 242 |
+
depth_limit : int, optional (default=len(G))
|
| 243 |
+
Specify the maximum search depth.
|
| 244 |
+
|
| 245 |
+
sort_neighbors : function (default=None)
|
| 246 |
+
A function that takes an iterator over nodes as the input, and
|
| 247 |
+
returns an iterable of the same nodes with a custom ordering.
|
| 248 |
+
For example, `sorted` will sort the nodes in increasing order.
|
| 249 |
+
|
| 250 |
+
Returns
|
| 251 |
+
-------
|
| 252 |
+
succ: dict
|
| 253 |
+
A dictionary with nodes as keys and list of successor nodes as values.
|
| 254 |
+
|
| 255 |
+
Examples
|
| 256 |
+
--------
|
| 257 |
+
>>> G = nx.path_graph(5)
|
| 258 |
+
>>> nx.dfs_successors(G, source=0)
|
| 259 |
+
{0: [1], 1: [2], 2: [3], 3: [4]}
|
| 260 |
+
>>> nx.dfs_successors(G, source=0, depth_limit=2)
|
| 261 |
+
{0: [1], 1: [2]}
|
| 262 |
+
|
| 263 |
+
Notes
|
| 264 |
+
-----
|
| 265 |
+
If a source is not specified then a source is chosen arbitrarily and
|
| 266 |
+
repeatedly until all components in the graph are searched.
|
| 267 |
+
|
| 268 |
+
The implementation of this function is adapted from David Eppstein's
|
| 269 |
+
depth-first search function in `PADS`_, with modifications
|
| 270 |
+
to allow depth limits based on the Wikipedia article
|
| 271 |
+
"`Depth-limited search`_".
|
| 272 |
+
|
| 273 |
+
.. _PADS: http://www.ics.uci.edu/~eppstein/PADS
|
| 274 |
+
.. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search
|
| 275 |
+
|
| 276 |
+
See Also
|
| 277 |
+
--------
|
| 278 |
+
dfs_preorder_nodes
|
| 279 |
+
dfs_postorder_nodes
|
| 280 |
+
dfs_labeled_edges
|
| 281 |
+
:func:`~networkx.algorithms.traversal.edgedfs.edge_dfs`
|
| 282 |
+
:func:`~networkx.algorithms.traversal.breadth_first_search.bfs_tree`
|
| 283 |
+
"""
|
| 284 |
+
d = defaultdict(list)
|
| 285 |
+
for s, t in dfs_edges(
|
| 286 |
+
G,
|
| 287 |
+
source=source,
|
| 288 |
+
depth_limit=depth_limit,
|
| 289 |
+
sort_neighbors=sort_neighbors,
|
| 290 |
+
):
|
| 291 |
+
d[s].append(t)
|
| 292 |
+
return dict(d)
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
@nx._dispatchable
|
| 296 |
+
def dfs_postorder_nodes(G, source=None, depth_limit=None, *, sort_neighbors=None):
|
| 297 |
+
"""Generate nodes in a depth-first-search post-ordering starting at source.
|
| 298 |
+
|
| 299 |
+
Parameters
|
| 300 |
+
----------
|
| 301 |
+
G : NetworkX graph
|
| 302 |
+
|
| 303 |
+
source : node, optional
|
| 304 |
+
Specify starting node for depth-first search.
|
| 305 |
+
|
| 306 |
+
depth_limit : int, optional (default=len(G))
|
| 307 |
+
Specify the maximum search depth.
|
| 308 |
+
|
| 309 |
+
sort_neighbors : function (default=None)
|
| 310 |
+
A function that takes an iterator over nodes as the input, and
|
| 311 |
+
returns an iterable of the same nodes with a custom ordering.
|
| 312 |
+
For example, `sorted` will sort the nodes in increasing order.
|
| 313 |
+
|
| 314 |
+
Returns
|
| 315 |
+
-------
|
| 316 |
+
nodes: generator
|
| 317 |
+
A generator of nodes in a depth-first-search post-ordering.
|
| 318 |
+
|
| 319 |
+
Examples
|
| 320 |
+
--------
|
| 321 |
+
>>> G = nx.path_graph(5)
|
| 322 |
+
>>> list(nx.dfs_postorder_nodes(G, source=0))
|
| 323 |
+
[4, 3, 2, 1, 0]
|
| 324 |
+
>>> list(nx.dfs_postorder_nodes(G, source=0, depth_limit=2))
|
| 325 |
+
[1, 0]
|
| 326 |
+
|
| 327 |
+
Notes
|
| 328 |
+
-----
|
| 329 |
+
If a source is not specified then a source is chosen arbitrarily and
|
| 330 |
+
repeatedly until all components in the graph are searched.
|
| 331 |
+
|
| 332 |
+
The implementation of this function is adapted from David Eppstein's
|
| 333 |
+
depth-first search function in `PADS`_, with modifications
|
| 334 |
+
to allow depth limits based on the Wikipedia article
|
| 335 |
+
"`Depth-limited search`_".
|
| 336 |
+
|
| 337 |
+
.. _PADS: http://www.ics.uci.edu/~eppstein/PADS
|
| 338 |
+
.. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search
|
| 339 |
+
|
| 340 |
+
See Also
|
| 341 |
+
--------
|
| 342 |
+
dfs_edges
|
| 343 |
+
dfs_preorder_nodes
|
| 344 |
+
dfs_labeled_edges
|
| 345 |
+
:func:`~networkx.algorithms.traversal.edgedfs.edge_dfs`
|
| 346 |
+
:func:`~networkx.algorithms.traversal.breadth_first_search.bfs_tree`
|
| 347 |
+
"""
|
| 348 |
+
edges = nx.dfs_labeled_edges(
|
| 349 |
+
G, source=source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
|
| 350 |
+
)
|
| 351 |
+
return (v for u, v, d in edges if d == "reverse")
|
| 352 |
+
|
| 353 |
+
|
| 354 |
+
@nx._dispatchable
|
| 355 |
+
def dfs_preorder_nodes(G, source=None, depth_limit=None, *, sort_neighbors=None):
|
| 356 |
+
"""Generate nodes in a depth-first-search pre-ordering starting at source.
|
| 357 |
+
|
| 358 |
+
Parameters
|
| 359 |
+
----------
|
| 360 |
+
G : NetworkX graph
|
| 361 |
+
|
| 362 |
+
source : node, optional
|
| 363 |
+
Specify starting node for depth-first search and return nodes in
|
| 364 |
+
the component reachable from source.
|
| 365 |
+
|
| 366 |
+
depth_limit : int, optional (default=len(G))
|
| 367 |
+
Specify the maximum search depth.
|
| 368 |
+
|
| 369 |
+
sort_neighbors : function (default=None)
|
| 370 |
+
A function that takes an iterator over nodes as the input, and
|
| 371 |
+
returns an iterable of the same nodes with a custom ordering.
|
| 372 |
+
For example, `sorted` will sort the nodes in increasing order.
|
| 373 |
+
|
| 374 |
+
Returns
|
| 375 |
+
-------
|
| 376 |
+
nodes: generator
|
| 377 |
+
A generator of nodes in a depth-first-search pre-ordering.
|
| 378 |
+
|
| 379 |
+
Examples
|
| 380 |
+
--------
|
| 381 |
+
>>> G = nx.path_graph(5)
|
| 382 |
+
>>> list(nx.dfs_preorder_nodes(G, source=0))
|
| 383 |
+
[0, 1, 2, 3, 4]
|
| 384 |
+
>>> list(nx.dfs_preorder_nodes(G, source=0, depth_limit=2))
|
| 385 |
+
[0, 1, 2]
|
| 386 |
+
|
| 387 |
+
Notes
|
| 388 |
+
-----
|
| 389 |
+
If a source is not specified then a source is chosen arbitrarily and
|
| 390 |
+
repeatedly until all components in the graph are searched.
|
| 391 |
+
|
| 392 |
+
The implementation of this function is adapted from David Eppstein's
|
| 393 |
+
depth-first search function in `PADS`_, with modifications
|
| 394 |
+
to allow depth limits based on the Wikipedia article
|
| 395 |
+
"`Depth-limited search`_".
|
| 396 |
+
|
| 397 |
+
.. _PADS: http://www.ics.uci.edu/~eppstein/PADS
|
| 398 |
+
.. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search
|
| 399 |
+
|
| 400 |
+
See Also
|
| 401 |
+
--------
|
| 402 |
+
dfs_edges
|
| 403 |
+
dfs_postorder_nodes
|
| 404 |
+
dfs_labeled_edges
|
| 405 |
+
:func:`~networkx.algorithms.traversal.breadth_first_search.bfs_edges`
|
| 406 |
+
"""
|
| 407 |
+
edges = nx.dfs_labeled_edges(
|
| 408 |
+
G, source=source, depth_limit=depth_limit, sort_neighbors=sort_neighbors
|
| 409 |
+
)
|
| 410 |
+
return (v for u, v, d in edges if d == "forward")
|
| 411 |
+
|
| 412 |
+
|
| 413 |
+
@nx._dispatchable
|
| 414 |
+
def dfs_labeled_edges(G, source=None, depth_limit=None, *, sort_neighbors=None):
|
| 415 |
+
"""Iterate over edges in a depth-first-search (DFS) labeled by type.
|
| 416 |
+
|
| 417 |
+
Parameters
|
| 418 |
+
----------
|
| 419 |
+
G : NetworkX graph
|
| 420 |
+
|
| 421 |
+
source : node, optional
|
| 422 |
+
Specify starting node for depth-first search and return edges in
|
| 423 |
+
the component reachable from source.
|
| 424 |
+
|
| 425 |
+
depth_limit : int, optional (default=len(G))
|
| 426 |
+
Specify the maximum search depth.
|
| 427 |
+
|
| 428 |
+
sort_neighbors : function (default=None)
|
| 429 |
+
A function that takes an iterator over nodes as the input, and
|
| 430 |
+
returns an iterable of the same nodes with a custom ordering.
|
| 431 |
+
For example, `sorted` will sort the nodes in increasing order.
|
| 432 |
+
|
| 433 |
+
Returns
|
| 434 |
+
-------
|
| 435 |
+
edges: generator
|
| 436 |
+
A generator of triples of the form (*u*, *v*, *d*), where (*u*,
|
| 437 |
+
*v*) is the edge being explored in the depth-first search and *d*
|
| 438 |
+
is one of the strings 'forward', 'nontree', 'reverse', or 'reverse-depth_limit'.
|
| 439 |
+
A 'forward' edge is one in which *u* has been visited but *v* has
|
| 440 |
+
not. A 'nontree' edge is one in which both *u* and *v* have been
|
| 441 |
+
visited but the edge is not in the DFS tree. A 'reverse' edge is
|
| 442 |
+
one in which both *u* and *v* have been visited and the edge is in
|
| 443 |
+
the DFS tree. When the `depth_limit` is reached via a 'forward' edge,
|
| 444 |
+
a 'reverse' edge is immediately generated rather than the subtree
|
| 445 |
+
being explored. To indicate this flavor of 'reverse' edge, the string
|
| 446 |
+
yielded is 'reverse-depth_limit'.
|
| 447 |
+
|
| 448 |
+
Examples
|
| 449 |
+
--------
|
| 450 |
+
|
| 451 |
+
The labels reveal the complete transcript of the depth-first search
|
| 452 |
+
algorithm in more detail than, for example, :func:`dfs_edges`::
|
| 453 |
+
|
| 454 |
+
>>> from pprint import pprint
|
| 455 |
+
>>>
|
| 456 |
+
>>> G = nx.DiGraph([(0, 1), (1, 2), (2, 1)])
|
| 457 |
+
>>> pprint(list(nx.dfs_labeled_edges(G, source=0)))
|
| 458 |
+
[(0, 0, 'forward'),
|
| 459 |
+
(0, 1, 'forward'),
|
| 460 |
+
(1, 2, 'forward'),
|
| 461 |
+
(2, 1, 'nontree'),
|
| 462 |
+
(1, 2, 'reverse'),
|
| 463 |
+
(0, 1, 'reverse'),
|
| 464 |
+
(0, 0, 'reverse')]
|
| 465 |
+
|
| 466 |
+
Notes
|
| 467 |
+
-----
|
| 468 |
+
If a source is not specified then a source is chosen arbitrarily and
|
| 469 |
+
repeatedly until all components in the graph are searched.
|
| 470 |
+
|
| 471 |
+
The implementation of this function is adapted from David Eppstein's
|
| 472 |
+
depth-first search function in `PADS`_, with modifications
|
| 473 |
+
to allow depth limits based on the Wikipedia article
|
| 474 |
+
"`Depth-limited search`_".
|
| 475 |
+
|
| 476 |
+
.. _PADS: http://www.ics.uci.edu/~eppstein/PADS
|
| 477 |
+
.. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search
|
| 478 |
+
|
| 479 |
+
See Also
|
| 480 |
+
--------
|
| 481 |
+
dfs_edges
|
| 482 |
+
dfs_preorder_nodes
|
| 483 |
+
dfs_postorder_nodes
|
| 484 |
+
"""
|
| 485 |
+
# Based on http://www.ics.uci.edu/~eppstein/PADS/DFS.py
|
| 486 |
+
# by D. Eppstein, July 2004.
|
| 487 |
+
if source is None:
|
| 488 |
+
# edges for all components
|
| 489 |
+
nodes = G
|
| 490 |
+
else:
|
| 491 |
+
# edges for components with source
|
| 492 |
+
nodes = [source]
|
| 493 |
+
if depth_limit is None:
|
| 494 |
+
depth_limit = len(G)
|
| 495 |
+
|
| 496 |
+
get_children = (
|
| 497 |
+
G.neighbors
|
| 498 |
+
if sort_neighbors is None
|
| 499 |
+
else lambda n: iter(sort_neighbors(G.neighbors(n)))
|
| 500 |
+
)
|
| 501 |
+
|
| 502 |
+
visited = set()
|
| 503 |
+
for start in nodes:
|
| 504 |
+
if start in visited:
|
| 505 |
+
continue
|
| 506 |
+
yield start, start, "forward"
|
| 507 |
+
visited.add(start)
|
| 508 |
+
stack = [(start, get_children(start))]
|
| 509 |
+
depth_now = 1
|
| 510 |
+
while stack:
|
| 511 |
+
parent, children = stack[-1]
|
| 512 |
+
for child in children:
|
| 513 |
+
if child in visited:
|
| 514 |
+
yield parent, child, "nontree"
|
| 515 |
+
else:
|
| 516 |
+
yield parent, child, "forward"
|
| 517 |
+
visited.add(child)
|
| 518 |
+
if depth_now < depth_limit:
|
| 519 |
+
stack.append((child, iter(get_children(child))))
|
| 520 |
+
depth_now += 1
|
| 521 |
+
break
|
| 522 |
+
else:
|
| 523 |
+
yield parent, child, "reverse-depth_limit"
|
| 524 |
+
else:
|
| 525 |
+
stack.pop()
|
| 526 |
+
depth_now -= 1
|
| 527 |
+
if stack:
|
| 528 |
+
yield stack[-1][0], parent, "reverse"
|
| 529 |
+
yield start, start, "reverse"
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/edgebfs.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
=============================
|
| 3 |
+
Breadth First Search on Edges
|
| 4 |
+
=============================
|
| 5 |
+
|
| 6 |
+
Algorithms for a breadth-first traversal of edges in a graph.
|
| 7 |
+
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from collections import deque
|
| 11 |
+
|
| 12 |
+
import networkx as nx
|
| 13 |
+
|
| 14 |
+
FORWARD = "forward"
|
| 15 |
+
REVERSE = "reverse"
|
| 16 |
+
|
| 17 |
+
__all__ = ["edge_bfs"]
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@nx._dispatchable
|
| 21 |
+
def edge_bfs(G, source=None, orientation=None):
|
| 22 |
+
"""A directed, breadth-first-search of edges in `G`, beginning at `source`.
|
| 23 |
+
|
| 24 |
+
Yield the edges of G in a breadth-first-search order continuing until
|
| 25 |
+
all edges are generated.
|
| 26 |
+
|
| 27 |
+
Parameters
|
| 28 |
+
----------
|
| 29 |
+
G : graph
|
| 30 |
+
A directed/undirected graph/multigraph.
|
| 31 |
+
|
| 32 |
+
source : node, list of nodes
|
| 33 |
+
The node from which the traversal begins. If None, then a source
|
| 34 |
+
is chosen arbitrarily and repeatedly until all edges from each node in
|
| 35 |
+
the graph are searched.
|
| 36 |
+
|
| 37 |
+
orientation : None | 'original' | 'reverse' | 'ignore' (default: None)
|
| 38 |
+
For directed graphs and directed multigraphs, edge traversals need not
|
| 39 |
+
respect the original orientation of the edges.
|
| 40 |
+
When set to 'reverse' every edge is traversed in the reverse direction.
|
| 41 |
+
When set to 'ignore', every edge is treated as undirected.
|
| 42 |
+
When set to 'original', every edge is treated as directed.
|
| 43 |
+
In all three cases, the yielded edge tuples add a last entry to
|
| 44 |
+
indicate the direction in which that edge was traversed.
|
| 45 |
+
If orientation is None, the yielded edge has no direction indicated.
|
| 46 |
+
The direction is respected, but not reported.
|
| 47 |
+
|
| 48 |
+
Yields
|
| 49 |
+
------
|
| 50 |
+
edge : directed edge
|
| 51 |
+
A directed edge indicating the path taken by the breadth-first-search.
|
| 52 |
+
For graphs, `edge` is of the form `(u, v)` where `u` and `v`
|
| 53 |
+
are the tail and head of the edge as determined by the traversal.
|
| 54 |
+
For multigraphs, `edge` is of the form `(u, v, key)`, where `key` is
|
| 55 |
+
the key of the edge. When the graph is directed, then `u` and `v`
|
| 56 |
+
are always in the order of the actual directed edge.
|
| 57 |
+
If orientation is not None then the edge tuple is extended to include
|
| 58 |
+
the direction of traversal ('forward' or 'reverse') on that edge.
|
| 59 |
+
|
| 60 |
+
Examples
|
| 61 |
+
--------
|
| 62 |
+
>>> nodes = [0, 1, 2, 3]
|
| 63 |
+
>>> edges = [(0, 1), (1, 0), (1, 0), (2, 0), (2, 1), (3, 1)]
|
| 64 |
+
|
| 65 |
+
>>> list(nx.edge_bfs(nx.Graph(edges), nodes))
|
| 66 |
+
[(0, 1), (0, 2), (1, 2), (1, 3)]
|
| 67 |
+
|
| 68 |
+
>>> list(nx.edge_bfs(nx.DiGraph(edges), nodes))
|
| 69 |
+
[(0, 1), (1, 0), (2, 0), (2, 1), (3, 1)]
|
| 70 |
+
|
| 71 |
+
>>> list(nx.edge_bfs(nx.MultiGraph(edges), nodes))
|
| 72 |
+
[(0, 1, 0), (0, 1, 1), (0, 1, 2), (0, 2, 0), (1, 2, 0), (1, 3, 0)]
|
| 73 |
+
|
| 74 |
+
>>> list(nx.edge_bfs(nx.MultiDiGraph(edges), nodes))
|
| 75 |
+
[(0, 1, 0), (1, 0, 0), (1, 0, 1), (2, 0, 0), (2, 1, 0), (3, 1, 0)]
|
| 76 |
+
|
| 77 |
+
>>> list(nx.edge_bfs(nx.DiGraph(edges), nodes, orientation="ignore"))
|
| 78 |
+
[(0, 1, 'forward'), (1, 0, 'reverse'), (2, 0, 'reverse'), (2, 1, 'reverse'), (3, 1, 'reverse')]
|
| 79 |
+
|
| 80 |
+
>>> list(nx.edge_bfs(nx.MultiDiGraph(edges), nodes, orientation="ignore"))
|
| 81 |
+
[(0, 1, 0, 'forward'), (1, 0, 0, 'reverse'), (1, 0, 1, 'reverse'), (2, 0, 0, 'reverse'), (2, 1, 0, 'reverse'), (3, 1, 0, 'reverse')]
|
| 82 |
+
|
| 83 |
+
Notes
|
| 84 |
+
-----
|
| 85 |
+
The goal of this function is to visit edges. It differs from the more
|
| 86 |
+
familiar breadth-first-search of nodes, as provided by
|
| 87 |
+
:func:`networkx.algorithms.traversal.breadth_first_search.bfs_edges`, in
|
| 88 |
+
that it does not stop once every node has been visited. In a directed graph
|
| 89 |
+
with edges [(0, 1), (1, 2), (2, 1)], the edge (2, 1) would not be visited
|
| 90 |
+
if not for the functionality provided by this function.
|
| 91 |
+
|
| 92 |
+
The naming of this function is very similar to bfs_edges. The difference
|
| 93 |
+
is that 'edge_bfs' yields edges even if they extend back to an already
|
| 94 |
+
explored node while 'bfs_edges' yields the edges of the tree that results
|
| 95 |
+
from a breadth-first-search (BFS) so no edges are reported if they extend
|
| 96 |
+
to already explored nodes. That means 'edge_bfs' reports all edges while
|
| 97 |
+
'bfs_edges' only report those traversed by a node-based BFS. Yet another
|
| 98 |
+
description is that 'bfs_edges' reports the edges traversed during BFS
|
| 99 |
+
while 'edge_bfs' reports all edges in the order they are explored.
|
| 100 |
+
|
| 101 |
+
See Also
|
| 102 |
+
--------
|
| 103 |
+
bfs_edges
|
| 104 |
+
bfs_tree
|
| 105 |
+
edge_dfs
|
| 106 |
+
|
| 107 |
+
"""
|
| 108 |
+
nodes = list(G.nbunch_iter(source))
|
| 109 |
+
if not nodes:
|
| 110 |
+
return
|
| 111 |
+
|
| 112 |
+
directed = G.is_directed()
|
| 113 |
+
kwds = {"data": False}
|
| 114 |
+
if G.is_multigraph() is True:
|
| 115 |
+
kwds["keys"] = True
|
| 116 |
+
|
| 117 |
+
# set up edge lookup
|
| 118 |
+
if orientation is None:
|
| 119 |
+
|
| 120 |
+
def edges_from(node):
|
| 121 |
+
return iter(G.edges(node, **kwds))
|
| 122 |
+
|
| 123 |
+
elif not directed or orientation == "original":
|
| 124 |
+
|
| 125 |
+
def edges_from(node):
|
| 126 |
+
for e in G.edges(node, **kwds):
|
| 127 |
+
yield e + (FORWARD,)
|
| 128 |
+
|
| 129 |
+
elif orientation == "reverse":
|
| 130 |
+
|
| 131 |
+
def edges_from(node):
|
| 132 |
+
for e in G.in_edges(node, **kwds):
|
| 133 |
+
yield e + (REVERSE,)
|
| 134 |
+
|
| 135 |
+
elif orientation == "ignore":
|
| 136 |
+
|
| 137 |
+
def edges_from(node):
|
| 138 |
+
for e in G.edges(node, **kwds):
|
| 139 |
+
yield e + (FORWARD,)
|
| 140 |
+
for e in G.in_edges(node, **kwds):
|
| 141 |
+
yield e + (REVERSE,)
|
| 142 |
+
|
| 143 |
+
else:
|
| 144 |
+
raise nx.NetworkXError("invalid orientation argument.")
|
| 145 |
+
|
| 146 |
+
if directed:
|
| 147 |
+
neighbors = G.successors
|
| 148 |
+
|
| 149 |
+
def edge_id(edge):
|
| 150 |
+
# remove direction indicator
|
| 151 |
+
return edge[:-1] if orientation is not None else edge
|
| 152 |
+
|
| 153 |
+
else:
|
| 154 |
+
neighbors = G.neighbors
|
| 155 |
+
|
| 156 |
+
def edge_id(edge):
|
| 157 |
+
return (frozenset(edge[:2]),) + edge[2:]
|
| 158 |
+
|
| 159 |
+
check_reverse = directed and orientation in ("reverse", "ignore")
|
| 160 |
+
|
| 161 |
+
# start BFS
|
| 162 |
+
visited_nodes = set(nodes)
|
| 163 |
+
visited_edges = set()
|
| 164 |
+
queue = deque([(n, edges_from(n)) for n in nodes])
|
| 165 |
+
while queue:
|
| 166 |
+
parent, children_edges = queue.popleft()
|
| 167 |
+
for edge in children_edges:
|
| 168 |
+
if check_reverse and edge[-1] == REVERSE:
|
| 169 |
+
child = edge[0]
|
| 170 |
+
else:
|
| 171 |
+
child = edge[1]
|
| 172 |
+
if child not in visited_nodes:
|
| 173 |
+
visited_nodes.add(child)
|
| 174 |
+
queue.append((child, edges_from(child)))
|
| 175 |
+
edgeid = edge_id(edge)
|
| 176 |
+
if edgeid not in visited_edges:
|
| 177 |
+
visited_edges.add(edgeid)
|
| 178 |
+
yield edge
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/edgedfs.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
===========================
|
| 3 |
+
Depth First Search on Edges
|
| 4 |
+
===========================
|
| 5 |
+
|
| 6 |
+
Algorithms for a depth-first traversal of edges in a graph.
|
| 7 |
+
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import networkx as nx
|
| 11 |
+
|
| 12 |
+
FORWARD = "forward"
|
| 13 |
+
REVERSE = "reverse"
|
| 14 |
+
|
| 15 |
+
__all__ = ["edge_dfs"]
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
@nx._dispatchable
|
| 19 |
+
def edge_dfs(G, source=None, orientation=None):
|
| 20 |
+
"""A directed, depth-first-search of edges in `G`, beginning at `source`.
|
| 21 |
+
|
| 22 |
+
Yield the edges of G in a depth-first-search order continuing until
|
| 23 |
+
all edges are generated.
|
| 24 |
+
|
| 25 |
+
Parameters
|
| 26 |
+
----------
|
| 27 |
+
G : graph
|
| 28 |
+
A directed/undirected graph/multigraph.
|
| 29 |
+
|
| 30 |
+
source : node, list of nodes
|
| 31 |
+
The node from which the traversal begins. If None, then a source
|
| 32 |
+
is chosen arbitrarily and repeatedly until all edges from each node in
|
| 33 |
+
the graph are searched.
|
| 34 |
+
|
| 35 |
+
orientation : None | 'original' | 'reverse' | 'ignore' (default: None)
|
| 36 |
+
For directed graphs and directed multigraphs, edge traversals need not
|
| 37 |
+
respect the original orientation of the edges.
|
| 38 |
+
When set to 'reverse' every edge is traversed in the reverse direction.
|
| 39 |
+
When set to 'ignore', every edge is treated as undirected.
|
| 40 |
+
When set to 'original', every edge is treated as directed.
|
| 41 |
+
In all three cases, the yielded edge tuples add a last entry to
|
| 42 |
+
indicate the direction in which that edge was traversed.
|
| 43 |
+
If orientation is None, the yielded edge has no direction indicated.
|
| 44 |
+
The direction is respected, but not reported.
|
| 45 |
+
|
| 46 |
+
Yields
|
| 47 |
+
------
|
| 48 |
+
edge : directed edge
|
| 49 |
+
A directed edge indicating the path taken by the depth-first traversal.
|
| 50 |
+
For graphs, `edge` is of the form `(u, v)` where `u` and `v`
|
| 51 |
+
are the tail and head of the edge as determined by the traversal.
|
| 52 |
+
For multigraphs, `edge` is of the form `(u, v, key)`, where `key` is
|
| 53 |
+
the key of the edge. When the graph is directed, then `u` and `v`
|
| 54 |
+
are always in the order of the actual directed edge.
|
| 55 |
+
If orientation is not None then the edge tuple is extended to include
|
| 56 |
+
the direction of traversal ('forward' or 'reverse') on that edge.
|
| 57 |
+
|
| 58 |
+
Examples
|
| 59 |
+
--------
|
| 60 |
+
>>> nodes = [0, 1, 2, 3]
|
| 61 |
+
>>> edges = [(0, 1), (1, 0), (1, 0), (2, 1), (3, 1)]
|
| 62 |
+
|
| 63 |
+
>>> list(nx.edge_dfs(nx.Graph(edges), nodes))
|
| 64 |
+
[(0, 1), (1, 2), (1, 3)]
|
| 65 |
+
|
| 66 |
+
>>> list(nx.edge_dfs(nx.DiGraph(edges), nodes))
|
| 67 |
+
[(0, 1), (1, 0), (2, 1), (3, 1)]
|
| 68 |
+
|
| 69 |
+
>>> list(nx.edge_dfs(nx.MultiGraph(edges), nodes))
|
| 70 |
+
[(0, 1, 0), (1, 0, 1), (0, 1, 2), (1, 2, 0), (1, 3, 0)]
|
| 71 |
+
|
| 72 |
+
>>> list(nx.edge_dfs(nx.MultiDiGraph(edges), nodes))
|
| 73 |
+
[(0, 1, 0), (1, 0, 0), (1, 0, 1), (2, 1, 0), (3, 1, 0)]
|
| 74 |
+
|
| 75 |
+
>>> list(nx.edge_dfs(nx.DiGraph(edges), nodes, orientation="ignore"))
|
| 76 |
+
[(0, 1, 'forward'), (1, 0, 'forward'), (2, 1, 'reverse'), (3, 1, 'reverse')]
|
| 77 |
+
|
| 78 |
+
>>> list(nx.edge_dfs(nx.MultiDiGraph(edges), nodes, orientation="ignore"))
|
| 79 |
+
[(0, 1, 0, 'forward'), (1, 0, 0, 'forward'), (1, 0, 1, 'reverse'), (2, 1, 0, 'reverse'), (3, 1, 0, 'reverse')]
|
| 80 |
+
|
| 81 |
+
Notes
|
| 82 |
+
-----
|
| 83 |
+
The goal of this function is to visit edges. It differs from the more
|
| 84 |
+
familiar depth-first traversal of nodes, as provided by
|
| 85 |
+
:func:`~networkx.algorithms.traversal.depth_first_search.dfs_edges`, in
|
| 86 |
+
that it does not stop once every node has been visited. In a directed graph
|
| 87 |
+
with edges [(0, 1), (1, 2), (2, 1)], the edge (2, 1) would not be visited
|
| 88 |
+
if not for the functionality provided by this function.
|
| 89 |
+
|
| 90 |
+
See Also
|
| 91 |
+
--------
|
| 92 |
+
:func:`~networkx.algorithms.traversal.depth_first_search.dfs_edges`
|
| 93 |
+
|
| 94 |
+
"""
|
| 95 |
+
nodes = list(G.nbunch_iter(source))
|
| 96 |
+
if not nodes:
|
| 97 |
+
return
|
| 98 |
+
|
| 99 |
+
directed = G.is_directed()
|
| 100 |
+
kwds = {"data": False}
|
| 101 |
+
if G.is_multigraph() is True:
|
| 102 |
+
kwds["keys"] = True
|
| 103 |
+
|
| 104 |
+
# set up edge lookup
|
| 105 |
+
if orientation is None:
|
| 106 |
+
|
| 107 |
+
def edges_from(node):
|
| 108 |
+
return iter(G.edges(node, **kwds))
|
| 109 |
+
|
| 110 |
+
elif not directed or orientation == "original":
|
| 111 |
+
|
| 112 |
+
def edges_from(node):
|
| 113 |
+
for e in G.edges(node, **kwds):
|
| 114 |
+
yield e + (FORWARD,)
|
| 115 |
+
|
| 116 |
+
elif orientation == "reverse":
|
| 117 |
+
|
| 118 |
+
def edges_from(node):
|
| 119 |
+
for e in G.in_edges(node, **kwds):
|
| 120 |
+
yield e + (REVERSE,)
|
| 121 |
+
|
| 122 |
+
elif orientation == "ignore":
|
| 123 |
+
|
| 124 |
+
def edges_from(node):
|
| 125 |
+
for e in G.edges(node, **kwds):
|
| 126 |
+
yield e + (FORWARD,)
|
| 127 |
+
for e in G.in_edges(node, **kwds):
|
| 128 |
+
yield e + (REVERSE,)
|
| 129 |
+
|
| 130 |
+
else:
|
| 131 |
+
raise nx.NetworkXError("invalid orientation argument.")
|
| 132 |
+
|
| 133 |
+
# set up formation of edge_id to easily look up if edge already returned
|
| 134 |
+
if directed:
|
| 135 |
+
|
| 136 |
+
def edge_id(edge):
|
| 137 |
+
# remove direction indicator
|
| 138 |
+
return edge[:-1] if orientation is not None else edge
|
| 139 |
+
|
| 140 |
+
else:
|
| 141 |
+
|
| 142 |
+
def edge_id(edge):
|
| 143 |
+
# single id for undirected requires frozenset on nodes
|
| 144 |
+
return (frozenset(edge[:2]),) + edge[2:]
|
| 145 |
+
|
| 146 |
+
# Basic setup
|
| 147 |
+
check_reverse = directed and orientation in ("reverse", "ignore")
|
| 148 |
+
|
| 149 |
+
visited_edges = set()
|
| 150 |
+
visited_nodes = set()
|
| 151 |
+
edges = {}
|
| 152 |
+
|
| 153 |
+
# start DFS
|
| 154 |
+
for start_node in nodes:
|
| 155 |
+
stack = [start_node]
|
| 156 |
+
while stack:
|
| 157 |
+
current_node = stack[-1]
|
| 158 |
+
if current_node not in visited_nodes:
|
| 159 |
+
edges[current_node] = edges_from(current_node)
|
| 160 |
+
visited_nodes.add(current_node)
|
| 161 |
+
|
| 162 |
+
try:
|
| 163 |
+
edge = next(edges[current_node])
|
| 164 |
+
except StopIteration:
|
| 165 |
+
# No more edges from the current node.
|
| 166 |
+
stack.pop()
|
| 167 |
+
else:
|
| 168 |
+
edgeid = edge_id(edge)
|
| 169 |
+
if edgeid not in visited_edges:
|
| 170 |
+
visited_edges.add(edgeid)
|
| 171 |
+
# Mark the traversed "to" node as to-be-explored.
|
| 172 |
+
if check_reverse and edge[-1] == REVERSE:
|
| 173 |
+
stack.append(edge[0])
|
| 174 |
+
else:
|
| 175 |
+
stack.append(edge[1])
|
| 176 |
+
yield edge
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__init__.py
ADDED
|
File without changes
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (208 Bytes). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_beamsearch.cpython-311.pyc
ADDED
|
Binary file (1.64 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_bfs.cpython-311.pyc
ADDED
|
Binary file (14.2 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_dfs.cpython-311.pyc
ADDED
|
Binary file (17.4 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_edgebfs.cpython-311.pyc
ADDED
|
Binary file (9.32 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_edgedfs.cpython-311.pyc
ADDED
|
Binary file (8.46 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/test_beamsearch.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Unit tests for the beam search functions."""
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def test_narrow():
|
| 9 |
+
"""Tests that a narrow beam width may cause an incomplete search."""
|
| 10 |
+
# In this search, we enqueue only the neighbor 3 at the first
|
| 11 |
+
# step, then only the neighbor 2 at the second step. Once at
|
| 12 |
+
# node 2, the search chooses node 3, since it has a higher value
|
| 13 |
+
# than node 1, but node 3 has already been visited, so the
|
| 14 |
+
# search terminates.
|
| 15 |
+
G = nx.cycle_graph(4)
|
| 16 |
+
edges = nx.bfs_beam_edges(G, source=0, value=lambda n: n, width=1)
|
| 17 |
+
assert list(edges) == [(0, 3), (3, 2)]
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@pytest.mark.parametrize("width", (2, None))
|
| 21 |
+
def test_wide(width):
|
| 22 |
+
"""All nodes are searched when `width` is None or >= max degree"""
|
| 23 |
+
G = nx.cycle_graph(4)
|
| 24 |
+
edges = nx.bfs_beam_edges(G, source=0, value=lambda n: n, width=width)
|
| 25 |
+
assert list(edges) == [(0, 3), (0, 1), (3, 2)]
|
.venv/lib/python3.11/site-packages/networkx/algorithms/traversal/tests/test_bfs.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from functools import partial
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class TestBFS:
|
| 9 |
+
@classmethod
|
| 10 |
+
def setup_class(cls):
|
| 11 |
+
# simple graph
|
| 12 |
+
G = nx.Graph()
|
| 13 |
+
G.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4)])
|
| 14 |
+
cls.G = G
|
| 15 |
+
|
| 16 |
+
def test_successor(self):
|
| 17 |
+
assert dict(nx.bfs_successors(self.G, source=0)) == {0: [1], 1: [2, 3], 2: [4]}
|
| 18 |
+
|
| 19 |
+
def test_predecessor(self):
|
| 20 |
+
assert dict(nx.bfs_predecessors(self.G, source=0)) == {1: 0, 2: 1, 3: 1, 4: 2}
|
| 21 |
+
|
| 22 |
+
def test_bfs_tree(self):
|
| 23 |
+
T = nx.bfs_tree(self.G, source=0)
|
| 24 |
+
assert sorted(T.nodes()) == sorted(self.G.nodes())
|
| 25 |
+
assert sorted(T.edges()) == [(0, 1), (1, 2), (1, 3), (2, 4)]
|
| 26 |
+
|
| 27 |
+
def test_bfs_edges(self):
|
| 28 |
+
edges = nx.bfs_edges(self.G, source=0)
|
| 29 |
+
assert list(edges) == [(0, 1), (1, 2), (1, 3), (2, 4)]
|
| 30 |
+
|
| 31 |
+
def test_bfs_edges_reverse(self):
|
| 32 |
+
D = nx.DiGraph()
|
| 33 |
+
D.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4)])
|
| 34 |
+
edges = nx.bfs_edges(D, source=4, reverse=True)
|
| 35 |
+
assert list(edges) == [(4, 2), (4, 3), (2, 1), (1, 0)]
|
| 36 |
+
|
| 37 |
+
def test_bfs_edges_sorting(self):
|
| 38 |
+
D = nx.DiGraph()
|
| 39 |
+
D.add_edges_from([(0, 1), (0, 2), (1, 4), (1, 3), (2, 5)])
|
| 40 |
+
sort_desc = partial(sorted, reverse=True)
|
| 41 |
+
edges_asc = nx.bfs_edges(D, source=0, sort_neighbors=sorted)
|
| 42 |
+
edges_desc = nx.bfs_edges(D, source=0, sort_neighbors=sort_desc)
|
| 43 |
+
assert list(edges_asc) == [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5)]
|
| 44 |
+
assert list(edges_desc) == [(0, 2), (0, 1), (2, 5), (1, 4), (1, 3)]
|
| 45 |
+
|
| 46 |
+
def test_bfs_tree_isolates(self):
|
| 47 |
+
G = nx.Graph()
|
| 48 |
+
G.add_node(1)
|
| 49 |
+
G.add_node(2)
|
| 50 |
+
T = nx.bfs_tree(G, source=1)
|
| 51 |
+
assert sorted(T.nodes()) == [1]
|
| 52 |
+
assert sorted(T.edges()) == []
|
| 53 |
+
|
| 54 |
+
def test_bfs_layers(self):
|
| 55 |
+
expected = {
|
| 56 |
+
0: [0],
|
| 57 |
+
1: [1],
|
| 58 |
+
2: [2, 3],
|
| 59 |
+
3: [4],
|
| 60 |
+
}
|
| 61 |
+
assert dict(enumerate(nx.bfs_layers(self.G, sources=[0]))) == expected
|
| 62 |
+
assert dict(enumerate(nx.bfs_layers(self.G, sources=0))) == expected
|
| 63 |
+
|
| 64 |
+
def test_bfs_layers_missing_source(self):
|
| 65 |
+
with pytest.raises(nx.NetworkXError):
|
| 66 |
+
next(nx.bfs_layers(self.G, sources="abc"))
|
| 67 |
+
with pytest.raises(nx.NetworkXError):
|
| 68 |
+
next(nx.bfs_layers(self.G, sources=["abc"]))
|
| 69 |
+
|
| 70 |
+
def test_descendants_at_distance(self):
|
| 71 |
+
for distance, descendants in enumerate([{0}, {1}, {2, 3}, {4}]):
|
| 72 |
+
assert nx.descendants_at_distance(self.G, 0, distance) == descendants
|
| 73 |
+
|
| 74 |
+
def test_descendants_at_distance_missing_source(self):
|
| 75 |
+
with pytest.raises(nx.NetworkXError):
|
| 76 |
+
nx.descendants_at_distance(self.G, "abc", 0)
|
| 77 |
+
|
| 78 |
+
def test_bfs_labeled_edges_directed(self):
|
| 79 |
+
D = nx.cycle_graph(5, create_using=nx.DiGraph)
|
| 80 |
+
expected = [
|
| 81 |
+
(0, 1, "tree"),
|
| 82 |
+
(1, 2, "tree"),
|
| 83 |
+
(2, 3, "tree"),
|
| 84 |
+
(3, 4, "tree"),
|
| 85 |
+
(4, 0, "reverse"),
|
| 86 |
+
]
|
| 87 |
+
answer = list(nx.bfs_labeled_edges(D, 0))
|
| 88 |
+
assert expected == answer
|
| 89 |
+
|
| 90 |
+
D.add_edge(4, 4)
|
| 91 |
+
expected.append((4, 4, "level"))
|
| 92 |
+
answer = list(nx.bfs_labeled_edges(D, 0))
|
| 93 |
+
assert expected == answer
|
| 94 |
+
|
| 95 |
+
D.add_edge(0, 2)
|
| 96 |
+
D.add_edge(1, 5)
|
| 97 |
+
D.add_edge(2, 5)
|
| 98 |
+
D.remove_edge(4, 4)
|
| 99 |
+
expected = [
|
| 100 |
+
(0, 1, "tree"),
|
| 101 |
+
(0, 2, "tree"),
|
| 102 |
+
(1, 2, "level"),
|
| 103 |
+
(1, 5, "tree"),
|
| 104 |
+
(2, 3, "tree"),
|
| 105 |
+
(2, 5, "forward"),
|
| 106 |
+
(3, 4, "tree"),
|
| 107 |
+
(4, 0, "reverse"),
|
| 108 |
+
]
|
| 109 |
+
answer = list(nx.bfs_labeled_edges(D, 0))
|
| 110 |
+
assert expected == answer
|
| 111 |
+
|
| 112 |
+
G = D.to_undirected()
|
| 113 |
+
G.add_edge(4, 4)
|
| 114 |
+
expected = [
|
| 115 |
+
(0, 1, "tree"),
|
| 116 |
+
(0, 2, "tree"),
|
| 117 |
+
(0, 4, "tree"),
|
| 118 |
+
(1, 2, "level"),
|
| 119 |
+
(1, 5, "tree"),
|
| 120 |
+
(2, 3, "tree"),
|
| 121 |
+
(2, 5, "forward"),
|
| 122 |
+
(4, 3, "forward"),
|
| 123 |
+
(4, 4, "level"),
|
| 124 |
+
]
|
| 125 |
+
answer = list(nx.bfs_labeled_edges(G, 0))
|
| 126 |
+
assert expected == answer
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
class TestBreadthLimitedSearch:
|
| 130 |
+
@classmethod
|
| 131 |
+
def setup_class(cls):
|
| 132 |
+
# a tree
|
| 133 |
+
G = nx.Graph()
|
| 134 |
+
nx.add_path(G, [0, 1, 2, 3, 4, 5, 6])
|
| 135 |
+
nx.add_path(G, [2, 7, 8, 9, 10])
|
| 136 |
+
cls.G = G
|
| 137 |
+
# a disconnected graph
|
| 138 |
+
D = nx.Graph()
|
| 139 |
+
D.add_edges_from([(0, 1), (2, 3)])
|
| 140 |
+
nx.add_path(D, [2, 7, 8, 9, 10])
|
| 141 |
+
cls.D = D
|
| 142 |
+
|
| 143 |
+
def test_limited_bfs_successor(self):
|
| 144 |
+
assert dict(nx.bfs_successors(self.G, source=1, depth_limit=3)) == {
|
| 145 |
+
1: [0, 2],
|
| 146 |
+
2: [3, 7],
|
| 147 |
+
3: [4],
|
| 148 |
+
7: [8],
|
| 149 |
+
}
|
| 150 |
+
result = {
|
| 151 |
+
n: sorted(s) for n, s in nx.bfs_successors(self.D, source=7, depth_limit=2)
|
| 152 |
+
}
|
| 153 |
+
assert result == {8: [9], 2: [3], 7: [2, 8]}
|
| 154 |
+
|
| 155 |
+
def test_limited_bfs_predecessor(self):
|
| 156 |
+
assert dict(nx.bfs_predecessors(self.G, source=1, depth_limit=3)) == {
|
| 157 |
+
0: 1,
|
| 158 |
+
2: 1,
|
| 159 |
+
3: 2,
|
| 160 |
+
4: 3,
|
| 161 |
+
7: 2,
|
| 162 |
+
8: 7,
|
| 163 |
+
}
|
| 164 |
+
assert dict(nx.bfs_predecessors(self.D, source=7, depth_limit=2)) == {
|
| 165 |
+
2: 7,
|
| 166 |
+
3: 2,
|
| 167 |
+
8: 7,
|
| 168 |
+
9: 8,
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
def test_limited_bfs_tree(self):
|
| 172 |
+
T = nx.bfs_tree(self.G, source=3, depth_limit=1)
|
| 173 |
+
assert sorted(T.edges()) == [(3, 2), (3, 4)]
|
| 174 |
+
|
| 175 |
+
def test_limited_bfs_edges(self):
|
| 176 |
+
edges = nx.bfs_edges(self.G, source=9, depth_limit=4)
|
| 177 |
+
assert list(edges) == [(9, 8), (9, 10), (8, 7), (7, 2), (2, 1), (2, 3)]
|
| 178 |
+
|
| 179 |
+
def test_limited_bfs_layers(self):
|
| 180 |
+
assert dict(enumerate(nx.bfs_layers(self.G, sources=[0]))) == {
|
| 181 |
+
0: [0],
|
| 182 |
+
1: [1],
|
| 183 |
+
2: [2],
|
| 184 |
+
3: [3, 7],
|
| 185 |
+
4: [4, 8],
|
| 186 |
+
5: [5, 9],
|
| 187 |
+
6: [6, 10],
|
| 188 |
+
}
|
| 189 |
+
assert dict(enumerate(nx.bfs_layers(self.D, sources=2))) == {
|
| 190 |
+
0: [2],
|
| 191 |
+
1: [3, 7],
|
| 192 |
+
2: [8],
|
| 193 |
+
3: [9],
|
| 194 |
+
4: [10],
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
def test_limited_descendants_at_distance(self):
|
| 198 |
+
for distance, descendants in enumerate(
|
| 199 |
+
[{0}, {1}, {2}, {3, 7}, {4, 8}, {5, 9}, {6, 10}]
|
| 200 |
+
):
|
| 201 |
+
assert nx.descendants_at_distance(self.G, 0, distance) == descendants
|
| 202 |
+
for distance, descendants in enumerate([{2}, {3, 7}, {8}, {9}, {10}]):
|
| 203 |
+
assert nx.descendants_at_distance(self.D, 2, distance) == descendants
|