anonymous-CAP commited on
Commit
1e321d6
·
verified ·
1 Parent(s): 39dbaed

Upload folder using huggingface_hub (part 8)

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +7 -0
  2. venv/lib/python3.12/site-packages/networkx-3.6.1.dist-info/INSTALLER +1 -0
  3. venv/lib/python3.12/site-packages/networkx-3.6.1.dist-info/METADATA +177 -0
  4. venv/lib/python3.12/site-packages/networkx-3.6.1.dist-info/RECORD +0 -0
  5. venv/lib/python3.12/site-packages/networkx-3.6.1.dist-info/WHEEL +5 -0
  6. venv/lib/python3.12/site-packages/networkx-3.6.1.dist-info/entry_points.txt +2 -0
  7. venv/lib/python3.12/site-packages/networkx-3.6.1.dist-info/licenses/LICENSE.txt +37 -0
  8. venv/lib/python3.12/site-packages/networkx-3.6.1.dist-info/top_level.txt +1 -0
  9. venv/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_triads.cpython-312.pyc +0 -0
  10. venv/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_vitality.cpython-312.pyc +0 -0
  11. venv/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_voronoi.cpython-312.pyc +0 -0
  12. venv/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_walks.cpython-312.pyc +0 -0
  13. venv/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_wiener.cpython-312.pyc +0 -0
  14. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_asteroidal.py +23 -0
  15. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_boundary.py +154 -0
  16. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_bridges.py +144 -0
  17. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_broadcasting.py +109 -0
  18. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_chains.py +136 -0
  19. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_chordal.py +129 -0
  20. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_clique.py +300 -0
  21. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_cluster.py +678 -0
  22. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_communicability.py +80 -0
  23. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_core.py +266 -0
  24. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_covering.py +85 -0
  25. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_cuts.py +171 -0
  26. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_cycles.py +984 -0
  27. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_d_separation.py +340 -0
  28. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_dag.py +835 -0
  29. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_distance_measures.py +831 -0
  30. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_distance_regular.py +85 -0
  31. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_dominance.py +299 -0
  32. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_dominating.py +115 -0
  33. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_efficiency.py +58 -0
  34. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_euler.py +314 -0
  35. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_graph_hashing.py +872 -0
  36. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_graphical.py +163 -0
  37. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_hierarchy.py +46 -0
  38. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_hybrid.py +24 -0
  39. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_isolate.py +26 -0
  40. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_link_prediction.py +615 -0
  41. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_lowest_common_ancestors.py +459 -0
  42. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_matching.py +556 -0
  43. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_max_weight_clique.py +179 -0
  44. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_mis.py +62 -0
  45. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_moral.py +15 -0
  46. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_node_classification.py +140 -0
  47. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_non_randomness.py +60 -0
  48. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_perfect_graph.py +27 -0
  49. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_planar_drawing.py +274 -0
  50. venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_planarity.py +556 -0
.gitattributes CHANGED
@@ -67,3 +67,10 @@ venv/lib/python3.12/site-packages/joblib/test/__pycache__/test_parallel.cpython-
67
  venv/lib/python3.12/site-packages/mpmath/__pycache__/function_docs.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
68
  venv/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_fp.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
69
  venv/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_functions2.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
67
  venv/lib/python3.12/site-packages/mpmath/__pycache__/function_docs.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
68
  venv/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_fp.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
69
  venv/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_functions2.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
70
+ venv/lib/python3.12/site-packages/networkx/drawing/__pycache__/nx_pylab.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
71
+ venv/lib/python3.12/site-packages/networkx/drawing/tests/baseline/test_display_complex.png filter=lfs diff=lfs merge=lfs -text
72
+ venv/lib/python3.12/site-packages/numpy/_core/__pycache__/_add_newdocs.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
73
+ venv/lib/python3.12/site-packages/numpy/_core/__pycache__/fromnumeric.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
74
+ venv/lib/python3.12/site-packages/numpy/_core/_multiarray_tests.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
75
+ venv/lib/python3.12/site-packages/numpy/_core/_multiarray_umath.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
76
+ venv/lib/python3.12/site-packages/numpy/_core/_simd.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
venv/lib/python3.12/site-packages/networkx-3.6.1.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
venv/lib/python3.12/site-packages/networkx-3.6.1.dist-info/METADATA ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: networkx
3
+ Version: 3.6.1
4
+ Summary: Python package for creating and manipulating graphs and networks
5
+ Author-email: Aric Hagberg <hagberg@lanl.gov>
6
+ Maintainer-email: NetworkX Developers <networkx-discuss@googlegroups.com>
7
+ License-Expression: BSD-3-Clause
8
+ Project-URL: Homepage, https://networkx.org/
9
+ Project-URL: Bug Tracker, https://github.com/networkx/networkx/issues
10
+ Project-URL: Documentation, https://networkx.org/documentation/stable/
11
+ Project-URL: Source Code, https://github.com/networkx/networkx
12
+ Keywords: Networks,Graph Theory,Mathematics,network,graph,discrete mathematics,math
13
+ Platform: Linux
14
+ Platform: Mac OSX
15
+ Platform: Windows
16
+ Platform: Unix
17
+ Classifier: Development Status :: 5 - Production/Stable
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: Intended Audience :: Science/Research
20
+ Classifier: Operating System :: OS Independent
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
26
+ Classifier: Programming Language :: Python :: 3 :: Only
27
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
29
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
30
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
31
+ Classifier: Topic :: Scientific/Engineering :: Physics
32
+ Requires-Python: !=3.14.1,>=3.11
33
+ Description-Content-Type: text/x-rst
34
+ License-File: LICENSE.txt
35
+ Provides-Extra: benchmarking
36
+ Requires-Dist: asv; extra == "benchmarking"
37
+ Requires-Dist: virtualenv; extra == "benchmarking"
38
+ Provides-Extra: default
39
+ Requires-Dist: numpy>=1.25; extra == "default"
40
+ Requires-Dist: scipy>=1.11.2; extra == "default"
41
+ Requires-Dist: matplotlib>=3.8; extra == "default"
42
+ Requires-Dist: pandas>=2.0; extra == "default"
43
+ Provides-Extra: developer
44
+ Requires-Dist: pre-commit>=4.1; extra == "developer"
45
+ Requires-Dist: mypy>=1.15; extra == "developer"
46
+ Provides-Extra: doc
47
+ Requires-Dist: sphinx>=8.0; extra == "doc"
48
+ Requires-Dist: pydata-sphinx-theme>=0.16; extra == "doc"
49
+ Requires-Dist: sphinx-gallery>=0.18; extra == "doc"
50
+ Requires-Dist: numpydoc>=1.8.0; extra == "doc"
51
+ Requires-Dist: pillow>=10; extra == "doc"
52
+ Requires-Dist: texext>=0.6.7; extra == "doc"
53
+ Requires-Dist: myst-nb>=1.1; extra == "doc"
54
+ Requires-Dist: intersphinx-registry; extra == "doc"
55
+ Provides-Extra: example
56
+ Requires-Dist: osmnx>=2.0.0; extra == "example"
57
+ Requires-Dist: momepy>=0.7.2; extra == "example"
58
+ Requires-Dist: contextily>=1.6; extra == "example"
59
+ Requires-Dist: seaborn>=0.13; extra == "example"
60
+ Requires-Dist: cairocffi>=1.7; extra == "example"
61
+ Requires-Dist: igraph>=0.11; extra == "example"
62
+ Requires-Dist: scikit-learn>=1.5; extra == "example"
63
+ Requires-Dist: iplotx>=0.9.0; extra == "example"
64
+ Provides-Extra: extra
65
+ Requires-Dist: lxml>=4.6; extra == "extra"
66
+ Requires-Dist: pygraphviz>=1.14; extra == "extra"
67
+ Requires-Dist: pydot>=3.0.1; extra == "extra"
68
+ Requires-Dist: sympy>=1.10; extra == "extra"
69
+ Provides-Extra: release
70
+ Requires-Dist: build>=0.10; extra == "release"
71
+ Requires-Dist: twine>=4.0; extra == "release"
72
+ Requires-Dist: wheel>=0.40; extra == "release"
73
+ Requires-Dist: changelist==0.5; extra == "release"
74
+ Provides-Extra: test
75
+ Requires-Dist: pytest>=7.2; extra == "test"
76
+ Requires-Dist: pytest-cov>=4.0; extra == "test"
77
+ Requires-Dist: pytest-xdist>=3.0; extra == "test"
78
+ Provides-Extra: test-extras
79
+ Requires-Dist: pytest-mpl; extra == "test-extras"
80
+ Requires-Dist: pytest-randomly; extra == "test-extras"
81
+ Dynamic: license-file
82
+
83
+ NetworkX
84
+ ========
85
+
86
+
87
+ .. image::
88
+ https://github.com/networkx/networkx/actions/workflows/test.yml/badge.svg?branch=main
89
+ :target: https://github.com/networkx/networkx/actions/workflows/test.yml
90
+
91
+ .. image::
92
+ https://img.shields.io/pypi/v/networkx.svg?
93
+ :target: https://pypi.python.org/pypi/networkx
94
+
95
+ .. image::
96
+ https://img.shields.io/pypi/l/networkx.svg?
97
+ :target: https://github.com/networkx/networkx/blob/main/LICENSE.txt
98
+
99
+ .. image::
100
+ https://img.shields.io/pypi/pyversions/networkx.svg?
101
+ :target: https://pypi.python.org/pypi/networkx
102
+
103
+ .. image::
104
+ https://img.shields.io/github/labels/networkx/networkx/good%20first%20issue?color=green&label=contribute
105
+ :target: https://github.com/networkx/networkx/contribute
106
+
107
+ .. image::
108
+ https://insights.linuxfoundation.org/api/badge/health-score?project=networkx
109
+ :target: https://insights.linuxfoundation.org/project/networkx
110
+
111
+
112
+ NetworkX is a Python package for the creation, manipulation,
113
+ and study of the structure, dynamics, and functions
114
+ of complex networks.
115
+
116
+ - **Website (including documentation):** https://networkx.org
117
+ - **Mailing list:** https://groups.google.com/forum/#!forum/networkx-discuss
118
+ - **Source:** https://github.com/networkx/networkx
119
+ - **Bug reports:** https://github.com/networkx/networkx/issues
120
+ - **Report a security vulnerability:** https://tidelift.com/security
121
+ - **Tutorial:** https://networkx.org/documentation/latest/tutorial.html
122
+ - **GitHub Discussions:** https://github.com/networkx/networkx/discussions
123
+ - **Discord (Scientific Python) invite link:** https://discord.com/invite/vur45CbwMz
124
+ - **NetworkX meetings calendar (open to all):** https://scientific-python.org/calendars/networkx.ics
125
+
126
+ Simple example
127
+ --------------
128
+
129
+ Find the shortest path between two nodes in an undirected graph:
130
+
131
+ .. code:: pycon
132
+
133
+ >>> import networkx as nx
134
+ >>> G = nx.Graph()
135
+ >>> G.add_edge("A", "B", weight=4)
136
+ >>> G.add_edge("B", "D", weight=2)
137
+ >>> G.add_edge("A", "C", weight=3)
138
+ >>> G.add_edge("C", "D", weight=4)
139
+ >>> nx.shortest_path(G, "A", "D", weight="weight")
140
+ ['A', 'B', 'D']
141
+
142
+ Install
143
+ -------
144
+
145
+ Install the latest released version of NetworkX:
146
+
147
+ .. code:: shell
148
+
149
+ $ pip install networkx
150
+
151
+ Install with all optional dependencies:
152
+
153
+ .. code:: shell
154
+
155
+ $ pip install networkx[default]
156
+
157
+ For additional details,
158
+ please see the `installation guide <https://networkx.org/documentation/stable/install.html>`_.
159
+
160
+ Bugs
161
+ ----
162
+
163
+ Please report any bugs that you find `here <https://github.com/networkx/networkx/issues>`_.
164
+ Or, even better, fork the repository on `GitHub <https://github.com/networkx/networkx>`_
165
+ and create a pull request (PR). We welcome all changes, big or small, and we
166
+ will help you make the PR if you are new to `git` (just ask on the issue and/or
167
+ see the `contributor guide <https://networkx.org/documentation/latest/developer/contribute.html>`_).
168
+
169
+ License
170
+ -------
171
+
172
+ Released under the `3-clause BSD license <https://github.com/networkx/networkx/blob/main/LICENSE.txt>`_::
173
+
174
+ Copyright (c) 2004-2025, NetworkX Developers
175
+ Aric Hagberg <hagberg@lanl.gov>
176
+ Dan Schult <dschult@colgate.edu>
177
+ Pieter Swart <swart@lanl.gov>
venv/lib/python3.12/site-packages/networkx-3.6.1.dist-info/RECORD ADDED
The diff for this file is too large to render. See raw diff
 
venv/lib/python3.12/site-packages/networkx-3.6.1.dist-info/WHEEL ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
venv/lib/python3.12/site-packages/networkx-3.6.1.dist-info/entry_points.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [networkx.backends]
2
+ nx_loopback = networkx.classes.tests.dispatch_interface:backend_interface
venv/lib/python3.12/site-packages/networkx-3.6.1.dist-info/licenses/LICENSE.txt ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ NetworkX is distributed with the 3-clause BSD license.
2
+
3
+ ::
4
+
5
+ Copyright (c) 2004-2025, NetworkX Developers
6
+ Aric Hagberg <hagberg@lanl.gov>
7
+ Dan Schult <dschult@colgate.edu>
8
+ Pieter Swart <swart@lanl.gov>
9
+ All rights reserved.
10
+
11
+ Redistribution and use in source and binary forms, with or without
12
+ modification, are permitted provided that the following conditions are
13
+ met:
14
+
15
+ * Redistributions of source code must retain the above copyright
16
+ notice, this list of conditions and the following disclaimer.
17
+
18
+ * Redistributions in binary form must reproduce the above
19
+ copyright notice, this list of conditions and the following
20
+ disclaimer in the documentation and/or other materials provided
21
+ with the distribution.
22
+
23
+ * Neither the name of the NetworkX Developers nor the names of its
24
+ contributors may be used to endorse or promote products derived
25
+ from this software without specific prior written permission.
26
+
27
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
venv/lib/python3.12/site-packages/networkx-3.6.1.dist-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ networkx
venv/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_triads.cpython-312.pyc ADDED
Binary file (17.3 kB). View file
 
venv/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_vitality.cpython-312.pyc ADDED
Binary file (3.11 kB). View file
 
venv/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_voronoi.cpython-312.pyc ADDED
Binary file (5.93 kB). View file
 
venv/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_walks.cpython-312.pyc ADDED
Binary file (2.8 kB). View file
 
venv/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_wiener.cpython-312.pyc ADDED
Binary file (6.78 kB). View file
 
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_asteroidal.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import networkx as nx
2
+
3
+
4
+ def test_is_at_free():
5
+ is_at_free = nx.asteroidal.is_at_free
6
+
7
+ cycle = nx.cycle_graph(6)
8
+ assert not is_at_free(cycle)
9
+
10
+ path = nx.path_graph(6)
11
+ assert is_at_free(path)
12
+
13
+ small_graph = nx.complete_graph(2)
14
+ assert is_at_free(small_graph)
15
+
16
+ petersen = nx.petersen_graph()
17
+ assert not is_at_free(petersen)
18
+
19
+ clique = nx.complete_graph(6)
20
+ assert is_at_free(clique)
21
+
22
+ line_clique = nx.line_graph(clique)
23
+ assert not is_at_free(line_clique)
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_boundary.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Unit tests for the :mod:`networkx.algorithms.boundary` module."""
2
+
3
+ from itertools import combinations
4
+
5
+ import pytest
6
+
7
+ import networkx as nx
8
+ from networkx import convert_node_labels_to_integers as cnlti
9
+ from networkx.utils import edges_equal
10
+
11
+
12
+ class TestNodeBoundary:
13
+ """Unit tests for the :func:`~networkx.node_boundary` function."""
14
+
15
+ def test_null_graph(self):
16
+ """Tests that the null graph has empty node boundaries."""
17
+ null = nx.null_graph()
18
+ assert nx.node_boundary(null, []) == set()
19
+ assert nx.node_boundary(null, [], []) == set()
20
+ assert nx.node_boundary(null, [1, 2, 3]) == set()
21
+ assert nx.node_boundary(null, [1, 2, 3], [4, 5, 6]) == set()
22
+ assert nx.node_boundary(null, [1, 2, 3], [3, 4, 5]) == set()
23
+
24
+ def test_path_graph(self):
25
+ P10 = cnlti(nx.path_graph(10), first_label=1)
26
+ assert nx.node_boundary(P10, []) == set()
27
+ assert nx.node_boundary(P10, [], []) == set()
28
+ assert nx.node_boundary(P10, [1, 2, 3]) == {4}
29
+ assert nx.node_boundary(P10, [4, 5, 6]) == {3, 7}
30
+ assert nx.node_boundary(P10, [3, 4, 5, 6, 7]) == {2, 8}
31
+ assert nx.node_boundary(P10, [8, 9, 10]) == {7}
32
+ assert nx.node_boundary(P10, [4, 5, 6], [9, 10]) == set()
33
+
34
+ def test_complete_graph(self):
35
+ K10 = cnlti(nx.complete_graph(10), first_label=1)
36
+ assert nx.node_boundary(K10, []) == set()
37
+ assert nx.node_boundary(K10, [], []) == set()
38
+ assert nx.node_boundary(K10, [1, 2, 3]) == {4, 5, 6, 7, 8, 9, 10}
39
+ assert nx.node_boundary(K10, [4, 5, 6]) == {1, 2, 3, 7, 8, 9, 10}
40
+ assert nx.node_boundary(K10, [3, 4, 5, 6, 7]) == {1, 2, 8, 9, 10}
41
+ assert nx.node_boundary(K10, [4, 5, 6], []) == set()
42
+ assert nx.node_boundary(K10, K10) == set()
43
+ assert nx.node_boundary(K10, [1, 2, 3], [3, 4, 5]) == {4, 5}
44
+
45
+ def test_petersen(self):
46
+ """Check boundaries in the petersen graph
47
+
48
+ cheeger(G,k)=min(|bdy(S)|/|S| for |S|=k, 0<k<=|V(G)|/2)
49
+
50
+ """
51
+
52
+ def cheeger(G, k):
53
+ return min(len(nx.node_boundary(G, nn)) / k for nn in combinations(G, k))
54
+
55
+ P = nx.petersen_graph()
56
+ assert cheeger(P, 1) == pytest.approx(3.00, abs=1e-2)
57
+ assert cheeger(P, 2) == pytest.approx(2.00, abs=1e-2)
58
+ assert cheeger(P, 3) == pytest.approx(1.67, abs=1e-2)
59
+ assert cheeger(P, 4) == pytest.approx(1.00, abs=1e-2)
60
+ assert cheeger(P, 5) == pytest.approx(0.80, abs=1e-2)
61
+
62
+ def test_directed(self):
63
+ """Tests the node boundary of a directed graph."""
64
+ G = nx.DiGraph([(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)])
65
+ S = {0, 1}
66
+ boundary = nx.node_boundary(G, S)
67
+ expected = {2}
68
+ assert boundary == expected
69
+
70
+ def test_multigraph(self):
71
+ """Tests the node boundary of a multigraph."""
72
+ G = nx.MultiGraph(list(nx.cycle_graph(5).edges()) * 2)
73
+ S = {0, 1}
74
+ boundary = nx.node_boundary(G, S)
75
+ expected = {2, 4}
76
+ assert boundary == expected
77
+
78
+ def test_multidigraph(self):
79
+ """Tests the edge boundary of a multidigraph."""
80
+ edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]
81
+ G = nx.MultiDiGraph(edges * 2)
82
+ S = {0, 1}
83
+ boundary = nx.node_boundary(G, S)
84
+ expected = {2}
85
+ assert boundary == expected
86
+
87
+
88
+ class TestEdgeBoundary:
89
+ """Unit tests for the :func:`~networkx.edge_boundary` function."""
90
+
91
+ def test_null_graph(self):
92
+ null = nx.null_graph()
93
+ assert list(nx.edge_boundary(null, [])) == []
94
+ assert list(nx.edge_boundary(null, [], [])) == []
95
+ assert list(nx.edge_boundary(null, [1, 2, 3])) == []
96
+ assert list(nx.edge_boundary(null, [1, 2, 3], [4, 5, 6])) == []
97
+ assert list(nx.edge_boundary(null, [1, 2, 3], [3, 4, 5])) == []
98
+
99
+ def test_path_graph(self):
100
+ P10 = cnlti(nx.path_graph(10), first_label=1)
101
+ assert list(nx.edge_boundary(P10, [])) == []
102
+ assert list(nx.edge_boundary(P10, [], [])) == []
103
+ assert list(nx.edge_boundary(P10, [1, 2, 3])) == [(3, 4)]
104
+ assert sorted(nx.edge_boundary(P10, [4, 5, 6])) == [(4, 3), (6, 7)]
105
+ assert sorted(nx.edge_boundary(P10, [3, 4, 5, 6, 7])) == [(3, 2), (7, 8)]
106
+ assert list(nx.edge_boundary(P10, [8, 9, 10])) == [(8, 7)]
107
+ assert sorted(nx.edge_boundary(P10, [4, 5, 6], [9, 10])) == []
108
+ assert list(nx.edge_boundary(P10, [1, 2, 3], [3, 4, 5])) == [(2, 3), (3, 4)]
109
+
110
+ def test_complete_graph(self):
111
+ K10 = cnlti(nx.complete_graph(10), first_label=1)
112
+
113
+ def ilen(iterable):
114
+ return sum(1 for i in iterable)
115
+
116
+ assert list(nx.edge_boundary(K10, [])) == []
117
+ assert list(nx.edge_boundary(K10, [], [])) == []
118
+ assert ilen(nx.edge_boundary(K10, [1, 2, 3])) == 21
119
+ assert ilen(nx.edge_boundary(K10, [4, 5, 6, 7])) == 24
120
+ assert ilen(nx.edge_boundary(K10, [3, 4, 5, 6, 7])) == 25
121
+ assert ilen(nx.edge_boundary(K10, [8, 9, 10])) == 21
122
+ assert edges_equal(
123
+ nx.edge_boundary(K10, [4, 5, 6], [9, 10]),
124
+ [(4, 9), (4, 10), (5, 9), (5, 10), (6, 9), (6, 10)],
125
+ )
126
+ assert edges_equal(
127
+ nx.edge_boundary(K10, [1, 2, 3], [3, 4, 5]),
128
+ [(1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5)],
129
+ )
130
+
131
+ def test_directed(self):
132
+ """Tests the edge boundary of a directed graph."""
133
+ G = nx.DiGraph([(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)])
134
+ S = {0, 1}
135
+ boundary = list(nx.edge_boundary(G, S))
136
+ expected = [(1, 2)]
137
+ assert boundary == expected
138
+
139
+ def test_multigraph(self):
140
+ """Tests the edge boundary of a multigraph."""
141
+ G = nx.MultiGraph(list(nx.cycle_graph(5).edges()) * 2)
142
+ S = {0, 1}
143
+ boundary = list(nx.edge_boundary(G, S))
144
+ expected = [(0, 4), (0, 4), (1, 2), (1, 2)]
145
+ assert boundary == expected
146
+
147
+ def test_multidigraph(self):
148
+ """Tests the edge boundary of a multidigraph."""
149
+ edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]
150
+ G = nx.MultiDiGraph(edges * 2)
151
+ S = {0, 1}
152
+ boundary = list(nx.edge_boundary(G, S))
153
+ expected = [(1, 2), (1, 2)]
154
+ assert boundary == expected
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_bridges.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Unit tests for bridge-finding algorithms."""
2
+
3
+ import pytest
4
+
5
+ import networkx as nx
6
+
7
+
8
+ class TestBridges:
9
+ """Unit tests for the bridge-finding function."""
10
+
11
+ def test_single_bridge(self):
12
+ edges = [
13
+ # DFS tree edges.
14
+ (1, 2),
15
+ (2, 3),
16
+ (3, 4),
17
+ (3, 5),
18
+ (5, 6),
19
+ (6, 7),
20
+ (7, 8),
21
+ (5, 9),
22
+ (9, 10),
23
+ # Nontree edges.
24
+ (1, 3),
25
+ (1, 4),
26
+ (2, 5),
27
+ (5, 10),
28
+ (6, 8),
29
+ ]
30
+ G = nx.Graph(edges)
31
+ source = 1
32
+ bridges = list(nx.bridges(G, source))
33
+ assert bridges == [(5, 6)]
34
+
35
+ def test_barbell_graph(self):
36
+ # The (3, 0) barbell graph has two triangles joined by a single edge.
37
+ G = nx.barbell_graph(3, 0)
38
+ source = 0
39
+ bridges = list(nx.bridges(G, source))
40
+ assert bridges == [(2, 3)]
41
+
42
+ def test_multiedge_bridge(self):
43
+ edges = [
44
+ (0, 1),
45
+ (0, 2),
46
+ (1, 2),
47
+ (1, 2),
48
+ (2, 3),
49
+ (3, 4),
50
+ (3, 4),
51
+ ]
52
+ G = nx.MultiGraph(edges)
53
+ assert list(nx.bridges(G)) == [(2, 3)]
54
+
55
+
56
+ class TestHasBridges:
57
+ """Unit tests for the has bridges function."""
58
+
59
+ def test_single_bridge(self):
60
+ edges = [
61
+ # DFS tree edges.
62
+ (1, 2),
63
+ (2, 3),
64
+ (3, 4),
65
+ (3, 5),
66
+ (5, 6), # The only bridge edge
67
+ (6, 7),
68
+ (7, 8),
69
+ (5, 9),
70
+ (9, 10),
71
+ # Nontree edges.
72
+ (1, 3),
73
+ (1, 4),
74
+ (2, 5),
75
+ (5, 10),
76
+ (6, 8),
77
+ ]
78
+ G = nx.Graph(edges)
79
+ assert nx.has_bridges(G) # Default root
80
+ assert nx.has_bridges(G, root=1) # arbitrary root in G
81
+
82
+ def test_has_bridges_raises_root_not_in_G(self):
83
+ G = nx.Graph()
84
+ G.add_nodes_from([1, 2, 3])
85
+ with pytest.raises(nx.NodeNotFound):
86
+ nx.has_bridges(G, root=6)
87
+
88
+ def test_multiedge_bridge(self):
89
+ edges = [
90
+ (0, 1),
91
+ (0, 2),
92
+ (1, 2),
93
+ (1, 2),
94
+ (2, 3),
95
+ (3, 4),
96
+ (3, 4),
97
+ ]
98
+ G = nx.MultiGraph(edges)
99
+ assert nx.has_bridges(G)
100
+ # Make every edge a multiedge
101
+ G.add_edges_from([(0, 1), (0, 2), (2, 3)])
102
+ assert not nx.has_bridges(G)
103
+
104
+ def test_bridges_multiple_components(self):
105
+ G = nx.Graph()
106
+ nx.add_path(G, [0, 1, 2]) # One connected component
107
+ nx.add_path(G, [4, 5, 6]) # Another connected component
108
+ assert list(nx.bridges(G, root=4)) == [(4, 5), (5, 6)]
109
+
110
+
111
+ class TestLocalBridges:
112
+ """Unit tests for the local_bridge function."""
113
+
114
+ @classmethod
115
+ def setup_class(cls):
116
+ cls.BB = nx.barbell_graph(4, 0)
117
+ cls.square = nx.cycle_graph(4)
118
+ cls.tri = nx.cycle_graph(3)
119
+
120
+ def test_nospan(self):
121
+ expected = {(3, 4), (4, 3)}
122
+ assert next(nx.local_bridges(self.BB, with_span=False)) in expected
123
+ assert set(nx.local_bridges(self.square, with_span=False)) == self.square.edges
124
+ assert list(nx.local_bridges(self.tri, with_span=False)) == []
125
+
126
+ def test_no_weight(self):
127
+ inf = float("inf")
128
+ expected = {(3, 4, inf), (4, 3, inf)}
129
+ assert next(nx.local_bridges(self.BB)) in expected
130
+ expected = {(u, v, 3) for u, v in self.square.edges}
131
+ assert set(nx.local_bridges(self.square)) == expected
132
+ assert list(nx.local_bridges(self.tri)) == []
133
+
134
+ def test_weight(self):
135
+ inf = float("inf")
136
+ G = self.square.copy()
137
+
138
+ G.edges[1, 2]["weight"] = 2
139
+ expected = {(u, v, 5 - wt) for u, v, wt in G.edges(data="weight", default=1)}
140
+ assert set(nx.local_bridges(G, weight="weight")) == expected
141
+
142
+ expected = {(u, v, 6) for u, v in G.edges}
143
+ lb = nx.local_bridges(G, weight=lambda u, v, d: 2)
144
+ assert set(lb) == expected
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_broadcasting.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Unit tests for the broadcasting module."""
2
+
3
+ import math
4
+
5
+ import pytest
6
+
7
+ import networkx as nx
8
+
9
+
10
+ def test_example_tree_broadcast():
11
+ """
12
+ Test the BROADCAST algorithm on the example in the paper titled: "Information Dissemination in Trees"
13
+ """
14
+ edge_list = [
15
+ (0, 1),
16
+ (1, 2),
17
+ (2, 7),
18
+ (3, 4),
19
+ (5, 4),
20
+ (4, 7),
21
+ (6, 7),
22
+ (7, 9),
23
+ (8, 9),
24
+ (9, 13),
25
+ (13, 14),
26
+ (14, 15),
27
+ (14, 16),
28
+ (14, 17),
29
+ (13, 11),
30
+ (11, 10),
31
+ (11, 12),
32
+ (13, 18),
33
+ (18, 19),
34
+ (18, 20),
35
+ ]
36
+ G = nx.Graph(edge_list)
37
+ b_T, b_C = nx.tree_broadcast_center(G)
38
+ assert b_T == 6
39
+ assert b_C == {13, 9}
40
+ # test broadcast time from specific vertex
41
+ assert nx.tree_broadcast_time(G, 17) == 8
42
+ assert nx.tree_broadcast_time(G, 3) == 9
43
+ # test broadcast time of entire tree
44
+ assert nx.tree_broadcast_time(G) == 10
45
+
46
+
47
+ @pytest.mark.parametrize("n", range(2, 12))
48
+ def test_path_broadcast(n):
49
+ G = nx.path_graph(n)
50
+ b_T, b_C = nx.tree_broadcast_center(G)
51
+ assert b_T == math.ceil(n / 2)
52
+ assert b_C == {
53
+ math.ceil(n / 2),
54
+ n // 2,
55
+ math.ceil(n / 2 - 1),
56
+ n // 2 - 1,
57
+ }
58
+ assert nx.tree_broadcast_time(G) == n - 1
59
+
60
+
61
+ def test_empty_graph_broadcast():
62
+ H = nx.empty_graph(1)
63
+ b_T, b_C = nx.tree_broadcast_center(H)
64
+ assert b_T == 0
65
+ assert b_C == {0}
66
+ assert nx.tree_broadcast_time(H) == 0
67
+
68
+
69
+ @pytest.mark.parametrize("n", range(4, 12))
70
+ def test_star_broadcast(n):
71
+ G = nx.star_graph(n)
72
+ b_T, b_C = nx.tree_broadcast_center(G)
73
+ assert b_T == n
74
+ assert b_C == set(G.nodes())
75
+ assert nx.tree_broadcast_time(G) == b_T
76
+
77
+
78
+ @pytest.mark.parametrize("n", range(2, 8))
79
+ def test_binomial_tree_broadcast(n):
80
+ G = nx.binomial_tree(n)
81
+ b_T, b_C = nx.tree_broadcast_center(G)
82
+ assert b_T == n
83
+ assert b_C == {0, 2 ** (n - 1)}
84
+ assert nx.tree_broadcast_time(G) == 2 * n - 1
85
+
86
+
87
+ @pytest.mark.parametrize("fn", [nx.tree_broadcast_center, nx.tree_broadcast_time])
88
+ @pytest.mark.parametrize("graph_type", [nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph])
89
+ def test_raises_graph_type(fn, graph_type):
90
+ """Check that broadcast functions properly raise for directed and multigraph types."""
91
+ G = nx.path_graph(5, create_using=graph_type)
92
+ with pytest.raises(nx.NetworkXNotImplemented, match=r"not implemented for"):
93
+ fn(G)
94
+
95
+
96
+ @pytest.mark.parametrize("fn", [nx.tree_broadcast_center, nx.tree_broadcast_time])
97
+ @pytest.mark.parametrize("gen", [nx.empty_graph, nx.cycle_graph])
98
+ def test_raises_not_tree(fn, gen):
99
+ """Check that broadcast functions properly raise for nontree graphs."""
100
+ G = gen(5)
101
+ with pytest.raises(nx.NotATree, match=r"not a tree"):
102
+ fn(G)
103
+
104
+
105
+ def test_raises_node_not_in_G():
106
+ """Check that `tree_broadcast_time` properly raises for invalid nodes."""
107
+ G = nx.path_graph(5)
108
+ with pytest.raises(nx.NodeNotFound, match=r"node.*not in G"):
109
+ nx.tree_broadcast_time(G, node=10)
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_chains.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Unit tests for the chain decomposition functions."""
2
+
3
+ from itertools import cycle, islice
4
+
5
+ import pytest
6
+
7
+ import networkx as nx
8
+
9
+
10
+ def cycles(seq):
11
+ """Yields cyclic permutations of the given sequence.
12
+
13
+ For example::
14
+
15
+ >>> list(cycles("abc"))
16
+ [('a', 'b', 'c'), ('b', 'c', 'a'), ('c', 'a', 'b')]
17
+
18
+ """
19
+ n = len(seq)
20
+ cycled_seq = cycle(seq)
21
+ for x in seq:
22
+ yield tuple(islice(cycled_seq, n))
23
+ next(cycled_seq)
24
+
25
+
26
+ def cyclic_equals(seq1, seq2):
27
+ """Decide whether two sequences are equal up to cyclic permutations.
28
+
29
+ For example::
30
+
31
+ >>> cyclic_equals("xyz", "zxy")
32
+ True
33
+ >>> cyclic_equals("xyz", "zyx")
34
+ False
35
+
36
+ """
37
+ # Cast seq2 to a tuple since `cycles()` yields tuples.
38
+ seq2 = tuple(seq2)
39
+ return any(x == tuple(seq2) for x in cycles(seq1))
40
+
41
+
42
+ class TestChainDecomposition:
43
+ """Unit tests for the chain decomposition function."""
44
+
45
+ def assertContainsChain(self, chain, expected):
46
+ # A cycle could be expressed in two different orientations, one
47
+ # forward and one backward, so we need to check for cyclic
48
+ # equality in both orientations.
49
+ reversed_chain = list(reversed([tuple(reversed(e)) for e in chain]))
50
+ for candidate in expected:
51
+ if cyclic_equals(chain, candidate):
52
+ break
53
+ if cyclic_equals(reversed_chain, candidate):
54
+ break
55
+ else:
56
+ self.fail("chain not found")
57
+
58
+ def test_decomposition(self):
59
+ edges = [
60
+ # DFS tree edges.
61
+ (1, 2),
62
+ (2, 3),
63
+ (3, 4),
64
+ (3, 5),
65
+ (5, 6),
66
+ (6, 7),
67
+ (7, 8),
68
+ (5, 9),
69
+ (9, 10),
70
+ # Nontree edges.
71
+ (1, 3),
72
+ (1, 4),
73
+ (2, 5),
74
+ (5, 10),
75
+ (6, 8),
76
+ ]
77
+ G = nx.Graph(edges)
78
+ expected = [
79
+ [(1, 3), (3, 2), (2, 1)],
80
+ [(1, 4), (4, 3)],
81
+ [(2, 5), (5, 3)],
82
+ [(5, 10), (10, 9), (9, 5)],
83
+ [(6, 8), (8, 7), (7, 6)],
84
+ ]
85
+ chains = list(nx.chain_decomposition(G, root=1))
86
+ assert len(chains) == len(expected)
87
+
88
+ def test_barbell_graph(self):
89
+ # The (3, 0) barbell graph has two triangles joined by a single edge.
90
+ G = nx.barbell_graph(3, 0)
91
+ chains = list(nx.chain_decomposition(G, root=0))
92
+ expected = [[(0, 1), (1, 2), (2, 0)], [(3, 4), (4, 5), (5, 3)]]
93
+ assert len(chains) == len(expected)
94
+ for chain in chains:
95
+ self.assertContainsChain(chain, expected)
96
+
97
+ def test_disconnected_graph(self):
98
+ """Test for a graph with multiple connected components."""
99
+ G = nx.barbell_graph(3, 0)
100
+ H = nx.barbell_graph(3, 0)
101
+ mapping = dict(zip(range(6), "abcdef"))
102
+ nx.relabel_nodes(H, mapping, copy=False)
103
+ G = nx.union(G, H)
104
+ chains = list(nx.chain_decomposition(G))
105
+ expected = [
106
+ [(0, 1), (1, 2), (2, 0)],
107
+ [(3, 4), (4, 5), (5, 3)],
108
+ [("a", "b"), ("b", "c"), ("c", "a")],
109
+ [("d", "e"), ("e", "f"), ("f", "d")],
110
+ ]
111
+ assert len(chains) == len(expected)
112
+ for chain in chains:
113
+ self.assertContainsChain(chain, expected)
114
+
115
+ def test_disconnected_graph_root_node(self):
116
+ """Test for a single component of a disconnected graph."""
117
+ G = nx.barbell_graph(3, 0)
118
+ H = nx.barbell_graph(3, 0)
119
+ mapping = dict(zip(range(6), "abcdef"))
120
+ nx.relabel_nodes(H, mapping, copy=False)
121
+ G = nx.union(G, H)
122
+ chains = list(nx.chain_decomposition(G, root="a"))
123
+ expected = [
124
+ [("a", "b"), ("b", "c"), ("c", "a")],
125
+ [("d", "e"), ("e", "f"), ("f", "d")],
126
+ ]
127
+ assert len(chains) == len(expected)
128
+ for chain in chains:
129
+ self.assertContainsChain(chain, expected)
130
+
131
+ def test_chain_decomposition_root_not_in_G(self):
132
+ """Test chain decomposition when root is not in graph"""
133
+ G = nx.Graph()
134
+ G.add_nodes_from([1, 2, 3])
135
+ with pytest.raises(nx.NodeNotFound):
136
+ nx.has_bridges(G, root=6)
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_chordal.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import networkx as nx
4
+
5
+
6
+ class TestMCS:
7
+ @classmethod
8
+ def setup_class(cls):
9
+ # simple graph
10
+ connected_chordal_G = nx.Graph()
11
+ connected_chordal_G.add_edges_from(
12
+ [
13
+ (1, 2),
14
+ (1, 3),
15
+ (2, 3),
16
+ (2, 4),
17
+ (3, 4),
18
+ (3, 5),
19
+ (3, 6),
20
+ (4, 5),
21
+ (4, 6),
22
+ (5, 6),
23
+ ]
24
+ )
25
+ cls.connected_chordal_G = connected_chordal_G
26
+
27
+ chordal_G = nx.Graph()
28
+ chordal_G.add_edges_from(
29
+ [
30
+ (1, 2),
31
+ (1, 3),
32
+ (2, 3),
33
+ (2, 4),
34
+ (3, 4),
35
+ (3, 5),
36
+ (3, 6),
37
+ (4, 5),
38
+ (4, 6),
39
+ (5, 6),
40
+ (7, 8),
41
+ ]
42
+ )
43
+ chordal_G.add_node(9)
44
+ cls.chordal_G = chordal_G
45
+
46
+ non_chordal_G = nx.Graph()
47
+ non_chordal_G.add_edges_from([(1, 2), (1, 3), (2, 4), (2, 5), (3, 4), (3, 5)])
48
+ cls.non_chordal_G = non_chordal_G
49
+
50
+ self_loop_G = nx.Graph()
51
+ self_loop_G.add_edges_from([(1, 1)])
52
+ cls.self_loop_G = self_loop_G
53
+
54
+ @pytest.mark.parametrize("G", (nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph()))
55
+ def test_is_chordal_not_implemented(self, G):
56
+ with pytest.raises(nx.NetworkXNotImplemented):
57
+ nx.is_chordal(G)
58
+
59
+ def test_is_chordal(self):
60
+ assert not nx.is_chordal(self.non_chordal_G)
61
+ assert nx.is_chordal(self.chordal_G)
62
+ assert nx.is_chordal(self.connected_chordal_G)
63
+ assert nx.is_chordal(nx.Graph())
64
+ assert nx.is_chordal(nx.complete_graph(3))
65
+ assert nx.is_chordal(nx.cycle_graph(3))
66
+ assert not nx.is_chordal(nx.cycle_graph(5))
67
+ assert nx.is_chordal(self.self_loop_G)
68
+
69
+ def test_induced_nodes(self):
70
+ G = nx.generators.classic.path_graph(10)
71
+ Induced_nodes = nx.find_induced_nodes(G, 1, 9, 2)
72
+ assert Induced_nodes == {1, 2, 3, 4, 5, 6, 7, 8, 9}
73
+ pytest.raises(
74
+ nx.NetworkXTreewidthBoundExceeded, nx.find_induced_nodes, G, 1, 9, 1
75
+ )
76
+ Induced_nodes = nx.find_induced_nodes(self.chordal_G, 1, 6)
77
+ assert Induced_nodes == {1, 2, 4, 6}
78
+ pytest.raises(nx.NetworkXError, nx.find_induced_nodes, self.non_chordal_G, 1, 5)
79
+
80
+ def test_graph_treewidth(self):
81
+ with pytest.raises(nx.NetworkXError, match="Input graph is not chordal"):
82
+ nx.chordal_graph_treewidth(self.non_chordal_G)
83
+
84
+ def test_chordal_find_cliques(self):
85
+ cliques = {
86
+ frozenset([9]),
87
+ frozenset([7, 8]),
88
+ frozenset([1, 2, 3]),
89
+ frozenset([2, 3, 4]),
90
+ frozenset([3, 4, 5, 6]),
91
+ }
92
+ assert set(nx.chordal_graph_cliques(self.chordal_G)) == cliques
93
+ with pytest.raises(nx.NetworkXError, match="Input graph is not chordal"):
94
+ set(nx.chordal_graph_cliques(self.non_chordal_G))
95
+ with pytest.raises(nx.NetworkXError, match="Input graph is not chordal"):
96
+ set(nx.chordal_graph_cliques(self.self_loop_G))
97
+
98
+ def test_chordal_find_cliques_path(self):
99
+ G = nx.path_graph(10)
100
+ cliqueset = nx.chordal_graph_cliques(G)
101
+ for u, v in G.edges():
102
+ assert frozenset([u, v]) in cliqueset or frozenset([v, u]) in cliqueset
103
+
104
+ def test_chordal_find_cliquesCC(self):
105
+ cliques = {frozenset([1, 2, 3]), frozenset([2, 3, 4]), frozenset([3, 4, 5, 6])}
106
+ cgc = nx.chordal_graph_cliques
107
+ assert set(cgc(self.connected_chordal_G)) == cliques
108
+
109
+ def test_complete_to_chordal_graph(self):
110
+ fgrg = nx.fast_gnp_random_graph
111
+ test_graphs = [
112
+ nx.barbell_graph(6, 2),
113
+ nx.cycle_graph(15),
114
+ nx.wheel_graph(20),
115
+ nx.grid_graph([10, 4]),
116
+ nx.ladder_graph(15),
117
+ nx.star_graph(5),
118
+ nx.bull_graph(),
119
+ fgrg(20, 0.3, seed=1),
120
+ ]
121
+ for G in test_graphs:
122
+ H, a = nx.complete_to_chordal_graph(G)
123
+ assert nx.is_chordal(H)
124
+ assert len(a) == H.number_of_nodes()
125
+ if nx.is_chordal(G):
126
+ assert G.number_of_edges() == H.number_of_edges()
127
+ assert set(a.values()) == {0}
128
+ else:
129
+ assert len(set(a.values())) == H.number_of_nodes()
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_clique.py ADDED
@@ -0,0 +1,300 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import networkx as nx
4
+ from networkx import convert_node_labels_to_integers as cnlti
5
+
6
+
7
+ class TestCliques:
8
+ def setup_method(self):
9
+ z = [3, 4, 3, 4, 2, 4, 2, 1, 1, 1, 1]
10
+ self.G = cnlti(nx.generators.havel_hakimi_graph(z), first_label=1)
11
+ self.cl = list(nx.find_cliques(self.G))
12
+ H = nx.complete_graph(6)
13
+ H = nx.relabel_nodes(H, {i: i + 1 for i in range(6)})
14
+ H.remove_edges_from([(2, 6), (2, 5), (2, 4), (1, 3), (5, 3)])
15
+ self.H = H
16
+
17
+ def test_find_cliques1(self):
18
+ cl = list(nx.find_cliques(self.G))
19
+ rcl = nx.find_cliques_recursive(self.G)
20
+ expected = [[2, 6, 1, 3], [2, 6, 4], [5, 4, 7], [8, 9], [10, 11]]
21
+ assert sorted(map(sorted, cl)) == sorted(map(sorted, rcl))
22
+ assert sorted(map(sorted, cl)) == sorted(map(sorted, expected))
23
+
24
+ def test_selfloops(self):
25
+ self.G.add_edge(1, 1)
26
+ cl = list(nx.find_cliques(self.G))
27
+ rcl = list(nx.find_cliques_recursive(self.G))
28
+ assert set(map(frozenset, cl)) == set(map(frozenset, rcl))
29
+ answer = [{2, 6, 1, 3}, {2, 6, 4}, {5, 4, 7}, {8, 9}, {10, 11}]
30
+ assert len(answer) == len(cl)
31
+ assert all(set(c) in answer for c in cl)
32
+
33
+ def test_find_cliques2(self):
34
+ hcl = list(nx.find_cliques(self.H))
35
+ assert sorted(map(sorted, hcl)) == [[1, 2], [1, 4, 5, 6], [2, 3], [3, 4, 6]]
36
+
37
+ def test_find_cliques3(self):
38
+ # all cliques are [[2, 6, 1, 3], [2, 6, 4], [5, 4, 7], [8, 9], [10, 11]]
39
+
40
+ cl = list(nx.find_cliques(self.G, [2]))
41
+ rcl = nx.find_cliques_recursive(self.G, [2])
42
+ expected = [[2, 6, 1, 3], [2, 6, 4]]
43
+ assert sorted(map(sorted, rcl)) == sorted(map(sorted, expected))
44
+ assert sorted(map(sorted, cl)) == sorted(map(sorted, expected))
45
+
46
+ cl = list(nx.find_cliques(self.G, [2, 3]))
47
+ rcl = nx.find_cliques_recursive(self.G, [2, 3])
48
+ expected = [[2, 6, 1, 3]]
49
+ assert sorted(map(sorted, rcl)) == sorted(map(sorted, expected))
50
+ assert sorted(map(sorted, cl)) == sorted(map(sorted, expected))
51
+
52
+ cl = list(nx.find_cliques(self.G, [2, 6, 4]))
53
+ rcl = nx.find_cliques_recursive(self.G, [2, 6, 4])
54
+ expected = [[2, 6, 4]]
55
+ assert sorted(map(sorted, rcl)) == sorted(map(sorted, expected))
56
+ assert sorted(map(sorted, cl)) == sorted(map(sorted, expected))
57
+
58
+ cl = list(nx.find_cliques(self.G, [2, 6, 4]))
59
+ rcl = nx.find_cliques_recursive(self.G, [2, 6, 4])
60
+ expected = [[2, 6, 4]]
61
+ assert sorted(map(sorted, rcl)) == sorted(map(sorted, expected))
62
+ assert sorted(map(sorted, cl)) == sorted(map(sorted, expected))
63
+
64
+ with pytest.raises(ValueError):
65
+ list(nx.find_cliques(self.G, [2, 6, 4, 1]))
66
+
67
+ with pytest.raises(ValueError):
68
+ list(nx.find_cliques_recursive(self.G, [2, 6, 4, 1]))
69
+
70
+ def test_find_cliques_directed(self):
71
+ G = nx.path_graph(4, create_using=nx.DiGraph)
72
+ msg = "not implemented for directed"
73
+ with pytest.raises(nx.NetworkXNotImplemented, match=msg):
74
+ list(nx.find_cliques(G))
75
+
76
+ with pytest.raises(nx.NetworkXNotImplemented, match=msg):
77
+ list(nx.find_cliques_recursive(G))
78
+
79
+ def test_number_of_cliques(self):
80
+ G = self.G
81
+ assert nx.number_of_cliques(G, 1) == 1
82
+ assert list(nx.number_of_cliques(G, [1]).values()) == [1]
83
+ assert list(nx.number_of_cliques(G, [1, 2]).values()) == [1, 2]
84
+ assert nx.number_of_cliques(G, [1, 2]) == {1: 1, 2: 2}
85
+ assert nx.number_of_cliques(G, 2) == 2
86
+ assert nx.number_of_cliques(G) == {
87
+ 1: 1,
88
+ 2: 2,
89
+ 3: 1,
90
+ 4: 2,
91
+ 5: 1,
92
+ 6: 2,
93
+ 7: 1,
94
+ 8: 1,
95
+ 9: 1,
96
+ 10: 1,
97
+ 11: 1,
98
+ }
99
+ assert nx.number_of_cliques(G, nodes=list(G)) == {
100
+ 1: 1,
101
+ 2: 2,
102
+ 3: 1,
103
+ 4: 2,
104
+ 5: 1,
105
+ 6: 2,
106
+ 7: 1,
107
+ 8: 1,
108
+ 9: 1,
109
+ 10: 1,
110
+ 11: 1,
111
+ }
112
+ assert nx.number_of_cliques(G, nodes=[2, 3, 4]) == {2: 2, 3: 1, 4: 2}
113
+ assert nx.number_of_cliques(G, cliques=self.cl) == {
114
+ 1: 1,
115
+ 2: 2,
116
+ 3: 1,
117
+ 4: 2,
118
+ 5: 1,
119
+ 6: 2,
120
+ 7: 1,
121
+ 8: 1,
122
+ 9: 1,
123
+ 10: 1,
124
+ 11: 1,
125
+ }
126
+ assert nx.number_of_cliques(G, list(G), cliques=self.cl) == {
127
+ 1: 1,
128
+ 2: 2,
129
+ 3: 1,
130
+ 4: 2,
131
+ 5: 1,
132
+ 6: 2,
133
+ 7: 1,
134
+ 8: 1,
135
+ 9: 1,
136
+ 10: 1,
137
+ 11: 1,
138
+ }
139
+
140
+ def test_node_clique_number(self):
141
+ G = self.G
142
+ assert nx.node_clique_number(G, 1) == 4
143
+ assert list(nx.node_clique_number(G, [1]).values()) == [4]
144
+ assert list(nx.node_clique_number(G, [1, 2]).values()) == [4, 4]
145
+ assert nx.node_clique_number(G, [1, 2]) == {1: 4, 2: 4}
146
+ assert nx.node_clique_number(G, 1) == 4
147
+ assert nx.node_clique_number(G) == {
148
+ 1: 4,
149
+ 2: 4,
150
+ 3: 4,
151
+ 4: 3,
152
+ 5: 3,
153
+ 6: 4,
154
+ 7: 3,
155
+ 8: 2,
156
+ 9: 2,
157
+ 10: 2,
158
+ 11: 2,
159
+ }
160
+ assert nx.node_clique_number(G, cliques=self.cl) == {
161
+ 1: 4,
162
+ 2: 4,
163
+ 3: 4,
164
+ 4: 3,
165
+ 5: 3,
166
+ 6: 4,
167
+ 7: 3,
168
+ 8: 2,
169
+ 9: 2,
170
+ 10: 2,
171
+ 11: 2,
172
+ }
173
+ assert nx.node_clique_number(G, [1, 2], cliques=self.cl) == {1: 4, 2: 4}
174
+ assert nx.node_clique_number(G, 1, cliques=self.cl) == 4
175
+
176
+ def test_make_clique_bipartite(self):
177
+ G = self.G
178
+ B = nx.make_clique_bipartite(G)
179
+ assert sorted(B) == [-5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
180
+ # Project onto the nodes of the original graph.
181
+ H = nx.projected_graph(B, range(1, 12))
182
+ assert H.adj == G.adj
183
+ # Project onto the nodes representing the cliques.
184
+ H1 = nx.projected_graph(B, range(-5, 0))
185
+ # Relabel the negative numbers as positive ones.
186
+ H1 = nx.relabel_nodes(H1, {-v: v for v in range(1, 6)})
187
+ assert sorted(H1) == [1, 2, 3, 4, 5]
188
+
189
+ def test_make_max_clique_graph(self):
190
+ """Tests that the maximal clique graph is the same as the bipartite
191
+ clique graph after being projected onto the nodes representing the
192
+ cliques.
193
+
194
+ """
195
+ G = self.G
196
+ B = nx.make_clique_bipartite(G)
197
+ # Project onto the nodes representing the cliques.
198
+ H1 = nx.projected_graph(B, range(-5, 0))
199
+ # Relabel the negative numbers as nonnegative ones, starting at
200
+ # 0.
201
+ H1 = nx.relabel_nodes(H1, {-v: v - 1 for v in range(1, 6)})
202
+ H2 = nx.make_max_clique_graph(G)
203
+ assert H1.adj == H2.adj
204
+
205
+ def test_directed(self):
206
+ with pytest.raises(nx.NetworkXNotImplemented):
207
+ next(nx.find_cliques(nx.DiGraph()))
208
+
209
+ def test_find_cliques_trivial(self):
210
+ G = nx.Graph()
211
+ assert sorted(nx.find_cliques(G)) == []
212
+ assert sorted(nx.find_cliques_recursive(G)) == []
213
+
214
+ def test_make_max_clique_graph_create_using(self):
215
+ G = nx.Graph([(1, 2), (3, 1), (4, 1), (5, 6)])
216
+ E = nx.Graph([(0, 1), (0, 2), (1, 2)])
217
+ E.add_node(3)
218
+ assert nx.is_isomorphic(nx.make_max_clique_graph(G, create_using=nx.Graph), E)
219
+
220
+
221
+ class TestEnumerateAllCliques:
222
+ def test_paper_figure_4(self):
223
+ # Same graph as given in Fig. 4 of paper enumerate_all_cliques is
224
+ # based on.
225
+ # http://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=1559964&isnumber=33129
226
+ G = nx.Graph()
227
+ edges_fig_4 = [
228
+ ("a", "b"),
229
+ ("a", "c"),
230
+ ("a", "d"),
231
+ ("a", "e"),
232
+ ("b", "c"),
233
+ ("b", "d"),
234
+ ("b", "e"),
235
+ ("c", "d"),
236
+ ("c", "e"),
237
+ ("d", "e"),
238
+ ("f", "b"),
239
+ ("f", "c"),
240
+ ("f", "g"),
241
+ ("g", "f"),
242
+ ("g", "c"),
243
+ ("g", "d"),
244
+ ("g", "e"),
245
+ ]
246
+ G.add_edges_from(edges_fig_4)
247
+
248
+ cliques = list(nx.enumerate_all_cliques(G))
249
+ clique_sizes = list(map(len, cliques))
250
+ assert sorted(clique_sizes) == clique_sizes
251
+
252
+ expected_cliques = [
253
+ ["a"],
254
+ ["b"],
255
+ ["c"],
256
+ ["d"],
257
+ ["e"],
258
+ ["f"],
259
+ ["g"],
260
+ ["a", "b"],
261
+ ["a", "b", "d"],
262
+ ["a", "b", "d", "e"],
263
+ ["a", "b", "e"],
264
+ ["a", "c"],
265
+ ["a", "c", "d"],
266
+ ["a", "c", "d", "e"],
267
+ ["a", "c", "e"],
268
+ ["a", "d"],
269
+ ["a", "d", "e"],
270
+ ["a", "e"],
271
+ ["b", "c"],
272
+ ["b", "c", "d"],
273
+ ["b", "c", "d", "e"],
274
+ ["b", "c", "e"],
275
+ ["b", "c", "f"],
276
+ ["b", "d"],
277
+ ["b", "d", "e"],
278
+ ["b", "e"],
279
+ ["b", "f"],
280
+ ["c", "d"],
281
+ ["c", "d", "e"],
282
+ ["c", "d", "e", "g"],
283
+ ["c", "d", "g"],
284
+ ["c", "e"],
285
+ ["c", "e", "g"],
286
+ ["c", "f"],
287
+ ["c", "f", "g"],
288
+ ["c", "g"],
289
+ ["d", "e"],
290
+ ["d", "e", "g"],
291
+ ["d", "g"],
292
+ ["e", "g"],
293
+ ["f", "g"],
294
+ ["a", "b", "c"],
295
+ ["a", "b", "c", "d"],
296
+ ["a", "b", "c", "d", "e"],
297
+ ["a", "b", "c", "e"],
298
+ ]
299
+
300
+ assert sorted(map(sorted, cliques)) == sorted(map(sorted, expected_cliques))
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_cluster.py ADDED
@@ -0,0 +1,678 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import networkx as nx
4
+
5
+
6
+ def test_square_clustering_adjacent_squares():
7
+ G = nx.Graph([(1, 2), (1, 3), (2, 4), (3, 4), (3, 5), (4, 6), (5, 6)])
8
+ # Corner nodes: C_4 == 0.5, central face nodes: C_4 = 1 / 3
9
+ expected = {1: 0.5, 2: 0.5, 3: 1 / 3, 4: 1 / 3, 5: 0.5, 6: 0.5}
10
+ assert nx.square_clustering(G) == expected
11
+
12
+
13
+ def test_square_clustering_2d_grid():
14
+ G = nx.grid_2d_graph(3, 3)
15
+ # Central node: 4 squares out of 20 potential
16
+ expected = {
17
+ (0, 0): 1 / 3,
18
+ (0, 1): 0.25,
19
+ (0, 2): 1 / 3,
20
+ (1, 0): 0.25,
21
+ (1, 1): 0.2,
22
+ (1, 2): 0.25,
23
+ (2, 0): 1 / 3,
24
+ (2, 1): 0.25,
25
+ (2, 2): 1 / 3,
26
+ }
27
+ assert nx.square_clustering(G) == expected
28
+
29
+
30
+ def test_square_clustering_multiple_squares_non_complete():
31
+ """An example where all nodes are part of all squares, but not every node
32
+ is connected to every other."""
33
+ G = nx.Graph([(0, 1), (0, 2), (1, 3), (2, 3), (1, 4), (2, 4), (1, 5), (2, 5)])
34
+ expected = {n: 1 for n in G}
35
+ assert nx.square_clustering(G) == expected
36
+
37
+
38
+ class TestTriangles:
39
+ def test_empty(self):
40
+ G = nx.Graph()
41
+ assert list(nx.triangles(G).values()) == []
42
+
43
+ def test_path(self):
44
+ G = nx.path_graph(10)
45
+ assert list(nx.triangles(G).values()) == [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
46
+ assert nx.triangles(G) == {
47
+ 0: 0,
48
+ 1: 0,
49
+ 2: 0,
50
+ 3: 0,
51
+ 4: 0,
52
+ 5: 0,
53
+ 6: 0,
54
+ 7: 0,
55
+ 8: 0,
56
+ 9: 0,
57
+ }
58
+
59
+ def test_cubical(self):
60
+ G = nx.cubical_graph()
61
+ assert list(nx.triangles(G).values()) == [0, 0, 0, 0, 0, 0, 0, 0]
62
+ assert nx.triangles(G, 1) == 0
63
+ assert list(nx.triangles(G, [1, 2]).values()) == [0, 0]
64
+ assert nx.triangles(G, 1) == 0
65
+ assert nx.triangles(G, [1, 2]) == {1: 0, 2: 0}
66
+
67
+ def test_k5(self):
68
+ G = nx.complete_graph(5)
69
+ assert list(nx.triangles(G).values()) == [6, 6, 6, 6, 6]
70
+ assert sum(nx.triangles(G).values()) / 3 == 10
71
+ assert nx.triangles(G, 1) == 6
72
+ G.remove_edge(1, 2)
73
+ assert list(nx.triangles(G).values()) == [5, 3, 3, 5, 5]
74
+ assert nx.triangles(G, 1) == 3
75
+ G.add_edge(3, 3) # ignore self-edges
76
+ assert list(nx.triangles(G).values()) == [5, 3, 3, 5, 5]
77
+ assert nx.triangles(G, 3) == 5
78
+
79
+
80
+ def test_all_triangles_non_integer_nodes():
81
+ G = nx.Graph()
82
+ G.add_edges_from(
83
+ [
84
+ ("a", "b"),
85
+ ("b", "c"),
86
+ ("c", "a"), # triangle: a-b-c
87
+ ]
88
+ )
89
+ expected = {frozenset({"a", "b", "c"})}
90
+ assert {frozenset(t) for t in nx.all_triangles(G)} == expected
91
+
92
+
93
+ def test_all_triangles_overlapping():
94
+ G = nx.Graph()
95
+ G.add_edges_from(
96
+ [
97
+ (0, 1),
98
+ (1, 2),
99
+ (2, 0), # triangle: 0-1-2
100
+ (0, 2),
101
+ (2, 3),
102
+ (3, 0), # triangle: 0-2-3
103
+ ]
104
+ )
105
+ expected = {frozenset({0, 1, 2}), frozenset({0, 2, 3})}
106
+ assert {frozenset(t) for t in nx.all_triangles(G)} == expected
107
+
108
+
109
+ def test_all_triangles_subset():
110
+ G = nx.Graph()
111
+ G.add_edges_from(
112
+ [
113
+ (0, 1),
114
+ (1, 2),
115
+ (2, 0), # triangle: 0-1-2
116
+ (2, 3),
117
+ (3, 4),
118
+ (4, 2), # triangle: 2-3-4
119
+ ]
120
+ )
121
+ assert {frozenset(t) for t in nx.all_triangles(G, nbunch=[0, 1])} == {
122
+ frozenset({0, 1, 2})
123
+ }
124
+
125
+
126
+ def test_all_triangles_subset_empty():
127
+ G = nx.Graph()
128
+ G.add_edges_from(
129
+ [
130
+ (0, 1),
131
+ (1, 2),
132
+ (2, 0), # triangle: 0-1-2
133
+ (2, 3),
134
+ (3, 4),
135
+ (4, 2), # triangle: 2-3-4
136
+ (5, 2),
137
+ ]
138
+ )
139
+ assert list(nx.all_triangles(G, nbunch=[5])) == []
140
+
141
+
142
+ def test_all_triangles_no_triangles():
143
+ G = nx.path_graph(4)
144
+ assert list(nx.all_triangles(G)) == []
145
+
146
+
147
+ def test_all_triangles_complete_graph_exact():
148
+ G = nx.complete_graph(4)
149
+
150
+ expected = {
151
+ frozenset({0, 1, 2}),
152
+ frozenset({0, 1, 3}),
153
+ frozenset({0, 2, 3}),
154
+ frozenset({1, 2, 3}),
155
+ }
156
+
157
+ assert {frozenset(t) for t in nx.all_triangles(G)} == expected
158
+
159
+
160
+ def test_all_triangles_directed_graph():
161
+ G = nx.DiGraph()
162
+ G.add_edges_from([(0, 1), (1, 2), (2, 0)])
163
+ with pytest.raises(nx.NetworkXNotImplemented):
164
+ list(nx.all_triangles(G))
165
+
166
+
167
+ @pytest.mark.parametrize("graph_type", [nx.Graph, nx.MultiGraph])
168
+ def test_all_triangles_multiedges(graph_type):
169
+ G = graph_type()
170
+ G.add_edges_from([(0, 1), (0, 2), (1, 2), (1, 2)])
171
+ assert {frozenset(t) for t in nx.all_triangles(G)} == {frozenset({0, 1, 2})}
172
+
173
+
174
+ class TestDirectedClustering:
175
+ def test_clustering(self):
176
+ G = nx.DiGraph()
177
+ assert list(nx.clustering(G).values()) == []
178
+ assert nx.clustering(G) == {}
179
+
180
+ def test_path(self):
181
+ G = nx.path_graph(10, create_using=nx.DiGraph())
182
+ assert list(nx.clustering(G).values()) == [
183
+ 0,
184
+ 0,
185
+ 0,
186
+ 0,
187
+ 0,
188
+ 0,
189
+ 0,
190
+ 0,
191
+ 0,
192
+ 0,
193
+ ]
194
+ assert nx.clustering(G) == {
195
+ 0: 0,
196
+ 1: 0,
197
+ 2: 0,
198
+ 3: 0,
199
+ 4: 0,
200
+ 5: 0,
201
+ 6: 0,
202
+ 7: 0,
203
+ 8: 0,
204
+ 9: 0,
205
+ }
206
+ assert nx.clustering(G, 0) == 0
207
+
208
+ def test_k5(self):
209
+ G = nx.complete_graph(5, create_using=nx.DiGraph())
210
+ assert list(nx.clustering(G).values()) == [1, 1, 1, 1, 1]
211
+ assert nx.average_clustering(G) == 1
212
+ G.remove_edge(1, 2)
213
+ assert list(nx.clustering(G).values()) == [
214
+ 11 / 12,
215
+ 1,
216
+ 1,
217
+ 11 / 12,
218
+ 11 / 12,
219
+ ]
220
+ assert nx.clustering(G, [1, 4]) == {1: 1, 4: 11 / 12}
221
+ G.remove_edge(2, 1)
222
+ assert list(nx.clustering(G).values()) == [
223
+ 5 / 6,
224
+ 1,
225
+ 1,
226
+ 5 / 6,
227
+ 5 / 6,
228
+ ]
229
+ assert nx.clustering(G, [1, 4]) == {1: 1, 4: 0.83333333333333337}
230
+ assert nx.clustering(G, 4) == 5 / 6
231
+
232
+ def test_triangle_and_edge(self):
233
+ G = nx.cycle_graph(3, create_using=nx.DiGraph())
234
+ G.add_edge(0, 4)
235
+ assert nx.clustering(G)[0] == 1 / 6
236
+
237
+
238
+ class TestDirectedWeightedClustering:
239
+ @classmethod
240
+ def setup_class(cls):
241
+ global np
242
+ np = pytest.importorskip("numpy")
243
+
244
+ def test_clustering(self):
245
+ G = nx.DiGraph()
246
+ assert list(nx.clustering(G, weight="weight").values()) == []
247
+ assert nx.clustering(G) == {}
248
+
249
+ def test_path(self):
250
+ G = nx.path_graph(10, create_using=nx.DiGraph())
251
+ assert list(nx.clustering(G, weight="weight").values()) == [
252
+ 0,
253
+ 0,
254
+ 0,
255
+ 0,
256
+ 0,
257
+ 0,
258
+ 0,
259
+ 0,
260
+ 0,
261
+ 0,
262
+ ]
263
+ assert nx.clustering(G, weight="weight") == {
264
+ 0: 0,
265
+ 1: 0,
266
+ 2: 0,
267
+ 3: 0,
268
+ 4: 0,
269
+ 5: 0,
270
+ 6: 0,
271
+ 7: 0,
272
+ 8: 0,
273
+ 9: 0,
274
+ }
275
+
276
+ def test_k5(self):
277
+ G = nx.complete_graph(5, create_using=nx.DiGraph())
278
+ assert list(nx.clustering(G, weight="weight").values()) == [1, 1, 1, 1, 1]
279
+ assert nx.average_clustering(G, weight="weight") == 1
280
+ G.remove_edge(1, 2)
281
+ assert list(nx.clustering(G, weight="weight").values()) == [
282
+ 11 / 12,
283
+ 1,
284
+ 1,
285
+ 11 / 12,
286
+ 11 / 12,
287
+ ]
288
+ assert nx.clustering(G, [1, 4], weight="weight") == {1: 1, 4: 11 / 12}
289
+ G.remove_edge(2, 1)
290
+ assert list(nx.clustering(G, weight="weight").values()) == [
291
+ 5 / 6,
292
+ 1,
293
+ 1,
294
+ 5 / 6,
295
+ 5 / 6,
296
+ ]
297
+ assert nx.clustering(G, [1, 4], weight="weight") == {
298
+ 1: 1,
299
+ 4: 0.83333333333333337,
300
+ }
301
+
302
+ def test_triangle_and_edge(self):
303
+ G = nx.cycle_graph(3, create_using=nx.DiGraph())
304
+ G.add_edge(0, 4, weight=2)
305
+ assert nx.clustering(G)[0] == 1 / 6
306
+ # Relaxed comparisons to allow graphblas-algorithms to pass tests
307
+ np.testing.assert_allclose(nx.clustering(G, weight="weight")[0], 1 / 12)
308
+ np.testing.assert_allclose(nx.clustering(G, 0, weight="weight"), 1 / 12)
309
+
310
+
311
+ class TestWeightedClustering:
312
+ @classmethod
313
+ def setup_class(cls):
314
+ global np
315
+ np = pytest.importorskip("numpy")
316
+
317
+ def test_clustering(self):
318
+ G = nx.Graph()
319
+ assert list(nx.clustering(G, weight="weight").values()) == []
320
+ assert nx.clustering(G) == {}
321
+
322
+ def test_path(self):
323
+ G = nx.path_graph(10)
324
+ assert list(nx.clustering(G, weight="weight").values()) == [
325
+ 0,
326
+ 0,
327
+ 0,
328
+ 0,
329
+ 0,
330
+ 0,
331
+ 0,
332
+ 0,
333
+ 0,
334
+ 0,
335
+ ]
336
+ assert nx.clustering(G, weight="weight") == {
337
+ 0: 0,
338
+ 1: 0,
339
+ 2: 0,
340
+ 3: 0,
341
+ 4: 0,
342
+ 5: 0,
343
+ 6: 0,
344
+ 7: 0,
345
+ 8: 0,
346
+ 9: 0,
347
+ }
348
+
349
+ def test_cubical(self):
350
+ G = nx.cubical_graph()
351
+ assert list(nx.clustering(G, weight="weight").values()) == [
352
+ 0,
353
+ 0,
354
+ 0,
355
+ 0,
356
+ 0,
357
+ 0,
358
+ 0,
359
+ 0,
360
+ ]
361
+ assert nx.clustering(G, 1) == 0
362
+ assert list(nx.clustering(G, [1, 2], weight="weight").values()) == [0, 0]
363
+ assert nx.clustering(G, 1, weight="weight") == 0
364
+ assert nx.clustering(G, [1, 2], weight="weight") == {1: 0, 2: 0}
365
+
366
+ def test_k5(self):
367
+ G = nx.complete_graph(5)
368
+ assert list(nx.clustering(G, weight="weight").values()) == [1, 1, 1, 1, 1]
369
+ assert nx.average_clustering(G, weight="weight") == 1
370
+ G.remove_edge(1, 2)
371
+ assert list(nx.clustering(G, weight="weight").values()) == [
372
+ 5 / 6,
373
+ 1,
374
+ 1,
375
+ 5 / 6,
376
+ 5 / 6,
377
+ ]
378
+ assert nx.clustering(G, [1, 4], weight="weight") == {
379
+ 1: 1,
380
+ 4: 0.83333333333333337,
381
+ }
382
+
383
+ def test_triangle_and_edge(self):
384
+ G = nx.cycle_graph(3)
385
+ G.add_edge(0, 4, weight=2)
386
+ assert nx.clustering(G)[0] == 1 / 3
387
+ np.testing.assert_allclose(nx.clustering(G, weight="weight")[0], 1 / 6)
388
+ np.testing.assert_allclose(nx.clustering(G, 0, weight="weight"), 1 / 6)
389
+
390
+ def test_triangle_and_signed_edge(self):
391
+ G = nx.cycle_graph(3)
392
+ G.add_edge(0, 1, weight=-1)
393
+ G.add_edge(3, 0, weight=0)
394
+ assert nx.clustering(G)[0] == 1 / 3
395
+ assert nx.clustering(G, weight="weight")[0] == -1 / 3
396
+
397
+
398
+ class TestClustering:
399
+ @classmethod
400
+ def setup_class(cls):
401
+ pytest.importorskip("numpy")
402
+
403
+ def test_clustering(self):
404
+ G = nx.Graph()
405
+ assert list(nx.clustering(G).values()) == []
406
+ assert nx.clustering(G) == {}
407
+
408
+ def test_path(self):
409
+ G = nx.path_graph(10)
410
+ assert list(nx.clustering(G).values()) == [
411
+ 0,
412
+ 0,
413
+ 0,
414
+ 0,
415
+ 0,
416
+ 0,
417
+ 0,
418
+ 0,
419
+ 0,
420
+ 0,
421
+ ]
422
+ assert nx.clustering(G) == {
423
+ 0: 0,
424
+ 1: 0,
425
+ 2: 0,
426
+ 3: 0,
427
+ 4: 0,
428
+ 5: 0,
429
+ 6: 0,
430
+ 7: 0,
431
+ 8: 0,
432
+ 9: 0,
433
+ }
434
+
435
+ def test_cubical(self):
436
+ G = nx.cubical_graph()
437
+ assert list(nx.clustering(G).values()) == [0, 0, 0, 0, 0, 0, 0, 0]
438
+ assert nx.clustering(G, 1) == 0
439
+ assert list(nx.clustering(G, [1, 2]).values()) == [0, 0]
440
+ assert nx.clustering(G, 1) == 0
441
+ assert nx.clustering(G, [1, 2]) == {1: 0, 2: 0}
442
+
443
+ def test_k5(self):
444
+ G = nx.complete_graph(5)
445
+ assert list(nx.clustering(G).values()) == [1, 1, 1, 1, 1]
446
+ assert nx.average_clustering(G) == 1
447
+ G.remove_edge(1, 2)
448
+ assert list(nx.clustering(G).values()) == [
449
+ 5 / 6,
450
+ 1,
451
+ 1,
452
+ 5 / 6,
453
+ 5 / 6,
454
+ ]
455
+ assert nx.clustering(G, [1, 4]) == {1: 1, 4: 0.83333333333333337}
456
+
457
+ def test_k5_signed(self):
458
+ G = nx.complete_graph(5)
459
+ assert list(nx.clustering(G).values()) == [1, 1, 1, 1, 1]
460
+ assert nx.average_clustering(G) == 1
461
+ G.remove_edge(1, 2)
462
+ G.add_edge(0, 1, weight=-1)
463
+ assert list(nx.clustering(G, weight="weight").values()) == [
464
+ 1 / 6,
465
+ -1 / 3,
466
+ 1,
467
+ 3 / 6,
468
+ 3 / 6,
469
+ ]
470
+
471
+
472
+ class TestTransitivity:
473
+ def test_transitivity(self):
474
+ G = nx.Graph()
475
+ assert nx.transitivity(G) == 0
476
+
477
+ def test_path(self):
478
+ G = nx.path_graph(10)
479
+ assert nx.transitivity(G) == 0
480
+
481
+ def test_cubical(self):
482
+ G = nx.cubical_graph()
483
+ assert nx.transitivity(G) == 0
484
+
485
+ def test_k5(self):
486
+ G = nx.complete_graph(5)
487
+ assert nx.transitivity(G) == 1
488
+ G.remove_edge(1, 2)
489
+ assert nx.transitivity(G) == 0.875
490
+
491
+
492
+ class TestSquareClustering:
493
+ def test_clustering(self):
494
+ G = nx.Graph()
495
+ assert list(nx.square_clustering(G).values()) == []
496
+ assert nx.square_clustering(G) == {}
497
+
498
+ def test_path(self):
499
+ G = nx.path_graph(10)
500
+ assert list(nx.square_clustering(G).values()) == [
501
+ 0,
502
+ 0,
503
+ 0,
504
+ 0,
505
+ 0,
506
+ 0,
507
+ 0,
508
+ 0,
509
+ 0,
510
+ 0,
511
+ ]
512
+ assert nx.square_clustering(G) == {
513
+ 0: 0,
514
+ 1: 0,
515
+ 2: 0,
516
+ 3: 0,
517
+ 4: 0,
518
+ 5: 0,
519
+ 6: 0,
520
+ 7: 0,
521
+ 8: 0,
522
+ 9: 0,
523
+ }
524
+
525
+ def test_cubical(self):
526
+ G = nx.cubical_graph()
527
+ assert list(nx.square_clustering(G).values()) == [
528
+ 1 / 3,
529
+ 1 / 3,
530
+ 1 / 3,
531
+ 1 / 3,
532
+ 1 / 3,
533
+ 1 / 3,
534
+ 1 / 3,
535
+ 1 / 3,
536
+ ]
537
+ assert list(nx.square_clustering(G, [1, 2]).values()) == [1 / 3, 1 / 3]
538
+ assert nx.square_clustering(G, [1])[1] == 1 / 3
539
+ assert nx.square_clustering(G, 1) == 1 / 3
540
+ assert nx.square_clustering(G, [1, 2]) == {1: 1 / 3, 2: 1 / 3}
541
+
542
+ def test_k5(self):
543
+ G = nx.complete_graph(5)
544
+ assert list(nx.square_clustering(G).values()) == [1, 1, 1, 1, 1]
545
+
546
+ def test_bipartite_k5(self):
547
+ G = nx.complete_bipartite_graph(5, 5)
548
+ assert list(nx.square_clustering(G).values()) == [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
549
+
550
+ def test_lind_square_clustering(self):
551
+ """Test C4 for figure 1 Lind et al (2005)"""
552
+ G = nx.Graph(
553
+ [
554
+ (1, 2),
555
+ (1, 3),
556
+ (1, 6),
557
+ (1, 7),
558
+ (2, 4),
559
+ (2, 5),
560
+ (3, 4),
561
+ (3, 5),
562
+ (6, 7),
563
+ (7, 8),
564
+ (6, 8),
565
+ (7, 9),
566
+ (7, 10),
567
+ (6, 11),
568
+ (6, 12),
569
+ (2, 13),
570
+ (2, 14),
571
+ (3, 15),
572
+ (3, 16),
573
+ ]
574
+ )
575
+ G1 = G.subgraph([1, 2, 3, 4, 5, 13, 14, 15, 16])
576
+ G2 = G.subgraph([1, 6, 7, 8, 9, 10, 11, 12])
577
+ assert nx.square_clustering(G, [1])[1] == 3 / 43
578
+ assert nx.square_clustering(G1, [1])[1] == 2 / 6
579
+ assert nx.square_clustering(G2, [1])[1] == 1 / 5
580
+
581
+ def test_peng_square_clustering(self):
582
+ """Test eq2 for figure 1 Peng et al (2008)"""
583
+ # Example graph from figure 1b
584
+ G = nx.Graph([(1, 2), (1, 3), (2, 4), (3, 4), (3, 5), (3, 6)])
585
+ # From table 1, row 2
586
+ expected = {1: 1 / 3, 2: 1, 3: 0.2, 4: 1 / 3, 5: 0, 6: 0}
587
+ assert nx.square_clustering(G) == expected
588
+
589
+ def test_self_loops_square_clustering(self):
590
+ G = nx.path_graph(5)
591
+ assert nx.square_clustering(G) == {0: 0, 1: 0, 2: 0, 3: 0, 4: 0}
592
+ G.add_edges_from([(0, 0), (1, 1), (2, 2)])
593
+ assert nx.square_clustering(G) == {0: 0, 1: 0, 2: 0, 3: 0, 4: 0}
594
+
595
+
596
+ class TestAverageClustering:
597
+ @classmethod
598
+ def setup_class(cls):
599
+ pytest.importorskip("numpy")
600
+
601
+ def test_empty(self):
602
+ G = nx.Graph()
603
+ with pytest.raises(ZeroDivisionError):
604
+ nx.average_clustering(G)
605
+
606
+ def test_average_clustering(self):
607
+ G = nx.cycle_graph(3)
608
+ G.add_edge(2, 3)
609
+ assert nx.average_clustering(G) == (1 + 1 + 1 / 3) / 4
610
+ assert nx.average_clustering(G, count_zeros=True) == (1 + 1 + 1 / 3) / 4
611
+ assert nx.average_clustering(G, count_zeros=False) == (1 + 1 + 1 / 3) / 3
612
+ assert nx.average_clustering(G, [1, 2, 3]) == (1 + 1 / 3) / 3
613
+ assert nx.average_clustering(G, [1, 2, 3], count_zeros=True) == (1 + 1 / 3) / 3
614
+ assert nx.average_clustering(G, [1, 2, 3], count_zeros=False) == (1 + 1 / 3) / 2
615
+
616
+ def test_average_clustering_signed(self):
617
+ G = nx.cycle_graph(3)
618
+ G.add_edge(2, 3)
619
+ G.add_edge(0, 1, weight=-1)
620
+ assert nx.average_clustering(G, weight="weight") == (-1 - 1 - 1 / 3) / 4
621
+ assert (
622
+ nx.average_clustering(G, weight="weight", count_zeros=True)
623
+ == (-1 - 1 - 1 / 3) / 4
624
+ )
625
+ assert (
626
+ nx.average_clustering(G, weight="weight", count_zeros=False)
627
+ == (-1 - 1 - 1 / 3) / 3
628
+ )
629
+
630
+
631
+ class TestDirectedAverageClustering:
632
+ @classmethod
633
+ def setup_class(cls):
634
+ pytest.importorskip("numpy")
635
+
636
+ def test_empty(self):
637
+ G = nx.DiGraph()
638
+ with pytest.raises(ZeroDivisionError):
639
+ nx.average_clustering(G)
640
+
641
+ def test_average_clustering(self):
642
+ G = nx.cycle_graph(3, create_using=nx.DiGraph())
643
+ G.add_edge(2, 3)
644
+ assert nx.average_clustering(G) == (1 + 1 + 1 / 3) / 8
645
+ assert nx.average_clustering(G, count_zeros=True) == (1 + 1 + 1 / 3) / 8
646
+ assert nx.average_clustering(G, count_zeros=False) == (1 + 1 + 1 / 3) / 6
647
+ assert nx.average_clustering(G, [1, 2, 3]) == (1 + 1 / 3) / 6
648
+ assert nx.average_clustering(G, [1, 2, 3], count_zeros=True) == (1 + 1 / 3) / 6
649
+ assert nx.average_clustering(G, [1, 2, 3], count_zeros=False) == (1 + 1 / 3) / 4
650
+
651
+
652
+ class TestGeneralizedDegree:
653
+ def test_generalized_degree(self):
654
+ G = nx.Graph()
655
+ assert nx.generalized_degree(G) == {}
656
+
657
+ def test_path(self):
658
+ G = nx.path_graph(5)
659
+ assert nx.generalized_degree(G, 0) == {0: 1}
660
+ assert nx.generalized_degree(G, 1) == {0: 2}
661
+
662
+ def test_cubical(self):
663
+ G = nx.cubical_graph()
664
+ assert nx.generalized_degree(G, 0) == {0: 3}
665
+
666
+ def test_k5(self):
667
+ G = nx.complete_graph(5)
668
+ assert nx.generalized_degree(G, 0) == {3: 4}
669
+ G.remove_edge(0, 1)
670
+ assert nx.generalized_degree(G, 0) == {2: 3}
671
+ assert nx.generalized_degree(G, [1, 2]) == {1: {2: 3}, 2: {2: 2, 3: 2}}
672
+ assert nx.generalized_degree(G) == {
673
+ 0: {2: 3},
674
+ 1: {2: 3},
675
+ 2: {2: 2, 3: 2},
676
+ 3: {2: 2, 3: 2},
677
+ 4: {2: 2, 3: 2},
678
+ }
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_communicability.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections import defaultdict
2
+
3
+ import pytest
4
+
5
+ pytest.importorskip("numpy")
6
+ pytest.importorskip("scipy")
7
+
8
+ import networkx as nx
9
+ from networkx.algorithms.communicability_alg import communicability, communicability_exp
10
+
11
+
12
+ class TestCommunicability:
13
+ def test_communicability(self):
14
+ answer = {
15
+ 0: {0: 1.5430806348152435, 1: 1.1752011936438012},
16
+ 1: {0: 1.1752011936438012, 1: 1.5430806348152435},
17
+ }
18
+ # answer={(0, 0): 1.5430806348152435,
19
+ # (0, 1): 1.1752011936438012,
20
+ # (1, 0): 1.1752011936438012,
21
+ # (1, 1): 1.5430806348152435}
22
+
23
+ result = communicability(nx.path_graph(2))
24
+ for k1, val in result.items():
25
+ for k2 in val:
26
+ assert answer[k1][k2] == pytest.approx(result[k1][k2], abs=1e-7)
27
+
28
+ def test_communicability2(self):
29
+ answer_orig = {
30
+ ("1", "1"): 1.6445956054135658,
31
+ ("1", "Albert"): 0.7430186221096251,
32
+ ("1", "Aric"): 0.7430186221096251,
33
+ ("1", "Dan"): 1.6208126320442937,
34
+ ("1", "Franck"): 0.42639707170035257,
35
+ ("Albert", "1"): 0.7430186221096251,
36
+ ("Albert", "Albert"): 2.4368257358712189,
37
+ ("Albert", "Aric"): 1.4368257358712191,
38
+ ("Albert", "Dan"): 2.0472097037446453,
39
+ ("Albert", "Franck"): 1.8340111678944691,
40
+ ("Aric", "1"): 0.7430186221096251,
41
+ ("Aric", "Albert"): 1.4368257358712191,
42
+ ("Aric", "Aric"): 2.4368257358712193,
43
+ ("Aric", "Dan"): 2.0472097037446457,
44
+ ("Aric", "Franck"): 1.8340111678944691,
45
+ ("Dan", "1"): 1.6208126320442937,
46
+ ("Dan", "Albert"): 2.0472097037446453,
47
+ ("Dan", "Aric"): 2.0472097037446457,
48
+ ("Dan", "Dan"): 3.1306328496328168,
49
+ ("Dan", "Franck"): 1.4860372442192515,
50
+ ("Franck", "1"): 0.42639707170035257,
51
+ ("Franck", "Albert"): 1.8340111678944691,
52
+ ("Franck", "Aric"): 1.8340111678944691,
53
+ ("Franck", "Dan"): 1.4860372442192515,
54
+ ("Franck", "Franck"): 2.3876142275231915,
55
+ }
56
+
57
+ answer = defaultdict(dict)
58
+ for (k1, k2), v in answer_orig.items():
59
+ answer[k1][k2] = v
60
+
61
+ G1 = nx.Graph(
62
+ [
63
+ ("Franck", "Aric"),
64
+ ("Aric", "Dan"),
65
+ ("Dan", "Albert"),
66
+ ("Albert", "Franck"),
67
+ ("Dan", "1"),
68
+ ("Franck", "Albert"),
69
+ ]
70
+ )
71
+
72
+ result = communicability(G1)
73
+ for k1, val in result.items():
74
+ for k2 in val:
75
+ assert answer[k1][k2] == pytest.approx(result[k1][k2], abs=1e-7)
76
+
77
+ result = communicability_exp(G1)
78
+ for k1, val in result.items():
79
+ for k2 in val:
80
+ assert answer[k1][k2] == pytest.approx(result[k1][k2], abs=1e-7)
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_core.py ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import networkx as nx
4
+ from networkx.utils import nodes_equal
5
+
6
+
7
+ class TestCore:
8
+ @classmethod
9
+ def setup_class(cls):
10
+ # G is the example graph in Figure 1 from Batagelj and
11
+ # Zaversnik's paper titled An O(m) Algorithm for Cores
12
+ # Decomposition of Networks, 2003,
13
+ # http://arXiv.org/abs/cs/0310049. With nodes labeled as
14
+ # shown, the 3-core is given by nodes 1-8, the 2-core by nodes
15
+ # 9-16, the 1-core by nodes 17-20 and node 21 is in the
16
+ # 0-core.
17
+ t1 = nx.convert_node_labels_to_integers(nx.tetrahedral_graph(), 1)
18
+ t2 = nx.convert_node_labels_to_integers(t1, 5)
19
+ G = nx.union(t1, t2)
20
+ G.add_edges_from(
21
+ [
22
+ (3, 7),
23
+ (2, 11),
24
+ (11, 5),
25
+ (11, 12),
26
+ (5, 12),
27
+ (12, 19),
28
+ (12, 18),
29
+ (3, 9),
30
+ (7, 9),
31
+ (7, 10),
32
+ (9, 10),
33
+ (9, 20),
34
+ (17, 13),
35
+ (13, 14),
36
+ (14, 15),
37
+ (15, 16),
38
+ (16, 13),
39
+ ]
40
+ )
41
+ G.add_node(21)
42
+ cls.G = G
43
+
44
+ # Create the graph H resulting from the degree sequence
45
+ # [0, 1, 2, 2, 2, 2, 3] when using the Havel-Hakimi algorithm.
46
+
47
+ degseq = [0, 1, 2, 2, 2, 2, 3]
48
+ H = nx.havel_hakimi_graph(degseq)
49
+ mapping = {6: 0, 0: 1, 4: 3, 5: 6, 3: 4, 1: 2, 2: 5}
50
+ cls.H = nx.relabel_nodes(H, mapping)
51
+
52
+ def test_trivial(self):
53
+ """Empty graph"""
54
+ G = nx.Graph()
55
+ assert nx.core_number(G) == {}
56
+
57
+ def test_core_number(self):
58
+ core = nx.core_number(self.G)
59
+ nodes_by_core = [sorted(n for n in core if core[n] == val) for val in range(4)]
60
+ assert nodes_equal(nodes_by_core[0], [21])
61
+ assert nodes_equal(nodes_by_core[1], [17, 18, 19, 20])
62
+ assert nodes_equal(nodes_by_core[2], [9, 10, 11, 12, 13, 14, 15, 16])
63
+ assert nodes_equal(nodes_by_core[3], [1, 2, 3, 4, 5, 6, 7, 8])
64
+
65
+ def test_core_number2(self):
66
+ core = nx.core_number(self.H)
67
+ nodes_by_core = [sorted(n for n in core if core[n] == val) for val in range(3)]
68
+ assert nodes_equal(nodes_by_core[0], [0])
69
+ assert nodes_equal(nodes_by_core[1], [1, 3])
70
+ assert nodes_equal(nodes_by_core[2], [2, 4, 5, 6])
71
+
72
+ def test_core_number_multigraph(self):
73
+ G = nx.complete_graph(3)
74
+ G = nx.MultiGraph(G)
75
+ G.add_edge(1, 2)
76
+ with pytest.raises(
77
+ nx.NetworkXNotImplemented, match="not implemented for multigraph type"
78
+ ):
79
+ nx.core_number(G)
80
+
81
+ def test_core_number_self_loop(self):
82
+ G = nx.cycle_graph(3)
83
+ G.add_edge(0, 0)
84
+ with pytest.raises(
85
+ nx.NetworkXNotImplemented, match="Input graph has self loops"
86
+ ):
87
+ nx.core_number(G)
88
+
89
+ def test_directed_core_number(self):
90
+ """core number had a bug for directed graphs found in issue #1959"""
91
+ # small example where too timid edge removal can make cn[2] = 3
92
+ G = nx.DiGraph()
93
+ edges = [(1, 2), (2, 1), (2, 3), (2, 4), (3, 4), (4, 3)]
94
+ G.add_edges_from(edges)
95
+ assert nx.core_number(G) == {1: 2, 2: 2, 3: 2, 4: 2}
96
+ # small example where too aggressive edge removal can make cn[2] = 2
97
+ more_edges = [(1, 5), (3, 5), (4, 5), (3, 6), (4, 6), (5, 6)]
98
+ G.add_edges_from(more_edges)
99
+ assert nx.core_number(G) == {1: 3, 2: 3, 3: 3, 4: 3, 5: 3, 6: 3}
100
+
101
+ def test_main_core(self):
102
+ main_core_subgraph = nx.k_core(self.H)
103
+ assert sorted(main_core_subgraph.nodes()) == [2, 4, 5, 6]
104
+
105
+ def test_k_core(self):
106
+ # k=0
107
+ k_core_subgraph = nx.k_core(self.H, k=0)
108
+ assert sorted(k_core_subgraph.nodes()) == sorted(self.H.nodes())
109
+ # k=1
110
+ k_core_subgraph = nx.k_core(self.H, k=1)
111
+ assert sorted(k_core_subgraph.nodes()) == [1, 2, 3, 4, 5, 6]
112
+ # k = 2
113
+ k_core_subgraph = nx.k_core(self.H, k=2)
114
+ assert sorted(k_core_subgraph.nodes()) == [2, 4, 5, 6]
115
+
116
+ def test_k_core_multigraph(self):
117
+ core_number = nx.core_number(self.H)
118
+ H = nx.MultiGraph(self.H)
119
+ with pytest.raises(nx.NetworkXNotImplemented):
120
+ nx.k_core(H, k=0, core_number=core_number)
121
+
122
+ def test_main_crust(self):
123
+ main_crust_subgraph = nx.k_crust(self.H)
124
+ assert sorted(main_crust_subgraph.nodes()) == [0, 1, 3]
125
+
126
+ def test_k_crust(self):
127
+ # k = 0
128
+ k_crust_subgraph = nx.k_crust(self.H, k=2)
129
+ assert sorted(k_crust_subgraph.nodes()) == sorted(self.H.nodes())
130
+ # k=1
131
+ k_crust_subgraph = nx.k_crust(self.H, k=1)
132
+ assert sorted(k_crust_subgraph.nodes()) == [0, 1, 3]
133
+ # k=2
134
+ k_crust_subgraph = nx.k_crust(self.H, k=0)
135
+ assert sorted(k_crust_subgraph.nodes()) == [0]
136
+
137
+ def test_k_crust_multigraph(self):
138
+ core_number = nx.core_number(self.H)
139
+ H = nx.MultiGraph(self.H)
140
+ with pytest.raises(nx.NetworkXNotImplemented):
141
+ nx.k_crust(H, k=0, core_number=core_number)
142
+
143
+ def test_main_shell(self):
144
+ main_shell_subgraph = nx.k_shell(self.H)
145
+ assert sorted(main_shell_subgraph.nodes()) == [2, 4, 5, 6]
146
+
147
+ def test_k_shell(self):
148
+ # k=0
149
+ k_shell_subgraph = nx.k_shell(self.H, k=2)
150
+ assert sorted(k_shell_subgraph.nodes()) == [2, 4, 5, 6]
151
+ # k=1
152
+ k_shell_subgraph = nx.k_shell(self.H, k=1)
153
+ assert sorted(k_shell_subgraph.nodes()) == [1, 3]
154
+ # k=2
155
+ k_shell_subgraph = nx.k_shell(self.H, k=0)
156
+ assert sorted(k_shell_subgraph.nodes()) == [0]
157
+
158
+ def test_k_shell_multigraph(self):
159
+ core_number = nx.core_number(self.H)
160
+ H = nx.MultiGraph(self.H)
161
+ with pytest.raises(nx.NetworkXNotImplemented):
162
+ nx.k_shell(H, k=0, core_number=core_number)
163
+
164
+ def test_k_corona(self):
165
+ # k=0
166
+ k_corona_subgraph = nx.k_corona(self.H, k=2)
167
+ assert sorted(k_corona_subgraph.nodes()) == [2, 4, 5, 6]
168
+ # k=1
169
+ k_corona_subgraph = nx.k_corona(self.H, k=1)
170
+ assert sorted(k_corona_subgraph.nodes()) == [1]
171
+ # k=2
172
+ k_corona_subgraph = nx.k_corona(self.H, k=0)
173
+ assert sorted(k_corona_subgraph.nodes()) == [0]
174
+
175
+ def test_k_corona_multigraph(self):
176
+ core_number = nx.core_number(self.H)
177
+ H = nx.MultiGraph(self.H)
178
+ with pytest.raises(nx.NetworkXNotImplemented):
179
+ nx.k_corona(H, k=0, core_number=core_number)
180
+
181
+ def test_k_truss(self):
182
+ # k=-1
183
+ k_truss_subgraph = nx.k_truss(self.G, -1)
184
+ assert sorted(k_truss_subgraph.nodes()) == list(range(1, 21))
185
+ # k=0
186
+ k_truss_subgraph = nx.k_truss(self.G, 0)
187
+ assert sorted(k_truss_subgraph.nodes()) == list(range(1, 21))
188
+ # k=1
189
+ k_truss_subgraph = nx.k_truss(self.G, 1)
190
+ assert sorted(k_truss_subgraph.nodes()) == list(range(1, 21))
191
+ # k=2
192
+ k_truss_subgraph = nx.k_truss(self.G, 2)
193
+ assert sorted(k_truss_subgraph.nodes()) == list(range(1, 21))
194
+ # k=3
195
+ k_truss_subgraph = nx.k_truss(self.G, 3)
196
+ assert sorted(k_truss_subgraph.nodes()) == list(range(1, 13))
197
+
198
+ k_truss_subgraph = nx.k_truss(self.G, 4)
199
+ assert sorted(k_truss_subgraph.nodes()) == list(range(1, 9))
200
+
201
+ k_truss_subgraph = nx.k_truss(self.G, 5)
202
+ assert sorted(k_truss_subgraph.nodes()) == []
203
+
204
+ def test_k_truss_digraph(self):
205
+ G = nx.complete_graph(3)
206
+ G = nx.DiGraph(G)
207
+ G.add_edge(2, 1)
208
+ with pytest.raises(
209
+ nx.NetworkXNotImplemented, match="not implemented for directed type"
210
+ ):
211
+ nx.k_truss(G, k=1)
212
+
213
+ def test_k_truss_multigraph(self):
214
+ G = nx.complete_graph(3)
215
+ G = nx.MultiGraph(G)
216
+ G.add_edge(1, 2)
217
+ with pytest.raises(
218
+ nx.NetworkXNotImplemented, match="not implemented for multigraph type"
219
+ ):
220
+ nx.k_truss(G, k=1)
221
+
222
+ def test_k_truss_self_loop(self):
223
+ G = nx.cycle_graph(3)
224
+ G.add_edge(0, 0)
225
+ with pytest.raises(
226
+ nx.NetworkXNotImplemented, match="Input graph has self loops"
227
+ ):
228
+ nx.k_truss(G, k=1)
229
+
230
+ def test_onion_layers(self):
231
+ layers = nx.onion_layers(self.G)
232
+ nodes_by_layer = [
233
+ sorted(n for n in layers if layers[n] == val) for val in range(1, 7)
234
+ ]
235
+ assert nodes_equal(nodes_by_layer[0], [21])
236
+ assert nodes_equal(nodes_by_layer[1], [17, 18, 19, 20])
237
+ assert nodes_equal(nodes_by_layer[2], [10, 12, 13, 14, 15, 16])
238
+ assert nodes_equal(nodes_by_layer[3], [9, 11])
239
+ assert nodes_equal(nodes_by_layer[4], [1, 2, 4, 5, 6, 8])
240
+ assert nodes_equal(nodes_by_layer[5], [3, 7])
241
+
242
+ def test_onion_digraph(self):
243
+ G = nx.complete_graph(3)
244
+ G = nx.DiGraph(G)
245
+ G.add_edge(2, 1)
246
+ with pytest.raises(
247
+ nx.NetworkXNotImplemented, match="not implemented for directed type"
248
+ ):
249
+ nx.onion_layers(G)
250
+
251
+ def test_onion_multigraph(self):
252
+ G = nx.complete_graph(3)
253
+ G = nx.MultiGraph(G)
254
+ G.add_edge(1, 2)
255
+ with pytest.raises(
256
+ nx.NetworkXNotImplemented, match="not implemented for multigraph type"
257
+ ):
258
+ nx.onion_layers(G)
259
+
260
+ def test_onion_self_loop(self):
261
+ G = nx.cycle_graph(3)
262
+ G.add_edge(0, 0)
263
+ with pytest.raises(
264
+ nx.NetworkXNotImplemented, match="Input graph contains self loops"
265
+ ):
266
+ nx.onion_layers(G)
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_covering.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import networkx as nx
4
+
5
+
6
+ class TestMinEdgeCover:
7
+ """Tests for :func:`networkx.algorithms.min_edge_cover`"""
8
+
9
+ def test_empty_graph(self):
10
+ G = nx.Graph()
11
+ assert nx.min_edge_cover(G) == set()
12
+
13
+ def test_graph_with_loop(self):
14
+ G = nx.Graph()
15
+ G.add_edge(0, 0)
16
+ assert nx.min_edge_cover(G) == {(0, 0)}
17
+
18
+ def test_graph_with_isolated_v(self):
19
+ G = nx.Graph()
20
+ G.add_node(1)
21
+ with pytest.raises(
22
+ nx.NetworkXException,
23
+ match="Graph has a node with no edge incident on it, so no edge cover exists.",
24
+ ):
25
+ nx.min_edge_cover(G)
26
+
27
+ def test_graph_single_edge(self):
28
+ G = nx.Graph([(0, 1)])
29
+ assert nx.min_edge_cover(G) in ({(0, 1)}, {(1, 0)})
30
+
31
+ def test_graph_two_edge_path(self):
32
+ G = nx.path_graph(3)
33
+ min_cover = nx.min_edge_cover(G)
34
+ assert len(min_cover) == 2
35
+ for u, v in G.edges:
36
+ assert (u, v) in min_cover or (v, u) in min_cover
37
+
38
+ def test_bipartite_explicit(self):
39
+ G = nx.Graph()
40
+ G.add_nodes_from([1, 2, 3, 4], bipartite=0)
41
+ G.add_nodes_from(["a", "b", "c"], bipartite=1)
42
+ G.add_edges_from([(1, "a"), (1, "b"), (2, "b"), (2, "c"), (3, "c"), (4, "a")])
43
+ # Use bipartite method by prescribing the algorithm
44
+ min_cover = nx.min_edge_cover(
45
+ G, nx.algorithms.bipartite.matching.eppstein_matching
46
+ )
47
+ assert nx.is_edge_cover(G, min_cover)
48
+ assert len(min_cover) == 8
49
+ # Use the default method which is not specialized for bipartite
50
+ min_cover2 = nx.min_edge_cover(G)
51
+ assert nx.is_edge_cover(G, min_cover2)
52
+ assert len(min_cover2) == 4
53
+
54
+ def test_complete_graph_even(self):
55
+ G = nx.complete_graph(10)
56
+ min_cover = nx.min_edge_cover(G)
57
+ assert nx.is_edge_cover(G, min_cover)
58
+ assert len(min_cover) == 5
59
+
60
+ def test_complete_graph_odd(self):
61
+ G = nx.complete_graph(11)
62
+ min_cover = nx.min_edge_cover(G)
63
+ assert nx.is_edge_cover(G, min_cover)
64
+ assert len(min_cover) == 6
65
+
66
+
67
+ class TestIsEdgeCover:
68
+ """Tests for :func:`networkx.algorithms.is_edge_cover`"""
69
+
70
+ def test_empty_graph(self):
71
+ G = nx.Graph()
72
+ assert nx.is_edge_cover(G, set())
73
+
74
+ def test_graph_with_loop(self):
75
+ G = nx.Graph()
76
+ G.add_edge(1, 1)
77
+ assert nx.is_edge_cover(G, {(1, 1)})
78
+
79
+ def test_graph_single_edge(self):
80
+ G = nx.Graph()
81
+ G.add_edge(0, 1)
82
+ assert nx.is_edge_cover(G, {(0, 0), (1, 1)})
83
+ assert nx.is_edge_cover(G, {(0, 1), (1, 0)})
84
+ assert nx.is_edge_cover(G, {(0, 1)})
85
+ assert not nx.is_edge_cover(G, {(0, 0)})
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_cuts.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Unit tests for the :mod:`networkx.algorithms.cuts` module."""
2
+
3
+ import networkx as nx
4
+
5
+
6
+ class TestCutSize:
7
+ """Unit tests for the :func:`~networkx.cut_size` function."""
8
+
9
+ def test_symmetric(self):
10
+ """Tests that the cut size is symmetric."""
11
+ G = nx.barbell_graph(3, 0)
12
+ S = {0, 1, 4}
13
+ T = {2, 3, 5}
14
+ assert nx.cut_size(G, S, T) == 4
15
+ assert nx.cut_size(G, T, S) == 4
16
+
17
+ def test_single_edge(self):
18
+ """Tests for a cut of a single edge."""
19
+ G = nx.barbell_graph(3, 0)
20
+ S = {0, 1, 2}
21
+ T = {3, 4, 5}
22
+ assert nx.cut_size(G, S, T) == 1
23
+ assert nx.cut_size(G, T, S) == 1
24
+
25
+ def test_directed(self):
26
+ """Tests that each directed edge is counted once in the cut."""
27
+ G = nx.barbell_graph(3, 0).to_directed()
28
+ S = {0, 1, 2}
29
+ T = {3, 4, 5}
30
+ assert nx.cut_size(G, S, T) == 2
31
+ assert nx.cut_size(G, T, S) == 2
32
+
33
+ def test_directed_symmetric(self):
34
+ """Tests that a cut in a directed graph is symmetric."""
35
+ G = nx.barbell_graph(3, 0).to_directed()
36
+ S = {0, 1, 4}
37
+ T = {2, 3, 5}
38
+ assert nx.cut_size(G, S, T) == 8
39
+ assert nx.cut_size(G, T, S) == 8
40
+
41
+ def test_multigraph(self):
42
+ """Tests that parallel edges are each counted for a cut."""
43
+ G = nx.MultiGraph(["ab", "ab"])
44
+ assert nx.cut_size(G, {"a"}, {"b"}) == 2
45
+
46
+
47
+ class TestVolume:
48
+ """Unit tests for the :func:`~networkx.volume` function."""
49
+
50
+ def test_graph(self):
51
+ G = nx.cycle_graph(4)
52
+ assert nx.volume(G, {0, 1}) == 4
53
+
54
+ def test_digraph(self):
55
+ G = nx.DiGraph([(0, 1), (1, 2), (2, 3), (3, 0)])
56
+ assert nx.volume(G, {0, 1}) == 2
57
+
58
+ def test_multigraph(self):
59
+ edges = list(nx.cycle_graph(4).edges())
60
+ G = nx.MultiGraph(edges * 2)
61
+ assert nx.volume(G, {0, 1}) == 8
62
+
63
+ def test_multidigraph(self):
64
+ edges = [(0, 1), (1, 2), (2, 3), (3, 0)]
65
+ G = nx.MultiDiGraph(edges * 2)
66
+ assert nx.volume(G, {0, 1}) == 4
67
+
68
+ def test_barbell(self):
69
+ G = nx.barbell_graph(3, 0)
70
+ assert nx.volume(G, {0, 1, 2}) == 7
71
+ assert nx.volume(G, {3, 4, 5}) == 7
72
+
73
+
74
+ class TestNormalizedCutSize:
75
+ """Unit tests for the :func:`~networkx.normalized_cut_size` function."""
76
+
77
+ def test_graph(self):
78
+ G = nx.path_graph(4)
79
+ S = {1, 2}
80
+ T = set(G) - S
81
+ size = nx.normalized_cut_size(G, S, T)
82
+ # The cut looks like this: o-{-o--o-}-o
83
+ expected = 2 * ((1 / 4) + (1 / 2))
84
+ assert expected == size
85
+ # Test with no input T
86
+ assert expected == nx.normalized_cut_size(G, S)
87
+
88
+ def test_directed(self):
89
+ G = nx.DiGraph([(0, 1), (1, 2), (2, 3)])
90
+ S = {1, 2}
91
+ T = set(G) - S
92
+ size = nx.normalized_cut_size(G, S, T)
93
+ # The cut looks like this: o-{->o-->o-}->o
94
+ expected = 2 * ((1 / 2) + (1 / 1))
95
+ assert expected == size
96
+ # Test with no input T
97
+ assert expected == nx.normalized_cut_size(G, S)
98
+
99
+
100
+ class TestConductance:
101
+ """Unit tests for the :func:`~networkx.conductance` function."""
102
+
103
+ def test_graph(self):
104
+ G = nx.barbell_graph(5, 0)
105
+ # Consider the singleton sets containing the "bridge" nodes.
106
+ # There is only one cut edge, and each set has volume five.
107
+ S = {4}
108
+ T = {5}
109
+ conductance = nx.conductance(G, S, T)
110
+ expected = 1 / 5
111
+ assert expected == conductance
112
+ # Test with no input T
113
+ G2 = nx.barbell_graph(3, 0)
114
+ # There is only one cut edge, and each set has volume seven.
115
+ S2 = {0, 1, 2}
116
+ assert nx.conductance(G2, S2) == 1 / 7
117
+
118
+
119
+ class TestEdgeExpansion:
120
+ """Unit tests for the :func:`~networkx.edge_expansion` function."""
121
+
122
+ def test_graph(self):
123
+ G = nx.barbell_graph(5, 0)
124
+ S = set(range(5))
125
+ T = set(G) - S
126
+ expansion = nx.edge_expansion(G, S, T)
127
+ expected = 1 / 5
128
+ assert expected == expansion
129
+ # Test with no input T
130
+ assert expected == nx.edge_expansion(G, S)
131
+
132
+
133
+ class TestNodeExpansion:
134
+ """Unit tests for the :func:`~networkx.node_expansion` function."""
135
+
136
+ def test_graph(self):
137
+ G = nx.path_graph(8)
138
+ S = {3, 4, 5}
139
+ expansion = nx.node_expansion(G, S)
140
+ # The neighborhood of S has cardinality five, and S has
141
+ # cardinality three.
142
+ expected = 5 / 3
143
+ assert expected == expansion
144
+
145
+
146
+ class TestBoundaryExpansion:
147
+ """Unit tests for the :func:`~networkx.boundary_expansion` function."""
148
+
149
+ def test_graph(self):
150
+ G = nx.complete_graph(10)
151
+ S = set(range(4))
152
+ expansion = nx.boundary_expansion(G, S)
153
+ # The node boundary of S has cardinality six, and S has
154
+ # cardinality three.
155
+ expected = 6 / 4
156
+ assert expected == expansion
157
+
158
+
159
+ class TestMixingExpansion:
160
+ """Unit tests for the :func:`~networkx.mixing_expansion` function."""
161
+
162
+ def test_graph(self):
163
+ G = nx.barbell_graph(5, 0)
164
+ S = set(range(5))
165
+ T = set(G) - S
166
+ expansion = nx.mixing_expansion(G, S, T)
167
+ # There is one cut edge, and the total number of edges in the
168
+ # graph is twice the total number of edges in a clique of size
169
+ # five, plus one more for the bridge.
170
+ expected = 1 / (2 * (5 * 4 + 1))
171
+ assert expected == expansion
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_cycles.py ADDED
@@ -0,0 +1,984 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from itertools import chain, islice, tee
3
+ from math import inf
4
+
5
+ import pytest
6
+
7
+ import networkx as nx
8
+ from networkx.algorithms.traversal.edgedfs import FORWARD, REVERSE
9
+
10
+
11
+ def check_independent(basis):
12
+ if len(basis) == 0:
13
+ return
14
+
15
+ np = pytest.importorskip("numpy")
16
+ sp = pytest.importorskip("scipy") # Required by incidence_matrix
17
+
18
+ H = nx.Graph()
19
+ for b in basis:
20
+ nx.add_cycle(H, b)
21
+ inc = nx.incidence_matrix(H, oriented=True)
22
+ rank = np.linalg.matrix_rank(inc.toarray(), tol=None, hermitian=False)
23
+ assert inc.shape[1] - rank == len(basis)
24
+
25
+
26
+ class TestCycles:
27
+ @classmethod
28
+ def setup_class(cls):
29
+ G = nx.Graph()
30
+ nx.add_cycle(G, [0, 1, 2, 3])
31
+ nx.add_cycle(G, [0, 3, 4, 5])
32
+ nx.add_cycle(G, [0, 1, 6, 7, 8])
33
+ G.add_edge(8, 9)
34
+ cls.G = G
35
+
36
+ def is_cyclic_permutation(self, a, b):
37
+ n = len(a)
38
+ if len(b) != n:
39
+ return False
40
+ l = a + a
41
+ return any(l[i : i + n] == b for i in range(n))
42
+
43
+ def test_cycle_basis(self):
44
+ G = self.G
45
+ cy = nx.cycle_basis(G, 0)
46
+ sort_cy = sorted(sorted(c) for c in cy)
47
+ assert sort_cy == [[0, 1, 2, 3], [0, 1, 6, 7, 8], [0, 3, 4, 5]]
48
+ cy = nx.cycle_basis(G, 1)
49
+ sort_cy = sorted(sorted(c) for c in cy)
50
+ assert sort_cy == [[0, 1, 2, 3], [0, 1, 6, 7, 8], [0, 3, 4, 5]]
51
+ cy = nx.cycle_basis(G, 9)
52
+ sort_cy = sorted(sorted(c) for c in cy)
53
+ assert sort_cy == [[0, 1, 2, 3], [0, 1, 6, 7, 8], [0, 3, 4, 5]]
54
+ # test disconnected graphs
55
+ nx.add_cycle(G, "ABC")
56
+ cy = nx.cycle_basis(G, 9)
57
+ sort_cy = sorted(sorted(c) for c in cy[:-1]) + [sorted(cy[-1])]
58
+ assert sort_cy == [[0, 1, 2, 3], [0, 1, 6, 7, 8], [0, 3, 4, 5], ["A", "B", "C"]]
59
+
60
+ def test_cycle_basis2(self):
61
+ with pytest.raises(nx.NetworkXNotImplemented):
62
+ G = nx.DiGraph()
63
+ cy = nx.cycle_basis(G, 0)
64
+
65
+ def test_cycle_basis3(self):
66
+ with pytest.raises(nx.NetworkXNotImplemented):
67
+ G = nx.MultiGraph()
68
+ cy = nx.cycle_basis(G, 0)
69
+
70
+ def test_cycle_basis_ordered(self):
71
+ # see gh-6654 replace sets with (ordered) dicts
72
+ G = nx.cycle_graph(5)
73
+ G.update(nx.cycle_graph(range(3, 8)))
74
+ cbG = nx.cycle_basis(G)
75
+
76
+ perm = {1: 0, 0: 1} # switch 0 and 1
77
+ H = nx.relabel_nodes(G, perm)
78
+ cbH = [[perm.get(n, n) for n in cyc] for cyc in nx.cycle_basis(H)]
79
+ assert cbG == cbH
80
+
81
+ def test_cycle_basis_self_loop(self):
82
+ """Tests the function for graphs with self loops"""
83
+ G = nx.Graph()
84
+ nx.add_cycle(G, [0, 1, 2, 3])
85
+ nx.add_cycle(G, [0, 0, 6, 2])
86
+ cy = nx.cycle_basis(G)
87
+ sort_cy = sorted(sorted(c) for c in cy)
88
+ assert sort_cy == [[0], [0, 1, 2], [0, 2, 3], [0, 2, 6]]
89
+
90
+ def test_simple_cycles(self):
91
+ edges = [(0, 0), (0, 1), (0, 2), (1, 2), (2, 0), (2, 1), (2, 2)]
92
+ G = nx.DiGraph(edges)
93
+ cc = sorted(nx.simple_cycles(G))
94
+ ca = [[0], [0, 1, 2], [0, 2], [1, 2], [2]]
95
+ assert len(cc) == len(ca)
96
+ for c in cc:
97
+ assert any(self.is_cyclic_permutation(c, rc) for rc in ca)
98
+
99
+ def test_simple_cycles_singleton(self):
100
+ G = nx.Graph([(0, 0)]) # self-loop
101
+ assert list(nx.simple_cycles(G)) == [[0]]
102
+
103
+ def test_unsortable(self):
104
+ # this test ensures that graphs whose nodes without an intrinsic
105
+ # ordering do not cause issues
106
+ G = nx.DiGraph()
107
+ nx.add_cycle(G, ["a", 1])
108
+ c = list(nx.simple_cycles(G))
109
+ assert len(c) == 1
110
+
111
+ def test_simple_cycles_small(self):
112
+ G = nx.DiGraph()
113
+ nx.add_cycle(G, [1, 2, 3])
114
+ c = sorted(nx.simple_cycles(G))
115
+ assert len(c) == 1
116
+ assert self.is_cyclic_permutation(c[0], [1, 2, 3])
117
+ nx.add_cycle(G, [10, 20, 30])
118
+ cc = sorted(nx.simple_cycles(G))
119
+ assert len(cc) == 2
120
+ ca = [[1, 2, 3], [10, 20, 30]]
121
+ for c in cc:
122
+ assert any(self.is_cyclic_permutation(c, rc) for rc in ca)
123
+
124
+ def test_simple_cycles_empty(self):
125
+ G = nx.DiGraph()
126
+ assert list(nx.simple_cycles(G)) == []
127
+
128
+ def worst_case_graph(self, k):
129
+ # see figure 1 in Johnson's paper
130
+ # this graph has exactly 3k simple cycles
131
+ G = nx.DiGraph()
132
+ for n in range(2, k + 2):
133
+ G.add_edge(1, n)
134
+ G.add_edge(n, k + 2)
135
+ G.add_edge(2 * k + 1, 1)
136
+ for n in range(k + 2, 2 * k + 2):
137
+ G.add_edge(n, 2 * k + 2)
138
+ G.add_edge(n, n + 1)
139
+ G.add_edge(2 * k + 3, k + 2)
140
+ for n in range(2 * k + 3, 3 * k + 3):
141
+ G.add_edge(2 * k + 2, n)
142
+ G.add_edge(n, 3 * k + 3)
143
+ G.add_edge(3 * k + 3, 2 * k + 2)
144
+ return G
145
+
146
+ def test_worst_case_graph(self):
147
+ # see figure 1 in Johnson's paper
148
+ for k in range(3, 10):
149
+ G = self.worst_case_graph(k)
150
+ l = len(list(nx.simple_cycles(G)))
151
+ assert l == 3 * k
152
+
153
+ def test_recursive_simple_and_not(self):
154
+ for k in range(2, 10):
155
+ G = self.worst_case_graph(k)
156
+ cc = sorted(nx.simple_cycles(G))
157
+ rcc = sorted(nx.recursive_simple_cycles(G))
158
+ assert len(cc) == len(rcc)
159
+ for c in cc:
160
+ assert any(self.is_cyclic_permutation(c, r) for r in rcc)
161
+ for rc in rcc:
162
+ assert any(self.is_cyclic_permutation(rc, c) for c in cc)
163
+
164
+ def test_simple_graph_with_reported_bug(self):
165
+ G = nx.DiGraph()
166
+ edges = [
167
+ (0, 2),
168
+ (0, 3),
169
+ (1, 0),
170
+ (1, 3),
171
+ (2, 1),
172
+ (2, 4),
173
+ (3, 2),
174
+ (3, 4),
175
+ (4, 0),
176
+ (4, 1),
177
+ (4, 5),
178
+ (5, 0),
179
+ (5, 1),
180
+ (5, 2),
181
+ (5, 3),
182
+ ]
183
+ G.add_edges_from(edges)
184
+ cc = sorted(nx.simple_cycles(G))
185
+ assert len(cc) == 26
186
+ rcc = sorted(nx.recursive_simple_cycles(G))
187
+ assert len(cc) == len(rcc)
188
+ for c in cc:
189
+ assert any(self.is_cyclic_permutation(c, rc) for rc in rcc)
190
+ for rc in rcc:
191
+ assert any(self.is_cyclic_permutation(rc, c) for c in cc)
192
+
193
+
194
+ def pairwise(iterable):
195
+ a, b = tee(iterable)
196
+ next(b, None)
197
+ return zip(a, b)
198
+
199
+
200
+ def cycle_edges(c):
201
+ return pairwise(chain(c, islice(c, 1)))
202
+
203
+
204
+ def directed_cycle_edgeset(c):
205
+ return frozenset(cycle_edges(c))
206
+
207
+
208
+ def undirected_cycle_edgeset(c):
209
+ if len(c) == 1:
210
+ return frozenset(cycle_edges(c))
211
+ return frozenset(map(frozenset, cycle_edges(c)))
212
+
213
+
214
+ def multigraph_cycle_edgeset(c):
215
+ if len(c) <= 2:
216
+ return frozenset(cycle_edges(c))
217
+ else:
218
+ return frozenset(map(frozenset, cycle_edges(c)))
219
+
220
+
221
+ class TestCycleEnumeration:
222
+ @staticmethod
223
+ def K(n):
224
+ return nx.complete_graph(n)
225
+
226
+ @staticmethod
227
+ def D(n):
228
+ return nx.complete_graph(n).to_directed()
229
+
230
+ @staticmethod
231
+ def edgeset_function(g):
232
+ if g.is_directed():
233
+ return directed_cycle_edgeset
234
+ elif g.is_multigraph():
235
+ return multigraph_cycle_edgeset
236
+ else:
237
+ return undirected_cycle_edgeset
238
+
239
+ def check_cycle(self, g, c, es, cache, source, original_c, length_bound, chordless):
240
+ if length_bound is not None and len(c) > length_bound:
241
+ raise RuntimeError(
242
+ f"computed cycle {original_c} exceeds length bound {length_bound}"
243
+ )
244
+ if source == "computed":
245
+ if es in cache:
246
+ raise RuntimeError(
247
+ f"computed cycle {original_c} has already been found!"
248
+ )
249
+ else:
250
+ cache[es] = tuple(original_c)
251
+ else:
252
+ if es in cache:
253
+ cache.pop(es)
254
+ else:
255
+ raise RuntimeError(f"expected cycle {original_c} was not computed")
256
+
257
+ if not all(g.has_edge(*e) for e in es):
258
+ raise RuntimeError(
259
+ f"{source} claimed cycle {original_c} is not a cycle of g"
260
+ )
261
+ if chordless and len(g.subgraph(c).edges) > len(c):
262
+ raise RuntimeError(f"{source} cycle {original_c} is not chordless")
263
+
264
+ def check_cycle_algorithm(
265
+ self,
266
+ g,
267
+ expected_cycles,
268
+ length_bound=None,
269
+ chordless=False,
270
+ algorithm=None,
271
+ ):
272
+ if algorithm is None:
273
+ algorithm = nx.chordless_cycles if chordless else nx.simple_cycles
274
+
275
+ # note: we shuffle the labels of g to rule out accidentally-correct
276
+ # behavior which occurred during the development of chordless cycle
277
+ # enumeration algorithms
278
+
279
+ relabel = list(range(len(g)))
280
+ rng = random.Random(42)
281
+ rng.shuffle(relabel)
282
+ label = dict(zip(g, relabel))
283
+ unlabel = dict(zip(relabel, g))
284
+ h = nx.relabel_nodes(g, label, copy=True)
285
+
286
+ edgeset = self.edgeset_function(h)
287
+
288
+ params = {}
289
+ if length_bound is not None:
290
+ params["length_bound"] = length_bound
291
+
292
+ cycle_cache = {}
293
+ for c in algorithm(h, **params):
294
+ original_c = [unlabel[x] for x in c]
295
+ es = edgeset(c)
296
+ self.check_cycle(
297
+ h, c, es, cycle_cache, "computed", original_c, length_bound, chordless
298
+ )
299
+
300
+ if isinstance(expected_cycles, int):
301
+ if len(cycle_cache) != expected_cycles:
302
+ raise RuntimeError(
303
+ f"expected {expected_cycles} cycles, got {len(cycle_cache)}"
304
+ )
305
+ return
306
+ for original_c in expected_cycles:
307
+ c = [label[x] for x in original_c]
308
+ es = edgeset(c)
309
+ self.check_cycle(
310
+ h, c, es, cycle_cache, "expected", original_c, length_bound, chordless
311
+ )
312
+
313
+ if len(cycle_cache):
314
+ for c in cycle_cache.values():
315
+ raise RuntimeError(
316
+ f"computed cycle {c} is valid but not in the expected cycle set!"
317
+ )
318
+
319
+ def check_cycle_enumeration_integer_sequence(
320
+ self,
321
+ g_family,
322
+ cycle_counts,
323
+ length_bound=None,
324
+ chordless=False,
325
+ algorithm=None,
326
+ ):
327
+ for g, num_cycles in zip(g_family, cycle_counts):
328
+ self.check_cycle_algorithm(
329
+ g,
330
+ num_cycles,
331
+ length_bound=length_bound,
332
+ chordless=chordless,
333
+ algorithm=algorithm,
334
+ )
335
+
336
+ def test_directed_chordless_cycle_digons(self):
337
+ g = nx.DiGraph()
338
+ nx.add_cycle(g, range(5))
339
+ nx.add_cycle(g, range(5)[::-1])
340
+ g.add_edge(0, 0)
341
+ expected_cycles = [(0,), (1, 2), (2, 3), (3, 4)]
342
+ self.check_cycle_algorithm(g, expected_cycles, chordless=True)
343
+
344
+ self.check_cycle_algorithm(g, expected_cycles, chordless=True, length_bound=2)
345
+
346
+ expected_cycles = [c for c in expected_cycles if len(c) < 2]
347
+ self.check_cycle_algorithm(g, expected_cycles, chordless=True, length_bound=1)
348
+
349
+ def test_chordless_cycles_multigraph_self_loops(self):
350
+ G = nx.MultiGraph([(1, 1), (2, 2), (1, 2), (1, 2)])
351
+ expected_cycles = [[1], [2]]
352
+ self.check_cycle_algorithm(G, expected_cycles, chordless=True)
353
+
354
+ G.add_edges_from([(2, 3), (3, 4), (3, 4), (1, 3)])
355
+ expected_cycles = [[1], [2], [3, 4]]
356
+ self.check_cycle_algorithm(G, expected_cycles, chordless=True)
357
+
358
+ def test_directed_chordless_cycle_undirected(self):
359
+ g = nx.DiGraph([(1, 2), (2, 3), (3, 4), (4, 5), (5, 0), (5, 1), (0, 2)])
360
+ expected_cycles = [(0, 2, 3, 4, 5), (1, 2, 3, 4, 5)]
361
+ self.check_cycle_algorithm(g, expected_cycles, chordless=True)
362
+
363
+ g = nx.DiGraph()
364
+ nx.add_cycle(g, range(5))
365
+ nx.add_cycle(g, range(4, 9))
366
+ g.add_edge(7, 3)
367
+ expected_cycles = [(0, 1, 2, 3, 4), (3, 4, 5, 6, 7), (4, 5, 6, 7, 8)]
368
+ self.check_cycle_algorithm(g, expected_cycles, chordless=True)
369
+
370
+ g.add_edge(3, 7)
371
+ expected_cycles = [(0, 1, 2, 3, 4), (3, 7), (4, 5, 6, 7, 8)]
372
+ self.check_cycle_algorithm(g, expected_cycles, chordless=True)
373
+
374
+ expected_cycles = [(3, 7)]
375
+ self.check_cycle_algorithm(g, expected_cycles, chordless=True, length_bound=4)
376
+
377
+ g.remove_edge(7, 3)
378
+ expected_cycles = [(0, 1, 2, 3, 4), (4, 5, 6, 7, 8)]
379
+ self.check_cycle_algorithm(g, expected_cycles, chordless=True)
380
+
381
+ g = nx.DiGraph((i, j) for i in range(10) for j in range(i))
382
+ expected_cycles = []
383
+ self.check_cycle_algorithm(g, expected_cycles, chordless=True)
384
+
385
+ def test_chordless_cycles_directed(self):
386
+ G = nx.DiGraph()
387
+ nx.add_cycle(G, range(5))
388
+ nx.add_cycle(G, range(4, 12))
389
+ expected = [[*range(5)], [*range(4, 12)]]
390
+ self.check_cycle_algorithm(G, expected, chordless=True)
391
+ self.check_cycle_algorithm(
392
+ G, [c for c in expected if len(c) <= 5], length_bound=5, chordless=True
393
+ )
394
+
395
+ G.add_edge(7, 3)
396
+ expected.append([*range(3, 8)])
397
+ self.check_cycle_algorithm(G, expected, chordless=True)
398
+ self.check_cycle_algorithm(
399
+ G, [c for c in expected if len(c) <= 5], length_bound=5, chordless=True
400
+ )
401
+
402
+ G.add_edge(3, 7)
403
+ expected[-1] = [7, 3]
404
+ self.check_cycle_algorithm(G, expected, chordless=True)
405
+ self.check_cycle_algorithm(
406
+ G, [c for c in expected if len(c) <= 5], length_bound=5, chordless=True
407
+ )
408
+
409
+ expected.pop()
410
+ G.remove_edge(7, 3)
411
+ self.check_cycle_algorithm(G, expected, chordless=True)
412
+ self.check_cycle_algorithm(
413
+ G, [c for c in expected if len(c) <= 5], length_bound=5, chordless=True
414
+ )
415
+
416
+ def test_directed_chordless_cycle_diclique(self):
417
+ g_family = [self.D(n) for n in range(10)]
418
+ expected_cycles = [(n * n - n) // 2 for n in range(10)]
419
+ self.check_cycle_enumeration_integer_sequence(
420
+ g_family, expected_cycles, chordless=True
421
+ )
422
+
423
+ expected_cycles = [(n * n - n) // 2 for n in range(10)]
424
+ self.check_cycle_enumeration_integer_sequence(
425
+ g_family, expected_cycles, length_bound=2
426
+ )
427
+
428
+ def test_directed_chordless_loop_blockade(self):
429
+ g = nx.DiGraph((i, i) for i in range(10))
430
+ nx.add_cycle(g, range(10))
431
+ expected_cycles = [(i,) for i in range(10)]
432
+ self.check_cycle_algorithm(g, expected_cycles, chordless=True)
433
+
434
+ self.check_cycle_algorithm(g, expected_cycles, length_bound=1)
435
+
436
+ g = nx.MultiDiGraph(g)
437
+ g.add_edges_from((i, i) for i in range(0, 10, 2))
438
+ expected_cycles = [(i,) for i in range(1, 10, 2)]
439
+ self.check_cycle_algorithm(g, expected_cycles, chordless=True)
440
+
441
+ def test_simple_cycles_notable_clique_sequences(self):
442
+ # A000292: Number of labeled graphs on n+3 nodes that are triangles.
443
+ g_family = [self.K(n) for n in range(2, 12)]
444
+ expected = [0, 1, 4, 10, 20, 35, 56, 84, 120, 165, 220]
445
+ self.check_cycle_enumeration_integer_sequence(
446
+ g_family, expected, length_bound=3
447
+ )
448
+
449
+ def triangles(g, **kwargs):
450
+ yield from (c for c in nx.simple_cycles(g, **kwargs) if len(c) == 3)
451
+
452
+ # directed complete graphs have twice as many triangles thanks to reversal
453
+ g_family = [self.D(n) for n in range(2, 12)]
454
+ expected = [2 * e for e in expected]
455
+ self.check_cycle_enumeration_integer_sequence(
456
+ g_family, expected, length_bound=3, algorithm=triangles
457
+ )
458
+
459
+ def four_cycles(g, **kwargs):
460
+ yield from (c for c in nx.simple_cycles(g, **kwargs) if len(c) == 4)
461
+
462
+ # A050534: the number of 4-cycles in the complete graph K_{n+1}
463
+ expected = [0, 0, 0, 3, 15, 45, 105, 210, 378, 630, 990]
464
+ g_family = [self.K(n) for n in range(1, 12)]
465
+ self.check_cycle_enumeration_integer_sequence(
466
+ g_family, expected, length_bound=4, algorithm=four_cycles
467
+ )
468
+
469
+ # directed complete graphs have twice as many 4-cycles thanks to reversal
470
+ expected = [2 * e for e in expected]
471
+ g_family = [self.D(n) for n in range(1, 15)]
472
+ self.check_cycle_enumeration_integer_sequence(
473
+ g_family, expected, length_bound=4, algorithm=four_cycles
474
+ )
475
+
476
+ # A006231: the number of elementary circuits in a complete directed graph with n nodes
477
+ expected = [0, 1, 5, 20, 84, 409, 2365]
478
+ g_family = [self.D(n) for n in range(1, 8)]
479
+ self.check_cycle_enumeration_integer_sequence(g_family, expected)
480
+
481
+ # A002807: Number of cycles in the complete graph on n nodes K_{n}.
482
+ expected = [0, 0, 0, 1, 7, 37, 197, 1172]
483
+ g_family = [self.K(n) for n in range(8)]
484
+ self.check_cycle_enumeration_integer_sequence(g_family, expected)
485
+
486
+ def test_directed_chordless_cycle_parallel_multiedges(self):
487
+ g = nx.MultiGraph()
488
+
489
+ nx.add_cycle(g, range(5))
490
+ expected = [[*range(5)]]
491
+ self.check_cycle_algorithm(g, expected, chordless=True)
492
+
493
+ nx.add_cycle(g, range(5))
494
+ expected = [*cycle_edges(range(5))]
495
+ self.check_cycle_algorithm(g, expected, chordless=True)
496
+
497
+ nx.add_cycle(g, range(5))
498
+ expected = []
499
+ self.check_cycle_algorithm(g, expected, chordless=True)
500
+
501
+ g = nx.MultiDiGraph()
502
+
503
+ nx.add_cycle(g, range(5))
504
+ expected = [[*range(5)]]
505
+ self.check_cycle_algorithm(g, expected, chordless=True)
506
+
507
+ nx.add_cycle(g, range(5))
508
+ self.check_cycle_algorithm(g, [], chordless=True)
509
+
510
+ nx.add_cycle(g, range(5))
511
+ self.check_cycle_algorithm(g, [], chordless=True)
512
+
513
+ g = nx.MultiDiGraph()
514
+
515
+ nx.add_cycle(g, range(5))
516
+ nx.add_cycle(g, range(5)[::-1])
517
+ expected = [*cycle_edges(range(5))]
518
+ self.check_cycle_algorithm(g, expected, chordless=True)
519
+
520
+ nx.add_cycle(g, range(5))
521
+ self.check_cycle_algorithm(g, [], chordless=True)
522
+
523
+ def test_chordless_cycles_graph(self):
524
+ G = nx.Graph()
525
+ nx.add_cycle(G, range(5))
526
+ nx.add_cycle(G, range(4, 12))
527
+ expected = [[*range(5)], [*range(4, 12)]]
528
+ self.check_cycle_algorithm(G, expected, chordless=True)
529
+ self.check_cycle_algorithm(
530
+ G, [c for c in expected if len(c) <= 5], length_bound=5, chordless=True
531
+ )
532
+
533
+ G.add_edge(7, 3)
534
+ expected.append([*range(3, 8)])
535
+ expected.append([4, 3, 7, 8, 9, 10, 11])
536
+ self.check_cycle_algorithm(G, expected, chordless=True)
537
+ self.check_cycle_algorithm(
538
+ G, [c for c in expected if len(c) <= 5], length_bound=5, chordless=True
539
+ )
540
+
541
+ def test_chordless_cycles_giant_hamiltonian(self):
542
+ # ... o - e - o - e - o ... # o = odd, e = even
543
+ # ... ---/ \-----/ \--- ... # <-- "long" edges
544
+ #
545
+ # each long edge belongs to exactly one triangle, and one giant cycle
546
+ # of length n/2. The remaining edges each belong to a triangle
547
+
548
+ n = 1000
549
+ assert n % 2 == 0
550
+ G = nx.Graph()
551
+ for v in range(n):
552
+ if not v % 2:
553
+ G.add_edge(v, (v + 2) % n)
554
+ G.add_edge(v, (v + 1) % n)
555
+
556
+ expected = [[*range(0, n, 2)]] + [
557
+ [x % n for x in range(i, i + 3)] for i in range(0, n, 2)
558
+ ]
559
+ self.check_cycle_algorithm(G, expected, chordless=True)
560
+ self.check_cycle_algorithm(
561
+ G, [c for c in expected if len(c) <= 3], length_bound=3, chordless=True
562
+ )
563
+
564
+ # ... o -> e -> o -> e -> o ... # o = odd, e = even
565
+ # ... <---/ \---<---/ \---< ... # <-- "long" edges
566
+ #
567
+ # this time, we orient the short and long edges in opposition
568
+ # the cycle structure of this graph is the same, but we need to reverse
569
+ # the long one in our representation. Also, we need to drop the size
570
+ # because our partitioning algorithm uses strongly connected components
571
+ # instead of separating graphs by their strong articulation points
572
+
573
+ n = 100
574
+ assert n % 2 == 0
575
+ G = nx.DiGraph()
576
+ for v in range(n):
577
+ G.add_edge(v, (v + 1) % n)
578
+ if not v % 2:
579
+ G.add_edge((v + 2) % n, v)
580
+
581
+ expected = [[*range(n - 2, -2, -2)]] + [
582
+ [x % n for x in range(i, i + 3)] for i in range(0, n, 2)
583
+ ]
584
+ self.check_cycle_algorithm(G, expected, chordless=True)
585
+ self.check_cycle_algorithm(
586
+ G, [c for c in expected if len(c) <= 3], length_bound=3, chordless=True
587
+ )
588
+
589
+ def test_simple_cycles_acyclic_tournament(self):
590
+ n = 10
591
+ G = nx.DiGraph((x, y) for x in range(n) for y in range(x))
592
+ self.check_cycle_algorithm(G, [])
593
+ self.check_cycle_algorithm(G, [], chordless=True)
594
+
595
+ for k in range(n + 1):
596
+ self.check_cycle_algorithm(G, [], length_bound=k)
597
+ self.check_cycle_algorithm(G, [], length_bound=k, chordless=True)
598
+
599
+ def test_simple_cycles_graph(self):
600
+ testG = nx.cycle_graph(8)
601
+ cyc1 = tuple(range(8))
602
+ self.check_cycle_algorithm(testG, [cyc1])
603
+
604
+ testG.add_edge(4, -1)
605
+ nx.add_path(testG, [3, -2, -3, -4])
606
+ self.check_cycle_algorithm(testG, [cyc1])
607
+
608
+ testG.update(nx.cycle_graph(range(8, 16)))
609
+ cyc2 = tuple(range(8, 16))
610
+ self.check_cycle_algorithm(testG, [cyc1, cyc2])
611
+
612
+ testG.update(nx.cycle_graph(range(4, 12)))
613
+ cyc3 = tuple(range(4, 12))
614
+ expected = {
615
+ (0, 1, 2, 3, 4, 5, 6, 7), # cyc1
616
+ (8, 9, 10, 11, 12, 13, 14, 15), # cyc2
617
+ (4, 5, 6, 7, 8, 9, 10, 11), # cyc3
618
+ (4, 5, 6, 7, 8, 15, 14, 13, 12, 11), # cyc2 + cyc3
619
+ (0, 1, 2, 3, 4, 11, 10, 9, 8, 7), # cyc1 + cyc3
620
+ (0, 1, 2, 3, 4, 11, 12, 13, 14, 15, 8, 7), # cyc1 + cyc2 + cyc3
621
+ }
622
+ self.check_cycle_algorithm(testG, expected)
623
+ assert len(expected) == (2**3 - 1) - 1 # 1 disjoint comb: cyc1 + cyc2
624
+
625
+ # Basis size = 5 (2 loops overlapping gives 5 small loops
626
+ # E
627
+ # / \ Note: A-F = 10-15
628
+ # 1-2-3-4-5
629
+ # / | | \ cyc1=012DAB -- left
630
+ # 0 D F 6 cyc2=234E -- top
631
+ # \ | | / cyc3=45678F -- right
632
+ # B-A-9-8-7 cyc4=89AC -- bottom
633
+ # \ / cyc5=234F89AD -- middle
634
+ # C
635
+ #
636
+ # combinations of 5 basis elements: 2^5 - 1 (one includes no cycles)
637
+ #
638
+ # disjoint combs: (11 total) not simple cycles
639
+ # Any pair not including cyc5 => choose(4, 2) = 6
640
+ # Any triple not including cyc5 => choose(4, 3) = 4
641
+ # Any quad not including cyc5 => choose(4, 4) = 1
642
+ #
643
+ # we expect 31 - 11 = 20 simple cycles
644
+ #
645
+ testG = nx.cycle_graph(12)
646
+ testG.update(nx.cycle_graph([12, 10, 13, 2, 14, 4, 15, 8]).edges)
647
+ expected = (2**5 - 1) - 11 # 11 disjoint combinations
648
+ self.check_cycle_algorithm(testG, expected)
649
+
650
+ def test_simple_cycles_bounded(self):
651
+ # iteratively construct a cluster of nested cycles running in the same direction
652
+ # there should be one cycle of every length
653
+ d = nx.DiGraph()
654
+ expected = []
655
+ for n in range(10):
656
+ nx.add_cycle(d, range(n))
657
+ expected.append(n)
658
+ for k, e in enumerate(expected):
659
+ self.check_cycle_algorithm(d, e, length_bound=k)
660
+
661
+ # iteratively construct a path of undirected cycles, connected at articulation
662
+ # points. there should be one cycle of every length except 2: no digons
663
+ g = nx.Graph()
664
+ top = 0
665
+ expected = []
666
+ for n in range(10):
667
+ expected.append(n if n < 2 else n - 1)
668
+ if n == 2:
669
+ # no digons in undirected graphs
670
+ continue
671
+ nx.add_cycle(g, range(top, top + n))
672
+ top += n
673
+ for k, e in enumerate(expected):
674
+ self.check_cycle_algorithm(g, e, length_bound=k)
675
+
676
+ def test_simple_cycles_bound_corner_cases(self):
677
+ G = nx.cycle_graph(4)
678
+ DG = nx.cycle_graph(4, create_using=nx.DiGraph)
679
+ assert list(nx.simple_cycles(G, length_bound=0)) == []
680
+ assert list(nx.simple_cycles(DG, length_bound=0)) == []
681
+ assert list(nx.chordless_cycles(G, length_bound=0)) == []
682
+ assert list(nx.chordless_cycles(DG, length_bound=0)) == []
683
+
684
+ def test_simple_cycles_bound_error(self):
685
+ with pytest.raises(ValueError):
686
+ G = nx.DiGraph()
687
+ for c in nx.simple_cycles(G, -1):
688
+ assert False
689
+
690
+ with pytest.raises(ValueError):
691
+ G = nx.Graph()
692
+ for c in nx.simple_cycles(G, -1):
693
+ assert False
694
+
695
+ with pytest.raises(ValueError):
696
+ G = nx.Graph()
697
+ for c in nx.chordless_cycles(G, -1):
698
+ assert False
699
+
700
+ with pytest.raises(ValueError):
701
+ G = nx.DiGraph()
702
+ for c in nx.chordless_cycles(G, -1):
703
+ assert False
704
+
705
+ def test_chordless_cycles_clique(self):
706
+ g_family = [self.K(n) for n in range(2, 15)]
707
+ expected = [0, 1, 4, 10, 20, 35, 56, 84, 120, 165, 220, 286, 364]
708
+ self.check_cycle_enumeration_integer_sequence(
709
+ g_family, expected, chordless=True
710
+ )
711
+
712
+ # directed cliques have as many digons as undirected graphs have edges
713
+ expected = [(n * n - n) // 2 for n in range(15)]
714
+ g_family = [self.D(n) for n in range(15)]
715
+ self.check_cycle_enumeration_integer_sequence(
716
+ g_family, expected, chordless=True
717
+ )
718
+
719
+
720
+ # These tests might fail with hash randomization since they depend on
721
+ # edge_dfs. For more information, see the comments in:
722
+ # networkx/algorithms/traversal/tests/test_edgedfs.py
723
+
724
+
725
+ class TestFindCycle:
726
+ @classmethod
727
+ def setup_class(cls):
728
+ cls.nodes = [0, 1, 2, 3]
729
+ cls.edges = [(-1, 0), (0, 1), (1, 0), (1, 0), (2, 1), (3, 1)]
730
+
731
+ def test_graph_nocycle(self):
732
+ G = nx.Graph(self.edges)
733
+ pytest.raises(nx.exception.NetworkXNoCycle, nx.find_cycle, G, self.nodes)
734
+
735
+ def test_graph_cycle(self):
736
+ G = nx.Graph(self.edges)
737
+ G.add_edge(2, 0)
738
+ x = list(nx.find_cycle(G, self.nodes))
739
+ x_ = [(0, 1), (1, 2), (2, 0)]
740
+ assert x == x_
741
+
742
+ def test_graph_orientation_none(self):
743
+ G = nx.Graph(self.edges)
744
+ G.add_edge(2, 0)
745
+ x = list(nx.find_cycle(G, self.nodes, orientation=None))
746
+ x_ = [(0, 1), (1, 2), (2, 0)]
747
+ assert x == x_
748
+
749
+ def test_graph_orientation_original(self):
750
+ G = nx.Graph(self.edges)
751
+ G.add_edge(2, 0)
752
+ x = list(nx.find_cycle(G, self.nodes, orientation="original"))
753
+ x_ = [(0, 1, FORWARD), (1, 2, FORWARD), (2, 0, FORWARD)]
754
+ assert x == x_
755
+
756
+ def test_digraph(self):
757
+ G = nx.DiGraph(self.edges)
758
+ x = list(nx.find_cycle(G, self.nodes))
759
+ x_ = [(0, 1), (1, 0)]
760
+ assert x == x_
761
+
762
+ def test_digraph_orientation_none(self):
763
+ G = nx.DiGraph(self.edges)
764
+ x = list(nx.find_cycle(G, self.nodes, orientation=None))
765
+ x_ = [(0, 1), (1, 0)]
766
+ assert x == x_
767
+
768
+ def test_digraph_orientation_original(self):
769
+ G = nx.DiGraph(self.edges)
770
+ x = list(nx.find_cycle(G, self.nodes, orientation="original"))
771
+ x_ = [(0, 1, FORWARD), (1, 0, FORWARD)]
772
+ assert x == x_
773
+
774
+ def test_multigraph(self):
775
+ G = nx.MultiGraph(self.edges)
776
+ x = list(nx.find_cycle(G, self.nodes))
777
+ x_ = [(0, 1, 0), (1, 0, 1)] # or (1, 0, 2)
778
+ # Hash randomization...could be any edge.
779
+ assert x[0] == x_[0]
780
+ assert x[1][:2] == x_[1][:2]
781
+
782
+ def test_multidigraph(self):
783
+ G = nx.MultiDiGraph(self.edges)
784
+ x = list(nx.find_cycle(G, self.nodes))
785
+ x_ = [(0, 1, 0), (1, 0, 0)] # (1, 0, 1)
786
+ assert x[0] == x_[0]
787
+ assert x[1][:2] == x_[1][:2]
788
+
789
+ def test_digraph_ignore(self):
790
+ G = nx.DiGraph(self.edges)
791
+ x = list(nx.find_cycle(G, self.nodes, orientation="ignore"))
792
+ x_ = [(0, 1, FORWARD), (1, 0, FORWARD)]
793
+ assert x == x_
794
+
795
+ def test_digraph_reverse(self):
796
+ G = nx.DiGraph(self.edges)
797
+ x = list(nx.find_cycle(G, self.nodes, orientation="reverse"))
798
+ x_ = [(1, 0, REVERSE), (0, 1, REVERSE)]
799
+ assert x == x_
800
+
801
+ def test_multidigraph_ignore(self):
802
+ G = nx.MultiDiGraph(self.edges)
803
+ x = list(nx.find_cycle(G, self.nodes, orientation="ignore"))
804
+ x_ = [(0, 1, 0, FORWARD), (1, 0, 0, FORWARD)] # or (1, 0, 1, 1)
805
+ assert x[0] == x_[0]
806
+ assert x[1][:2] == x_[1][:2]
807
+ assert x[1][3] == x_[1][3]
808
+
809
+ def test_multidigraph_ignore2(self):
810
+ # Loop traversed an edge while ignoring its orientation.
811
+ G = nx.MultiDiGraph([(0, 1), (1, 2), (1, 2)])
812
+ x = list(nx.find_cycle(G, [0, 1, 2], orientation="ignore"))
813
+ x_ = [(1, 2, 0, FORWARD), (1, 2, 1, REVERSE)]
814
+ assert x == x_
815
+
816
+ def test_multidigraph_original(self):
817
+ # Node 2 doesn't need to be searched again from visited from 4.
818
+ # The goal here is to cover the case when 2 to be researched from 4,
819
+ # when 4 is visited from the first time (so we must make sure that 4
820
+ # is not visited from 2, and hence, we respect the edge orientation).
821
+ G = nx.MultiDiGraph([(0, 1), (1, 2), (2, 3), (4, 2)])
822
+ pytest.raises(
823
+ nx.exception.NetworkXNoCycle,
824
+ nx.find_cycle,
825
+ G,
826
+ [0, 1, 2, 3, 4],
827
+ orientation="original",
828
+ )
829
+
830
+ def test_dag(self):
831
+ G = nx.DiGraph([(0, 1), (0, 2), (1, 2)])
832
+ pytest.raises(
833
+ nx.exception.NetworkXNoCycle, nx.find_cycle, G, orientation="original"
834
+ )
835
+ x = list(nx.find_cycle(G, orientation="ignore"))
836
+ assert x == [(0, 1, FORWARD), (1, 2, FORWARD), (0, 2, REVERSE)]
837
+
838
+ def test_prev_explored(self):
839
+ # https://github.com/networkx/networkx/issues/2323
840
+
841
+ G = nx.DiGraph()
842
+ G.add_edges_from([(1, 0), (2, 0), (1, 2), (2, 1)])
843
+ pytest.raises(nx.NetworkXNoCycle, nx.find_cycle, G, source=0)
844
+ x = list(nx.find_cycle(G, 1))
845
+ x_ = [(1, 2), (2, 1)]
846
+ assert x == x_
847
+
848
+ x = list(nx.find_cycle(G, 2))
849
+ x_ = [(2, 1), (1, 2)]
850
+ assert x == x_
851
+
852
+ x = list(nx.find_cycle(G))
853
+ x_ = [(1, 2), (2, 1)]
854
+ assert x == x_
855
+
856
+ def test_no_cycle(self):
857
+ # https://github.com/networkx/networkx/issues/2439
858
+
859
+ G = nx.DiGraph()
860
+ G.add_edges_from([(1, 2), (2, 0), (3, 1), (3, 2)])
861
+ pytest.raises(nx.NetworkXNoCycle, nx.find_cycle, G, source=0)
862
+ pytest.raises(nx.NetworkXNoCycle, nx.find_cycle, G)
863
+
864
+
865
+ def assert_basis_equal(a, b):
866
+ assert sorted(a) == sorted(b)
867
+
868
+
869
+ class TestMinimumCycleBasis:
870
+ @classmethod
871
+ def setup_class(cls):
872
+ T = nx.Graph()
873
+ nx.add_cycle(T, [1, 2, 3, 4], weight=1)
874
+ T.add_edge(2, 4, weight=5)
875
+ cls.diamond_graph = T
876
+
877
+ def test_unweighted_diamond(self):
878
+ mcb = nx.minimum_cycle_basis(self.diamond_graph)
879
+ assert_basis_equal(mcb, [[2, 4, 1], [3, 4, 2]])
880
+
881
+ def test_weighted_diamond(self):
882
+ mcb = nx.minimum_cycle_basis(self.diamond_graph, weight="weight")
883
+ assert_basis_equal(mcb, [[2, 4, 1], [4, 3, 2, 1]])
884
+
885
+ def test_dimensionality(self):
886
+ # checks |MCB|=|E|-|V|+|NC|
887
+ ntrial = 10
888
+ for seed in range(1234, 1234 + ntrial):
889
+ rg = nx.erdos_renyi_graph(10, 0.3, seed=seed)
890
+ nnodes = rg.number_of_nodes()
891
+ nedges = rg.number_of_edges()
892
+ ncomp = nx.number_connected_components(rg)
893
+
894
+ mcb = nx.minimum_cycle_basis(rg)
895
+ assert len(mcb) == nedges - nnodes + ncomp
896
+ check_independent(mcb)
897
+
898
+ def test_complete_graph(self):
899
+ cg = nx.complete_graph(5)
900
+ mcb = nx.minimum_cycle_basis(cg)
901
+ assert all(len(cycle) == 3 for cycle in mcb)
902
+ check_independent(mcb)
903
+
904
+ def test_tree_graph(self):
905
+ tg = nx.balanced_tree(3, 3)
906
+ assert not nx.minimum_cycle_basis(tg)
907
+
908
+ def test_petersen_graph(self):
909
+ G = nx.petersen_graph()
910
+ mcb = list(nx.minimum_cycle_basis(G))
911
+ expected = [
912
+ [4, 9, 7, 5, 0],
913
+ [1, 2, 3, 4, 0],
914
+ [1, 6, 8, 5, 0],
915
+ [4, 3, 8, 5, 0],
916
+ [1, 6, 9, 4, 0],
917
+ [1, 2, 7, 5, 0],
918
+ ]
919
+ assert len(mcb) == len(expected)
920
+ assert all(c in expected for c in mcb)
921
+
922
+ # check that order of the nodes is a path
923
+ for c in mcb:
924
+ assert all(G.has_edge(u, v) for u, v in nx.utils.pairwise(c, cyclic=True))
925
+ # check independence of the basis
926
+ check_independent(mcb)
927
+
928
+ def test_gh6787_variable_weighted_complete_graph(self):
929
+ N = 8
930
+ cg = nx.complete_graph(N)
931
+ cg.add_weighted_edges_from([(u, v, 9) for u, v in cg.edges])
932
+ cg.add_weighted_edges_from([(u, v, 1) for u, v in nx.cycle_graph(N).edges])
933
+ mcb = nx.minimum_cycle_basis(cg, weight="weight")
934
+ check_independent(mcb)
935
+
936
+ def test_gh6787_and_edge_attribute_names(self):
937
+ G = nx.cycle_graph(4)
938
+ G.add_weighted_edges_from([(0, 2, 10), (1, 3, 10)], weight="dist")
939
+ expected = [[1, 3, 0], [3, 2, 1, 0], [1, 2, 0]]
940
+ mcb = list(nx.minimum_cycle_basis(G, weight="dist"))
941
+ assert len(mcb) == len(expected)
942
+ assert all(c in expected for c in mcb)
943
+
944
+ # test not using a weight with weight attributes
945
+ expected = [[1, 3, 0], [1, 2, 0], [3, 2, 0]]
946
+ mcb = list(nx.minimum_cycle_basis(G))
947
+ assert len(mcb) == len(expected)
948
+ assert all(c in expected for c in mcb)
949
+
950
+
951
+ class TestGirth:
952
+ @pytest.mark.parametrize(
953
+ ("G", "expected"),
954
+ (
955
+ (nx.chvatal_graph(), 4),
956
+ (nx.tutte_graph(), 4),
957
+ (nx.petersen_graph(), 5),
958
+ (nx.heawood_graph(), 6),
959
+ (nx.pappus_graph(), 6),
960
+ (nx.random_labeled_tree(10, seed=42), inf),
961
+ (nx.empty_graph(10), inf),
962
+ (nx.Graph(chain(cycle_edges(range(5)), cycle_edges(range(6, 10)))), 4),
963
+ (
964
+ nx.Graph(
965
+ [
966
+ (0, 6),
967
+ (0, 8),
968
+ (0, 9),
969
+ (1, 8),
970
+ (2, 8),
971
+ (2, 9),
972
+ (4, 9),
973
+ (5, 9),
974
+ (6, 8),
975
+ (6, 9),
976
+ (7, 8),
977
+ ]
978
+ ),
979
+ 3,
980
+ ),
981
+ ),
982
+ )
983
+ def test_girth(self, G, expected):
984
+ assert nx.girth(G) == expected
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_d_separation.py ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from itertools import combinations
2
+
3
+ import pytest
4
+
5
+ import networkx as nx
6
+
7
+
8
+ def path_graph():
9
+ """Return a path graph of length three."""
10
+ G = nx.path_graph(3, create_using=nx.DiGraph)
11
+ G.graph["name"] = "path"
12
+ nx.freeze(G)
13
+ return G
14
+
15
+
16
+ def fork_graph():
17
+ """Return a three node fork graph."""
18
+ G = nx.DiGraph(name="fork")
19
+ G.add_edges_from([(0, 1), (0, 2)])
20
+ nx.freeze(G)
21
+ return G
22
+
23
+
24
+ def collider_graph():
25
+ """Return a collider/v-structure graph with three nodes."""
26
+ G = nx.DiGraph(name="collider")
27
+ G.add_edges_from([(0, 2), (1, 2)])
28
+ nx.freeze(G)
29
+ return G
30
+
31
+
32
+ def naive_bayes_graph():
33
+ """Return a simply Naive Bayes PGM graph."""
34
+ G = nx.DiGraph(name="naive_bayes")
35
+ G.add_edges_from([(0, 1), (0, 2), (0, 3), (0, 4)])
36
+ nx.freeze(G)
37
+ return G
38
+
39
+
40
+ def asia_graph():
41
+ """Return the 'Asia' PGM graph."""
42
+ G = nx.DiGraph(name="asia")
43
+ G.add_edges_from(
44
+ [
45
+ ("asia", "tuberculosis"),
46
+ ("smoking", "cancer"),
47
+ ("smoking", "bronchitis"),
48
+ ("tuberculosis", "either"),
49
+ ("cancer", "either"),
50
+ ("either", "xray"),
51
+ ("either", "dyspnea"),
52
+ ("bronchitis", "dyspnea"),
53
+ ]
54
+ )
55
+ nx.freeze(G)
56
+ return G
57
+
58
+
59
+ @pytest.fixture(name="path_graph")
60
+ def path_graph_fixture():
61
+ return path_graph()
62
+
63
+
64
+ @pytest.fixture(name="fork_graph")
65
+ def fork_graph_fixture():
66
+ return fork_graph()
67
+
68
+
69
+ @pytest.fixture(name="collider_graph")
70
+ def collider_graph_fixture():
71
+ return collider_graph()
72
+
73
+
74
+ @pytest.fixture(name="naive_bayes_graph")
75
+ def naive_bayes_graph_fixture():
76
+ return naive_bayes_graph()
77
+
78
+
79
+ @pytest.fixture(name="asia_graph")
80
+ def asia_graph_fixture():
81
+ return asia_graph()
82
+
83
+
84
+ @pytest.fixture()
85
+ def large_collider_graph():
86
+ edge_list = [("A", "B"), ("C", "B"), ("B", "D"), ("D", "E"), ("B", "F"), ("G", "E")]
87
+ G = nx.DiGraph(edge_list)
88
+ return G
89
+
90
+
91
+ @pytest.fixture()
92
+ def chain_and_fork_graph():
93
+ edge_list = [("A", "B"), ("B", "C"), ("B", "D"), ("D", "C")]
94
+ G = nx.DiGraph(edge_list)
95
+ return G
96
+
97
+
98
+ @pytest.fixture()
99
+ def no_separating_set_graph():
100
+ edge_list = [("A", "B")]
101
+ G = nx.DiGraph(edge_list)
102
+ return G
103
+
104
+
105
+ @pytest.fixture()
106
+ def large_no_separating_set_graph():
107
+ edge_list = [("A", "B"), ("C", "A"), ("C", "B")]
108
+ G = nx.DiGraph(edge_list)
109
+ return G
110
+
111
+
112
+ @pytest.fixture()
113
+ def collider_trek_graph():
114
+ edge_list = [("A", "B"), ("C", "B"), ("C", "D")]
115
+ G = nx.DiGraph(edge_list)
116
+ return G
117
+
118
+
119
+ @pytest.mark.parametrize(
120
+ "graph",
121
+ [path_graph(), fork_graph(), collider_graph(), naive_bayes_graph(), asia_graph()],
122
+ )
123
+ def test_markov_condition(graph):
124
+ """Test that the Markov condition holds for each PGM graph."""
125
+ for node in graph.nodes:
126
+ parents = set(graph.predecessors(node))
127
+ non_descendants = graph.nodes - nx.descendants(graph, node) - {node} - parents
128
+ assert nx.is_d_separator(graph, {node}, non_descendants, parents)
129
+
130
+
131
+ def test_path_graph_dsep(path_graph):
132
+ """Example-based test of d-separation for path_graph."""
133
+ assert nx.is_d_separator(path_graph, {0}, {2}, {1})
134
+ assert not nx.is_d_separator(path_graph, {0}, {2}, set())
135
+
136
+
137
+ def test_fork_graph_dsep(fork_graph):
138
+ """Example-based test of d-separation for fork_graph."""
139
+ assert nx.is_d_separator(fork_graph, {1}, {2}, {0})
140
+ assert not nx.is_d_separator(fork_graph, {1}, {2}, set())
141
+
142
+
143
+ def test_collider_graph_dsep(collider_graph):
144
+ """Example-based test of d-separation for collider_graph."""
145
+ assert nx.is_d_separator(collider_graph, {0}, {1}, set())
146
+ assert not nx.is_d_separator(collider_graph, {0}, {1}, {2})
147
+
148
+
149
+ def test_naive_bayes_dsep(naive_bayes_graph):
150
+ """Example-based test of d-separation for naive_bayes_graph."""
151
+ for u, v in combinations(range(1, 5), 2):
152
+ assert nx.is_d_separator(naive_bayes_graph, {u}, {v}, {0})
153
+ assert not nx.is_d_separator(naive_bayes_graph, {u}, {v}, set())
154
+
155
+
156
+ def test_asia_graph_dsep(asia_graph):
157
+ """Example-based test of d-separation for asia_graph."""
158
+ assert nx.is_d_separator(
159
+ asia_graph, {"asia", "smoking"}, {"dyspnea", "xray"}, {"bronchitis", "either"}
160
+ )
161
+ assert nx.is_d_separator(
162
+ asia_graph, {"tuberculosis", "cancer"}, {"bronchitis"}, {"smoking", "xray"}
163
+ )
164
+
165
+
166
+ def test_undirected_graphs_are_not_supported():
167
+ """
168
+ Test that undirected graphs are not supported.
169
+
170
+ d-separation and its related algorithms do not apply in
171
+ the case of undirected graphs.
172
+ """
173
+ g = nx.path_graph(3, nx.Graph)
174
+ with pytest.raises(nx.NetworkXNotImplemented):
175
+ nx.is_d_separator(g, {0}, {1}, {2})
176
+ with pytest.raises(nx.NetworkXNotImplemented):
177
+ nx.is_minimal_d_separator(g, {0}, {1}, {2})
178
+ with pytest.raises(nx.NetworkXNotImplemented):
179
+ nx.find_minimal_d_separator(g, {0}, {1})
180
+
181
+
182
+ def test_cyclic_graphs_raise_error():
183
+ """
184
+ Test that cycle graphs should cause erroring.
185
+
186
+ This is because PGMs assume a directed acyclic graph.
187
+ """
188
+ g = nx.cycle_graph(3, nx.DiGraph)
189
+ with pytest.raises(nx.NetworkXError):
190
+ nx.is_d_separator(g, {0}, {1}, {2})
191
+ with pytest.raises(nx.NetworkXError):
192
+ nx.find_minimal_d_separator(g, {0}, {1})
193
+ with pytest.raises(nx.NetworkXError):
194
+ nx.is_minimal_d_separator(g, {0}, {1}, {2})
195
+
196
+
197
+ def test_invalid_nodes_raise_error(asia_graph):
198
+ """
199
+ Test that graphs that have invalid nodes passed in raise errors.
200
+ """
201
+ # Check both set and node arguments
202
+ with pytest.raises(nx.NodeNotFound):
203
+ nx.is_d_separator(asia_graph, {0}, {1}, {2})
204
+ with pytest.raises(nx.NodeNotFound):
205
+ nx.is_d_separator(asia_graph, 0, 1, 2)
206
+ with pytest.raises(nx.NodeNotFound):
207
+ nx.is_minimal_d_separator(asia_graph, {0}, {1}, {2})
208
+ with pytest.raises(nx.NodeNotFound):
209
+ nx.is_minimal_d_separator(asia_graph, 0, 1, 2)
210
+ with pytest.raises(nx.NodeNotFound):
211
+ nx.find_minimal_d_separator(asia_graph, {0}, {1})
212
+ with pytest.raises(nx.NodeNotFound):
213
+ nx.find_minimal_d_separator(asia_graph, 0, 1)
214
+
215
+
216
+ def test_nondisjoint_node_sets_raise_error(collider_graph):
217
+ """
218
+ Test that error is raised when node sets aren't disjoint.
219
+ """
220
+ with pytest.raises(nx.NetworkXError):
221
+ nx.is_d_separator(collider_graph, 0, 1, 0)
222
+ with pytest.raises(nx.NetworkXError):
223
+ nx.is_d_separator(collider_graph, 0, 2, 0)
224
+ with pytest.raises(nx.NetworkXError):
225
+ nx.is_d_separator(collider_graph, 0, 0, 1)
226
+ with pytest.raises(nx.NetworkXError):
227
+ nx.is_d_separator(collider_graph, 1, 0, 0)
228
+ with pytest.raises(nx.NetworkXError):
229
+ nx.find_minimal_d_separator(collider_graph, 0, 0)
230
+ with pytest.raises(nx.NetworkXError):
231
+ nx.find_minimal_d_separator(collider_graph, 0, 1, included=0)
232
+ with pytest.raises(nx.NetworkXError):
233
+ nx.find_minimal_d_separator(collider_graph, 1, 0, included=0)
234
+ with pytest.raises(nx.NetworkXError):
235
+ nx.is_minimal_d_separator(collider_graph, 0, 0, set())
236
+ with pytest.raises(nx.NetworkXError):
237
+ nx.is_minimal_d_separator(collider_graph, 0, 1, set(), included=0)
238
+ with pytest.raises(nx.NetworkXError):
239
+ nx.is_minimal_d_separator(collider_graph, 1, 0, set(), included=0)
240
+
241
+
242
+ def test_is_minimal_d_separator(
243
+ large_collider_graph,
244
+ chain_and_fork_graph,
245
+ no_separating_set_graph,
246
+ large_no_separating_set_graph,
247
+ collider_trek_graph,
248
+ ):
249
+ # Case 1:
250
+ # create a graph A -> B <- C
251
+ # B -> D -> E;
252
+ # B -> F;
253
+ # G -> E;
254
+ assert not nx.is_d_separator(large_collider_graph, {"B"}, {"E"}, set())
255
+
256
+ # minimal set of the corresponding graph
257
+ # for B and E should be (D,)
258
+ Zmin = nx.find_minimal_d_separator(large_collider_graph, "B", "E")
259
+ # check that the minimal d-separator is a d-separating set
260
+ assert nx.is_d_separator(large_collider_graph, "B", "E", Zmin)
261
+ # the minimal separating set should also pass the test for minimality
262
+ assert nx.is_minimal_d_separator(large_collider_graph, "B", "E", Zmin)
263
+ # function should also work with set arguments
264
+ assert nx.is_minimal_d_separator(large_collider_graph, {"A", "B"}, {"G", "E"}, Zmin)
265
+ assert Zmin == {"D"}
266
+
267
+ # Case 2:
268
+ # create a graph A -> B -> C
269
+ # B -> D -> C;
270
+ assert not nx.is_d_separator(chain_and_fork_graph, {"A"}, {"C"}, set())
271
+ Zmin = nx.find_minimal_d_separator(chain_and_fork_graph, "A", "C")
272
+
273
+ # the minimal separating set should pass the test for minimality
274
+ assert nx.is_minimal_d_separator(chain_and_fork_graph, "A", "C", Zmin)
275
+ assert Zmin == {"B"}
276
+ Znotmin = Zmin.union({"D"})
277
+ assert not nx.is_minimal_d_separator(chain_and_fork_graph, "A", "C", Znotmin)
278
+
279
+ # Case 3:
280
+ # create a graph A -> B
281
+
282
+ # there is no m-separating set between A and B at all, so
283
+ # no minimal m-separating set can exist
284
+ assert not nx.is_d_separator(no_separating_set_graph, {"A"}, {"B"}, set())
285
+ assert nx.find_minimal_d_separator(no_separating_set_graph, "A", "B") is None
286
+
287
+ # Case 4:
288
+ # create a graph A -> B with A <- C -> B
289
+
290
+ # there is no m-separating set between A and B at all, so
291
+ # no minimal m-separating set can exist
292
+ # however, the algorithm will initially propose C as a
293
+ # minimal (but invalid) separating set
294
+ assert not nx.is_d_separator(large_no_separating_set_graph, {"A"}, {"B"}, {"C"})
295
+ assert nx.find_minimal_d_separator(large_no_separating_set_graph, "A", "B") is None
296
+
297
+ # Test `included` and `excluded` args
298
+ # create graph A -> B <- C -> D
299
+ assert nx.find_minimal_d_separator(collider_trek_graph, "A", "D", included="B") == {
300
+ "B",
301
+ "C",
302
+ }
303
+ assert (
304
+ nx.find_minimal_d_separator(
305
+ collider_trek_graph, "A", "D", included="B", restricted="B"
306
+ )
307
+ is None
308
+ )
309
+
310
+
311
+ def test_is_minimal_d_separator_checks_dsep():
312
+ """Test that is_minimal_d_separator checks for d-separation as well."""
313
+ g = nx.DiGraph()
314
+ g.add_edges_from(
315
+ [
316
+ ("A", "B"),
317
+ ("A", "E"),
318
+ ("B", "C"),
319
+ ("B", "D"),
320
+ ("D", "C"),
321
+ ("D", "F"),
322
+ ("E", "D"),
323
+ ("E", "F"),
324
+ ]
325
+ )
326
+
327
+ assert not nx.is_d_separator(g, {"C"}, {"F"}, {"D"})
328
+
329
+ # since {'D'} and {} are not d-separators, we return false
330
+ assert not nx.is_minimal_d_separator(g, "C", "F", {"D"})
331
+ assert not nx.is_minimal_d_separator(g, "C", "F", set())
332
+
333
+
334
+ def test__reachable(large_collider_graph):
335
+ reachable = nx.algorithms.d_separation._reachable
336
+ g = large_collider_graph
337
+ x = {"F", "D"}
338
+ ancestors = {"A", "B", "C", "D", "F"}
339
+ assert reachable(g, x, ancestors, {"B"}) == {"B", "F", "D"}
340
+ assert reachable(g, x, ancestors, set()) == ancestors
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_dag.py ADDED
@@ -0,0 +1,835 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections import deque
2
+ from itertools import combinations, permutations
3
+
4
+ import pytest
5
+
6
+ import networkx as nx
7
+ from networkx.utils import edges_equal, pairwise
8
+
9
+
10
+ # Recipe from the itertools documentation.
11
+ def _consume(iterator):
12
+ "Consume the iterator entirely."
13
+ # Feed the entire iterator into a zero-length deque.
14
+ deque(iterator, maxlen=0)
15
+
16
+
17
+ class TestDagLongestPath:
18
+ """Unit tests computing the longest path in a directed acyclic graph."""
19
+
20
+ def test_empty(self):
21
+ G = nx.DiGraph()
22
+ assert nx.dag_longest_path(G) == []
23
+
24
+ def test_unweighted1(self):
25
+ edges = [(1, 2), (2, 3), (2, 4), (3, 5), (5, 6), (3, 7)]
26
+ G = nx.DiGraph(edges)
27
+ assert nx.dag_longest_path(G) == [1, 2, 3, 5, 6]
28
+
29
+ def test_unweighted2(self):
30
+ edges = [(1, 2), (2, 3), (3, 4), (4, 5), (1, 3), (1, 5), (3, 5)]
31
+ G = nx.DiGraph(edges)
32
+ assert nx.dag_longest_path(G) == [1, 2, 3, 4, 5]
33
+
34
+ def test_weighted(self):
35
+ G = nx.DiGraph()
36
+ edges = [(1, 2, -5), (2, 3, 1), (3, 4, 1), (4, 5, 0), (3, 5, 4), (1, 6, 2)]
37
+ G.add_weighted_edges_from(edges)
38
+ assert nx.dag_longest_path(G) == [2, 3, 5]
39
+
40
+ def test_undirected_not_implemented(self):
41
+ G = nx.Graph()
42
+ pytest.raises(nx.NetworkXNotImplemented, nx.dag_longest_path, G)
43
+
44
+ def test_unorderable_nodes(self):
45
+ """Tests that computing the longest path does not depend on
46
+ nodes being orderable.
47
+
48
+ For more information, see issue #1989.
49
+
50
+ """
51
+ # Create the directed path graph on four nodes in a diamond shape,
52
+ # with nodes represented as (unorderable) Python objects.
53
+ nodes = [object() for n in range(4)]
54
+ G = nx.DiGraph()
55
+ G.add_edge(nodes[0], nodes[1])
56
+ G.add_edge(nodes[0], nodes[2])
57
+ G.add_edge(nodes[2], nodes[3])
58
+ G.add_edge(nodes[1], nodes[3])
59
+
60
+ # this will raise NotImplementedError when nodes need to be ordered
61
+ nx.dag_longest_path(G)
62
+
63
+ def test_multigraph_unweighted(self):
64
+ edges = [(1, 2), (2, 3), (2, 3), (3, 4), (4, 5), (1, 3), (1, 5), (3, 5)]
65
+ G = nx.MultiDiGraph(edges)
66
+ assert nx.dag_longest_path(G) == [1, 2, 3, 4, 5]
67
+
68
+ def test_multigraph_weighted(self):
69
+ G = nx.MultiDiGraph()
70
+ edges = [
71
+ (1, 2, 2),
72
+ (2, 3, 2),
73
+ (1, 3, 1),
74
+ (1, 3, 5),
75
+ (1, 3, 2),
76
+ ]
77
+ G.add_weighted_edges_from(edges)
78
+ assert nx.dag_longest_path(G) == [1, 3]
79
+
80
+ def test_multigraph_weighted_default_weight(self):
81
+ G = nx.MultiDiGraph([(1, 2), (2, 3)]) # Unweighted edges
82
+ G.add_weighted_edges_from([(1, 3, 1), (1, 3, 5), (1, 3, 2)])
83
+
84
+ # Default value for default weight is 1
85
+ assert nx.dag_longest_path(G) == [1, 3]
86
+ assert nx.dag_longest_path(G, default_weight=3) == [1, 2, 3]
87
+
88
+
89
+ class TestDagLongestPathLength:
90
+ """Unit tests for computing the length of a longest path in a
91
+ directed acyclic graph.
92
+
93
+ """
94
+
95
+ def test_unweighted(self):
96
+ edges = [(1, 2), (2, 3), (2, 4), (3, 5), (5, 6), (5, 7)]
97
+ G = nx.DiGraph(edges)
98
+ assert nx.dag_longest_path_length(G) == 4
99
+
100
+ edges = [(1, 2), (2, 3), (3, 4), (4, 5), (1, 3), (1, 5), (3, 5)]
101
+ G = nx.DiGraph(edges)
102
+ assert nx.dag_longest_path_length(G) == 4
103
+
104
+ # test degenerate graphs
105
+ G = nx.DiGraph()
106
+ G.add_node(1)
107
+ assert nx.dag_longest_path_length(G) == 0
108
+
109
+ def test_undirected_not_implemented(self):
110
+ G = nx.Graph()
111
+ pytest.raises(nx.NetworkXNotImplemented, nx.dag_longest_path_length, G)
112
+
113
+ def test_weighted(self):
114
+ edges = [(1, 2, -5), (2, 3, 1), (3, 4, 1), (4, 5, 0), (3, 5, 4), (1, 6, 2)]
115
+ G = nx.DiGraph()
116
+ G.add_weighted_edges_from(edges)
117
+ assert nx.dag_longest_path_length(G) == 5
118
+
119
+ def test_multigraph_unweighted(self):
120
+ edges = [(1, 2), (2, 3), (2, 3), (3, 4), (4, 5), (1, 3), (1, 5), (3, 5)]
121
+ G = nx.MultiDiGraph(edges)
122
+ assert nx.dag_longest_path_length(G) == 4
123
+
124
+ def test_multigraph_weighted(self):
125
+ G = nx.MultiDiGraph()
126
+ edges = [
127
+ (1, 2, 2),
128
+ (2, 3, 2),
129
+ (1, 3, 1),
130
+ (1, 3, 5),
131
+ (1, 3, 2),
132
+ ]
133
+ G.add_weighted_edges_from(edges)
134
+ assert nx.dag_longest_path_length(G) == 5
135
+
136
+
137
+ class TestDAG:
138
+ @classmethod
139
+ def setup_class(cls):
140
+ pass
141
+
142
+ def test_topological_sort1(self):
143
+ DG = nx.DiGraph([(1, 2), (1, 3), (2, 3)])
144
+
145
+ for algorithm in [nx.topological_sort, nx.lexicographical_topological_sort]:
146
+ assert tuple(algorithm(DG)) == (1, 2, 3)
147
+
148
+ DG.add_edge(3, 2)
149
+
150
+ for algorithm in [nx.topological_sort, nx.lexicographical_topological_sort]:
151
+ pytest.raises(nx.NetworkXUnfeasible, _consume, algorithm(DG))
152
+
153
+ DG.remove_edge(2, 3)
154
+
155
+ for algorithm in [nx.topological_sort, nx.lexicographical_topological_sort]:
156
+ assert tuple(algorithm(DG)) == (1, 3, 2)
157
+
158
+ DG.remove_edge(3, 2)
159
+
160
+ assert tuple(nx.topological_sort(DG)) in {(1, 2, 3), (1, 3, 2)}
161
+ assert tuple(nx.lexicographical_topological_sort(DG)) == (1, 2, 3)
162
+
163
+ def test_is_directed_acyclic_graph(self):
164
+ G = nx.generators.complete_graph(2)
165
+ assert not nx.is_directed_acyclic_graph(G)
166
+ assert not nx.is_directed_acyclic_graph(G.to_directed())
167
+ assert not nx.is_directed_acyclic_graph(nx.Graph([(3, 4), (4, 5)]))
168
+ assert nx.is_directed_acyclic_graph(nx.DiGraph([(3, 4), (4, 5)]))
169
+
170
+ def test_topological_sort2(self):
171
+ DG = nx.DiGraph(
172
+ {
173
+ 1: [2],
174
+ 2: [3],
175
+ 3: [4],
176
+ 4: [5],
177
+ 5: [1],
178
+ 11: [12],
179
+ 12: [13],
180
+ 13: [14],
181
+ 14: [15],
182
+ }
183
+ )
184
+ pytest.raises(nx.NetworkXUnfeasible, _consume, nx.topological_sort(DG))
185
+
186
+ assert not nx.is_directed_acyclic_graph(DG)
187
+
188
+ DG.remove_edge(1, 2)
189
+ _consume(nx.topological_sort(DG))
190
+ assert nx.is_directed_acyclic_graph(DG)
191
+
192
+ def test_topological_sort3(self):
193
+ DG = nx.DiGraph()
194
+ DG.add_edges_from([(1, i) for i in range(2, 5)])
195
+ DG.add_edges_from([(2, i) for i in range(5, 9)])
196
+ DG.add_edges_from([(6, i) for i in range(9, 12)])
197
+ DG.add_edges_from([(4, i) for i in range(12, 15)])
198
+
199
+ def validate(order):
200
+ assert isinstance(order, list)
201
+ assert set(order) == set(DG)
202
+ for u, v in combinations(order, 2):
203
+ assert not nx.has_path(DG, v, u)
204
+
205
+ validate(list(nx.topological_sort(DG)))
206
+
207
+ DG.add_edge(14, 1)
208
+ pytest.raises(nx.NetworkXUnfeasible, _consume, nx.topological_sort(DG))
209
+
210
+ def test_topological_sort4(self):
211
+ G = nx.Graph()
212
+ G.add_edge(1, 2)
213
+ # Only directed graphs can be topologically sorted.
214
+ pytest.raises(nx.NetworkXError, _consume, nx.topological_sort(G))
215
+
216
+ def test_topological_sort5(self):
217
+ G = nx.DiGraph()
218
+ G.add_edge(0, 1)
219
+ assert list(nx.topological_sort(G)) == [0, 1]
220
+
221
+ def test_topological_sort6(self):
222
+ for algorithm in [nx.topological_sort, nx.lexicographical_topological_sort]:
223
+
224
+ def runtime_error():
225
+ DG = nx.DiGraph([(1, 2), (2, 3), (3, 4)])
226
+ first = True
227
+ for x in algorithm(DG):
228
+ if first:
229
+ first = False
230
+ DG.add_edge(5 - x, 5)
231
+
232
+ def unfeasible_error():
233
+ DG = nx.DiGraph([(1, 2), (2, 3), (3, 4)])
234
+ first = True
235
+ for x in algorithm(DG):
236
+ if first:
237
+ first = False
238
+ DG.remove_node(4)
239
+
240
+ def runtime_error2():
241
+ DG = nx.DiGraph([(1, 2), (2, 3), (3, 4)])
242
+ first = True
243
+ for x in algorithm(DG):
244
+ if first:
245
+ first = False
246
+ DG.remove_node(2)
247
+
248
+ pytest.raises(RuntimeError, runtime_error)
249
+ pytest.raises(RuntimeError, runtime_error2)
250
+ pytest.raises(nx.NetworkXUnfeasible, unfeasible_error)
251
+
252
+ def test_all_topological_sorts_1(self):
253
+ DG = nx.DiGraph([(1, 2), (2, 3), (3, 4), (4, 5)])
254
+ assert list(nx.all_topological_sorts(DG)) == [[1, 2, 3, 4, 5]]
255
+
256
+ def test_all_topological_sorts_2(self):
257
+ DG = nx.DiGraph([(1, 3), (2, 1), (2, 4), (4, 3), (4, 5)])
258
+ assert sorted(nx.all_topological_sorts(DG)) == [
259
+ [2, 1, 4, 3, 5],
260
+ [2, 1, 4, 5, 3],
261
+ [2, 4, 1, 3, 5],
262
+ [2, 4, 1, 5, 3],
263
+ [2, 4, 5, 1, 3],
264
+ ]
265
+
266
+ def test_all_topological_sorts_3(self):
267
+ def unfeasible():
268
+ DG = nx.DiGraph([(1, 2), (2, 3), (3, 4), (4, 2), (4, 5)])
269
+ # convert to list to execute generator
270
+ list(nx.all_topological_sorts(DG))
271
+
272
+ def not_implemented():
273
+ G = nx.Graph([(1, 2), (2, 3)])
274
+ # convert to list to execute generator
275
+ list(nx.all_topological_sorts(G))
276
+
277
+ def not_implemented_2():
278
+ G = nx.MultiGraph([(1, 2), (1, 2), (2, 3)])
279
+ list(nx.all_topological_sorts(G))
280
+
281
+ pytest.raises(nx.NetworkXUnfeasible, unfeasible)
282
+ pytest.raises(nx.NetworkXNotImplemented, not_implemented)
283
+ pytest.raises(nx.NetworkXNotImplemented, not_implemented_2)
284
+
285
+ def test_all_topological_sorts_4(self):
286
+ DG = nx.DiGraph()
287
+ for i in range(7):
288
+ DG.add_node(i)
289
+ assert sorted(map(list, permutations(DG.nodes))) == sorted(
290
+ nx.all_topological_sorts(DG)
291
+ )
292
+
293
+ def test_all_topological_sorts_multigraph_1(self):
294
+ DG = nx.MultiDiGraph([(1, 2), (1, 2), (2, 3), (3, 4), (3, 5), (3, 5), (3, 5)])
295
+ assert sorted(nx.all_topological_sorts(DG)) == sorted(
296
+ [[1, 2, 3, 4, 5], [1, 2, 3, 5, 4]]
297
+ )
298
+
299
+ def test_all_topological_sorts_multigraph_2(self):
300
+ N = 9
301
+ edges = []
302
+ for i in range(1, N):
303
+ edges.extend([(i, i + 1)] * i)
304
+ DG = nx.MultiDiGraph(edges)
305
+ assert list(nx.all_topological_sorts(DG)) == [list(range(1, N + 1))]
306
+
307
+ def test_ancestors(self):
308
+ G = nx.DiGraph()
309
+ ancestors = nx.algorithms.dag.ancestors
310
+ G.add_edges_from([(1, 2), (1, 3), (4, 2), (4, 3), (4, 5), (2, 6), (5, 6)])
311
+ assert ancestors(G, 6) == {1, 2, 4, 5}
312
+ assert ancestors(G, 3) == {1, 4}
313
+ assert ancestors(G, 1) == set()
314
+ pytest.raises(nx.NetworkXError, ancestors, G, 8)
315
+
316
+ def test_descendants(self):
317
+ G = nx.DiGraph()
318
+ descendants = nx.algorithms.dag.descendants
319
+ G.add_edges_from([(1, 2), (1, 3), (4, 2), (4, 3), (4, 5), (2, 6), (5, 6)])
320
+ assert descendants(G, 1) == {2, 3, 6}
321
+ assert descendants(G, 4) == {2, 3, 5, 6}
322
+ assert descendants(G, 3) == set()
323
+ pytest.raises(nx.NetworkXError, descendants, G, 8)
324
+
325
+ def test_transitive_closure(self):
326
+ G = nx.DiGraph([(1, 2), (2, 3), (3, 4)])
327
+ solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
328
+ assert edges_equal(nx.transitive_closure(G).edges(), solution, directed=True)
329
+ G = nx.DiGraph([(1, 2), (2, 3), (2, 4)])
330
+ solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4)]
331
+ assert edges_equal(nx.transitive_closure(G).edges(), solution, directed=True)
332
+ G = nx.DiGraph([(1, 2), (2, 3), (3, 1)])
333
+ solution = [(1, 2), (2, 1), (2, 3), (3, 2), (1, 3), (3, 1)]
334
+ soln = sorted(solution + [(n, n) for n in G])
335
+ assert edges_equal(
336
+ sorted(nx.transitive_closure(G).edges()), soln, directed=True
337
+ )
338
+
339
+ G = nx.Graph([(1, 2), (2, 3), (3, 4)])
340
+ solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
341
+ assert edges_equal(sorted(nx.transitive_closure(G).edges()), solution)
342
+
343
+ G = nx.MultiGraph([(1, 2), (2, 3), (3, 4)])
344
+ solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
345
+ assert edges_equal(sorted(nx.transitive_closure(G).edges()), solution)
346
+
347
+ G = nx.MultiDiGraph([(1, 2), (2, 3), (3, 4)])
348
+ solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
349
+ assert edges_equal(
350
+ sorted(nx.transitive_closure(G).edges()), solution, directed=True
351
+ )
352
+
353
+ # test if edge data is copied
354
+ G = nx.DiGraph([(1, 2, {"a": 3}), (2, 3, {"b": 0}), (3, 4)])
355
+ H = nx.transitive_closure(G)
356
+ for u, v in G.edges():
357
+ assert G.get_edge_data(u, v) == H.get_edge_data(u, v)
358
+
359
+ k = 10
360
+ G = nx.DiGraph((i, i + 1, {"f": "b", "weight": i}) for i in range(k))
361
+ H = nx.transitive_closure(G)
362
+ for u, v in G.edges():
363
+ assert G.get_edge_data(u, v) == H.get_edge_data(u, v)
364
+
365
+ G = nx.Graph()
366
+ with pytest.raises(nx.NetworkXError):
367
+ nx.transitive_closure(G, reflexive="wrong input")
368
+
369
+ def test_reflexive_transitive_closure(self):
370
+ G = nx.DiGraph([(1, 2), (2, 3), (3, 4)])
371
+ solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
372
+ soln = sorted(solution + [(n, n) for n in G])
373
+ assert edges_equal(nx.transitive_closure(G).edges(), solution, directed=True)
374
+ assert edges_equal(
375
+ nx.transitive_closure(G, False).edges(), solution, directed=True
376
+ )
377
+ assert edges_equal(nx.transitive_closure(G, True).edges(), soln, directed=True)
378
+ assert edges_equal(
379
+ nx.transitive_closure(G, None).edges(), solution, directed=True
380
+ )
381
+
382
+ G = nx.DiGraph([(1, 2), (2, 3), (2, 4)])
383
+ solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4)]
384
+ soln = sorted(solution + [(n, n) for n in G])
385
+ assert edges_equal(nx.transitive_closure(G).edges(), solution, directed=True)
386
+ assert edges_equal(
387
+ nx.transitive_closure(G, False).edges(), solution, directed=True
388
+ )
389
+ assert edges_equal(nx.transitive_closure(G, True).edges(), soln, directed=True)
390
+ assert edges_equal(
391
+ nx.transitive_closure(G, None).edges(), solution, directed=True
392
+ )
393
+
394
+ G = nx.DiGraph([(1, 2), (2, 3), (3, 1)])
395
+ solution = sorted([(1, 2), (2, 1), (2, 3), (3, 2), (1, 3), (3, 1)])
396
+ soln = sorted(solution + [(n, n) for n in G])
397
+ assert edges_equal(
398
+ sorted(nx.transitive_closure(G).edges()), soln, directed=True
399
+ )
400
+ assert edges_equal(
401
+ sorted(nx.transitive_closure(G, False).edges()), soln, directed=True
402
+ )
403
+ assert edges_equal(
404
+ sorted(nx.transitive_closure(G, None).edges()), solution, directed=True
405
+ )
406
+ assert edges_equal(
407
+ sorted(nx.transitive_closure(G, True).edges()), soln, directed=True
408
+ )
409
+
410
+ G = nx.Graph([(1, 2), (2, 3), (3, 4)])
411
+ solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
412
+ soln = sorted(solution + [(n, n) for n in G])
413
+ assert edges_equal(nx.transitive_closure(G).edges(), solution)
414
+ assert edges_equal(nx.transitive_closure(G, False).edges(), solution)
415
+ assert edges_equal(nx.transitive_closure(G, True).edges(), soln)
416
+ assert edges_equal(nx.transitive_closure(G, None).edges(), solution)
417
+
418
+ G = nx.MultiGraph([(1, 2), (2, 3), (3, 4)])
419
+ solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
420
+ soln = sorted(solution + [(n, n) for n in G])
421
+ assert edges_equal(nx.transitive_closure(G).edges(), solution)
422
+ assert edges_equal(nx.transitive_closure(G, False).edges(), solution)
423
+ assert edges_equal(nx.transitive_closure(G, True).edges(), soln)
424
+ assert edges_equal(nx.transitive_closure(G, None).edges(), solution)
425
+
426
+ G = nx.MultiDiGraph([(1, 2), (2, 3), (3, 4)])
427
+ solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
428
+ soln = sorted(solution + [(n, n) for n in G])
429
+ assert edges_equal(nx.transitive_closure(G).edges(), solution, directed=True)
430
+ assert edges_equal(
431
+ nx.transitive_closure(G, False).edges(), solution, directed=True
432
+ )
433
+ assert edges_equal(nx.transitive_closure(G, True).edges(), soln, directed=True)
434
+ assert edges_equal(
435
+ nx.transitive_closure(G, None).edges(), solution, directed=True
436
+ )
437
+
438
+ def test_transitive_closure_dag(self):
439
+ G = nx.DiGraph([(1, 2), (2, 3), (3, 4)])
440
+ transitive_closure = nx.algorithms.dag.transitive_closure_dag
441
+ solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
442
+ assert edges_equal(transitive_closure(G).edges(), solution, directed=True)
443
+ G = nx.DiGraph([(1, 2), (2, 3), (2, 4)])
444
+ solution = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4)]
445
+ assert edges_equal(transitive_closure(G).edges(), solution, directed=True)
446
+ G = nx.Graph([(1, 2), (2, 3), (3, 4)])
447
+ pytest.raises(nx.NetworkXNotImplemented, transitive_closure, G)
448
+
449
+ # test if edge data is copied
450
+ G = nx.DiGraph([(1, 2, {"a": 3}), (2, 3, {"b": 0}), (3, 4)])
451
+ H = transitive_closure(G)
452
+ for u, v in G.edges():
453
+ assert G.get_edge_data(u, v) == H.get_edge_data(u, v)
454
+
455
+ k = 10
456
+ G = nx.DiGraph((i, i + 1, {"foo": "bar", "weight": i}) for i in range(k))
457
+ H = transitive_closure(G)
458
+ for u, v in G.edges():
459
+ assert G.get_edge_data(u, v) == H.get_edge_data(u, v)
460
+
461
+ def test_transitive_reduction(self):
462
+ G = nx.DiGraph([(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)])
463
+ transitive_reduction = nx.algorithms.dag.transitive_reduction
464
+ solution = [(1, 2), (2, 3), (3, 4)]
465
+ assert edges_equal(transitive_reduction(G).edges(), solution, directed=True)
466
+ G = nx.DiGraph([(1, 2), (1, 3), (1, 4), (2, 3), (2, 4)])
467
+ transitive_reduction = nx.algorithms.dag.transitive_reduction
468
+ solution = [(1, 2), (2, 3), (2, 4)]
469
+ assert edges_equal(transitive_reduction(G).edges(), solution, directed=True)
470
+ G = nx.Graph([(1, 2), (2, 3), (3, 4)])
471
+ pytest.raises(nx.NetworkXNotImplemented, transitive_reduction, G)
472
+
473
+ def _check_antichains(self, solution, result):
474
+ sol = [frozenset(a) for a in solution]
475
+ res = [frozenset(a) for a in result]
476
+ assert set(sol) == set(res)
477
+
478
+ def test_antichains(self):
479
+ antichains = nx.algorithms.dag.antichains
480
+ G = nx.DiGraph([(1, 2), (2, 3), (3, 4)])
481
+ solution = [[], [4], [3], [2], [1]]
482
+ self._check_antichains(list(antichains(G)), solution)
483
+ G = nx.DiGraph([(1, 2), (2, 3), (2, 4), (3, 5), (5, 6), (5, 7)])
484
+ solution = [
485
+ [],
486
+ [4],
487
+ [7],
488
+ [7, 4],
489
+ [6],
490
+ [6, 4],
491
+ [6, 7],
492
+ [6, 7, 4],
493
+ [5],
494
+ [5, 4],
495
+ [3],
496
+ [3, 4],
497
+ [2],
498
+ [1],
499
+ ]
500
+ self._check_antichains(list(antichains(G)), solution)
501
+ G = nx.DiGraph([(1, 2), (1, 3), (3, 4), (3, 5), (5, 6)])
502
+ solution = [
503
+ [],
504
+ [6],
505
+ [5],
506
+ [4],
507
+ [4, 6],
508
+ [4, 5],
509
+ [3],
510
+ [2],
511
+ [2, 6],
512
+ [2, 5],
513
+ [2, 4],
514
+ [2, 4, 6],
515
+ [2, 4, 5],
516
+ [2, 3],
517
+ [1],
518
+ ]
519
+ self._check_antichains(list(antichains(G)), solution)
520
+ G = nx.DiGraph({0: [1, 2], 1: [4], 2: [3], 3: [4]})
521
+ solution = [[], [4], [3], [2], [1], [1, 3], [1, 2], [0]]
522
+ self._check_antichains(list(antichains(G)), solution)
523
+ G = nx.DiGraph()
524
+ self._check_antichains(list(antichains(G)), [[]])
525
+ G = nx.DiGraph()
526
+ G.add_nodes_from([0, 1, 2])
527
+ solution = [[], [0], [1], [1, 0], [2], [2, 0], [2, 1], [2, 1, 0]]
528
+ self._check_antichains(list(antichains(G)), solution)
529
+
530
+ def f(x):
531
+ return list(antichains(x))
532
+
533
+ G = nx.Graph([(1, 2), (2, 3), (3, 4)])
534
+ pytest.raises(nx.NetworkXNotImplemented, f, G)
535
+ G = nx.DiGraph([(1, 2), (2, 3), (3, 1)])
536
+ pytest.raises(nx.NetworkXUnfeasible, f, G)
537
+
538
+ def test_lexicographical_topological_sort(self):
539
+ G = nx.DiGraph([(1, 2), (2, 3), (1, 4), (1, 5), (2, 6)])
540
+ assert list(nx.lexicographical_topological_sort(G)) == [1, 2, 3, 4, 5, 6]
541
+ assert list(nx.lexicographical_topological_sort(G, key=lambda x: x)) == [
542
+ 1,
543
+ 2,
544
+ 3,
545
+ 4,
546
+ 5,
547
+ 6,
548
+ ]
549
+ assert list(nx.lexicographical_topological_sort(G, key=lambda x: -x)) == [
550
+ 1,
551
+ 5,
552
+ 4,
553
+ 2,
554
+ 6,
555
+ 3,
556
+ ]
557
+
558
+ def test_lexicographical_topological_sort2(self):
559
+ """
560
+ Check the case of two or more nodes with same key value.
561
+ Want to avoid exception raised due to comparing nodes directly.
562
+ See Issue #3493
563
+ """
564
+
565
+ class Test_Node:
566
+ def __init__(self, n):
567
+ self.label = n
568
+ self.priority = 1
569
+
570
+ def __repr__(self):
571
+ return f"Node({self.label})"
572
+
573
+ def sorting_key(node):
574
+ return node.priority
575
+
576
+ test_nodes = [Test_Node(n) for n in range(4)]
577
+ G = nx.DiGraph()
578
+ edges = [(0, 1), (0, 2), (0, 3), (2, 3)]
579
+ G.add_edges_from((test_nodes[a], test_nodes[b]) for a, b in edges)
580
+
581
+ sorting = list(nx.lexicographical_topological_sort(G, key=sorting_key))
582
+ assert sorting == test_nodes
583
+
584
+
585
+ def test_topological_generations():
586
+ G = nx.DiGraph(
587
+ {1: [2, 3], 2: [4, 5], 3: [7], 4: [], 5: [6, 7], 6: [], 7: []}
588
+ ).reverse()
589
+ # order within each generation is inconsequential
590
+ generations = [sorted(gen) for gen in nx.topological_generations(G)]
591
+ expected = [[4, 6, 7], [3, 5], [2], [1]]
592
+ assert generations == expected
593
+
594
+ MG = nx.MultiDiGraph(G.edges)
595
+ MG.add_edge(2, 1)
596
+ generations = [sorted(gen) for gen in nx.topological_generations(MG)]
597
+ assert generations == expected
598
+
599
+
600
+ def test_topological_generations_empty():
601
+ G = nx.DiGraph()
602
+ assert list(nx.topological_generations(G)) == []
603
+
604
+
605
+ def test_topological_generations_cycle():
606
+ G = nx.DiGraph([[2, 1], [3, 1], [1, 2]])
607
+ with pytest.raises(nx.NetworkXUnfeasible):
608
+ list(nx.topological_generations(G))
609
+
610
+
611
+ def test_is_aperiodic_cycle():
612
+ G = nx.DiGraph()
613
+ nx.add_cycle(G, [1, 2, 3, 4])
614
+ assert not nx.is_aperiodic(G)
615
+
616
+
617
+ def test_is_aperiodic_cycle2():
618
+ G = nx.DiGraph()
619
+ nx.add_cycle(G, [1, 2, 3, 4])
620
+ nx.add_cycle(G, [3, 4, 5, 6, 7])
621
+ assert nx.is_aperiodic(G)
622
+
623
+
624
+ def test_is_aperiodic_cycle3():
625
+ G = nx.DiGraph()
626
+ nx.add_cycle(G, [1, 2, 3, 4])
627
+ nx.add_cycle(G, [3, 4, 5, 6])
628
+ assert not nx.is_aperiodic(G)
629
+
630
+
631
+ def test_is_aperiodic_cycle4():
632
+ G = nx.DiGraph()
633
+ nx.add_cycle(G, [1, 2, 3, 4])
634
+ G.add_edge(1, 3)
635
+ assert nx.is_aperiodic(G)
636
+
637
+
638
+ def test_is_aperiodic_selfloop():
639
+ G = nx.DiGraph()
640
+ nx.add_cycle(G, [1, 2, 3, 4])
641
+ G.add_edge(1, 1)
642
+ assert nx.is_aperiodic(G)
643
+
644
+
645
+ def test_is_aperiodic_null_graph_raises():
646
+ G = nx.DiGraph()
647
+ pytest.raises(nx.NetworkXPointlessConcept, nx.is_aperiodic, G)
648
+
649
+
650
+ def test_is_aperiodic_undirected_raises():
651
+ G = nx.Graph([(1, 2), (2, 3), (3, 1)])
652
+ pytest.raises(nx.NetworkXError, nx.is_aperiodic, G)
653
+
654
+
655
+ def test_is_aperiodic_disconnected_raises():
656
+ G = nx.DiGraph()
657
+ nx.add_cycle(G, [0, 1, 2])
658
+ G.add_edge(3, 3)
659
+ pytest.raises(nx.NetworkXError, nx.is_aperiodic, G)
660
+
661
+
662
+ def test_is_aperiodic_weakly_connected_raises():
663
+ G = nx.DiGraph([(1, 2), (2, 3)])
664
+ pytest.raises(nx.NetworkXError, nx.is_aperiodic, G)
665
+
666
+
667
+ def test_is_aperiodic_empty_graph():
668
+ G = nx.empty_graph(create_using=nx.DiGraph)
669
+ with pytest.raises(nx.NetworkXPointlessConcept, match="Graph has no nodes."):
670
+ nx.is_aperiodic(G)
671
+
672
+
673
+ def test_is_aperiodic_bipartite():
674
+ # Bipartite graph
675
+ G = nx.DiGraph(nx.davis_southern_women_graph())
676
+ assert not nx.is_aperiodic(G)
677
+
678
+
679
+ def test_is_aperiodic_single_node():
680
+ G = nx.DiGraph()
681
+ G.add_node(0)
682
+ assert not nx.is_aperiodic(G)
683
+ G.add_edge(0, 0)
684
+ assert nx.is_aperiodic(G)
685
+
686
+
687
+ class TestDagToBranching:
688
+ """Unit tests for the :func:`networkx.dag_to_branching` function."""
689
+
690
+ def test_single_root(self):
691
+ """Tests that a directed acyclic graph with a single degree
692
+ zero node produces an arborescence.
693
+
694
+ """
695
+ G = nx.DiGraph([(0, 1), (0, 2), (1, 3), (2, 3)])
696
+ B = nx.dag_to_branching(G)
697
+ expected = nx.DiGraph([(0, 1), (1, 3), (0, 2), (2, 4)])
698
+ assert nx.is_arborescence(B)
699
+ assert nx.is_isomorphic(B, expected)
700
+
701
+ def test_multiple_roots(self):
702
+ """Tests that a directed acyclic graph with multiple degree zero
703
+ nodes creates an arborescence with multiple (weakly) connected
704
+ components.
705
+
706
+ """
707
+ G = nx.DiGraph([(0, 1), (0, 2), (1, 3), (2, 3), (5, 2)])
708
+ B = nx.dag_to_branching(G)
709
+ expected = nx.DiGraph([(0, 1), (1, 3), (0, 2), (2, 4), (5, 6), (6, 7)])
710
+ assert nx.is_branching(B)
711
+ assert not nx.is_arborescence(B)
712
+ assert nx.is_isomorphic(B, expected)
713
+
714
+ # # Attributes are not copied by this function. If they were, this would
715
+ # # be a good test to uncomment.
716
+ # def test_copy_attributes(self):
717
+ # """Tests that node attributes are copied in the branching."""
718
+ # G = nx.DiGraph([(0, 1), (0, 2), (1, 3), (2, 3)])
719
+ # for v in G:
720
+ # G.node[v]['label'] = str(v)
721
+ # B = nx.dag_to_branching(G)
722
+ # # Determine the root node of the branching.
723
+ # root = next(v for v, d in B.in_degree() if d == 0)
724
+ # assert_equal(B.node[root]['label'], '0')
725
+ # children = B[root]
726
+ # # Get the left and right children, nodes 1 and 2, respectively.
727
+ # left, right = sorted(children, key=lambda v: B.node[v]['label'])
728
+ # assert_equal(B.node[left]['label'], '1')
729
+ # assert_equal(B.node[right]['label'], '2')
730
+ # # Get the left grandchild.
731
+ # children = B[left]
732
+ # assert_equal(len(children), 1)
733
+ # left_grandchild = arbitrary_element(children)
734
+ # assert_equal(B.node[left_grandchild]['label'], '3')
735
+ # # Get the right grandchild.
736
+ # children = B[right]
737
+ # assert_equal(len(children), 1)
738
+ # right_grandchild = arbitrary_element(children)
739
+ # assert_equal(B.node[right_grandchild]['label'], '3')
740
+
741
+ def test_already_arborescence(self):
742
+ """Tests that a directed acyclic graph that is already an
743
+ arborescence produces an isomorphic arborescence as output.
744
+
745
+ """
746
+ A = nx.balanced_tree(2, 2, create_using=nx.DiGraph())
747
+ B = nx.dag_to_branching(A)
748
+ assert nx.is_isomorphic(A, B)
749
+
750
+ def test_already_branching(self):
751
+ """Tests that a directed acyclic graph that is already a
752
+ branching produces an isomorphic branching as output.
753
+
754
+ """
755
+ T1 = nx.balanced_tree(2, 2, create_using=nx.DiGraph())
756
+ T2 = nx.balanced_tree(2, 2, create_using=nx.DiGraph())
757
+ G = nx.disjoint_union(T1, T2)
758
+ B = nx.dag_to_branching(G)
759
+ assert nx.is_isomorphic(G, B)
760
+
761
+ def test_not_acyclic(self):
762
+ """Tests that a non-acyclic graph causes an exception."""
763
+ with pytest.raises(nx.HasACycle):
764
+ G = nx.DiGraph(pairwise("abc", cyclic=True))
765
+ nx.dag_to_branching(G)
766
+
767
+ def test_undirected(self):
768
+ with pytest.raises(nx.NetworkXNotImplemented):
769
+ nx.dag_to_branching(nx.Graph())
770
+
771
+ def test_multigraph(self):
772
+ with pytest.raises(nx.NetworkXNotImplemented):
773
+ nx.dag_to_branching(nx.MultiGraph())
774
+
775
+ def test_multidigraph(self):
776
+ with pytest.raises(nx.NetworkXNotImplemented):
777
+ nx.dag_to_branching(nx.MultiDiGraph())
778
+
779
+
780
+ def test_ancestors_descendants_undirected():
781
+ """Regression test to ensure ancestors and descendants work as expected on
782
+ undirected graphs."""
783
+ G = nx.path_graph(5)
784
+ nx.ancestors(G, 2) == nx.descendants(G, 2) == {0, 1, 3, 4}
785
+
786
+
787
+ def test_v_structures_raise():
788
+ G = nx.Graph()
789
+ with pytest.raises(nx.NetworkXNotImplemented, match="for undirected type"):
790
+ nx.dag.v_structures(G)
791
+
792
+
793
+ @pytest.mark.parametrize(
794
+ ("edgelist", "expected"),
795
+ (
796
+ (
797
+ [(0, 1), (0, 2), (3, 2)],
798
+ {(0, 2, 3)},
799
+ ),
800
+ (
801
+ [("A", "B"), ("C", "B"), ("D", "G"), ("D", "E"), ("G", "E")],
802
+ {("A", "B", "C")},
803
+ ),
804
+ ([(0, 1), (2, 1), (0, 2)], set()), # adjacent parents case: see gh-7385
805
+ ),
806
+ )
807
+ def test_v_structures(edgelist, expected):
808
+ G = nx.DiGraph(edgelist)
809
+ v_structs = set(nx.dag.v_structures(G))
810
+ assert v_structs == expected
811
+
812
+
813
+ def test_colliders_raise():
814
+ G = nx.Graph()
815
+ with pytest.raises(nx.NetworkXNotImplemented, match="for undirected type"):
816
+ nx.dag.colliders(G)
817
+
818
+
819
+ @pytest.mark.parametrize(
820
+ ("edgelist", "expected"),
821
+ (
822
+ (
823
+ [(0, 1), (0, 2), (3, 2)],
824
+ {(0, 2, 3)},
825
+ ),
826
+ (
827
+ [("A", "B"), ("C", "B"), ("D", "G"), ("D", "E"), ("G", "E")],
828
+ {("A", "B", "C"), ("D", "E", "G")},
829
+ ),
830
+ ),
831
+ )
832
+ def test_colliders(edgelist, expected):
833
+ G = nx.DiGraph(edgelist)
834
+ colliders = set(nx.dag.colliders(G))
835
+ assert colliders == expected
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_distance_measures.py ADDED
@@ -0,0 +1,831 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import itertools
2
+ import math
3
+ from random import Random
4
+
5
+ import pytest
6
+
7
+ import networkx as nx
8
+ from networkx import convert_node_labels_to_integers as cnlti
9
+ from networkx.algorithms.distance_measures import _extrema_bounding
10
+
11
+
12
+ def test__extrema_bounding_invalid_compute_kwarg():
13
+ G = nx.path_graph(3)
14
+ with pytest.raises(ValueError, match="compute must be one of"):
15
+ _extrema_bounding(G, compute="spam")
16
+
17
+
18
+ class TestDistance:
19
+ def setup_method(self):
20
+ self.G = cnlti(nx.grid_2d_graph(4, 4), first_label=1, ordering="sorted")
21
+
22
+ @pytest.mark.parametrize("seed", list(range(10)))
23
+ @pytest.mark.parametrize("n", list(range(10, 20)))
24
+ @pytest.mark.parametrize("prob", [x / 10 for x in range(0, 10, 2)])
25
+ def test_use_bounds_on_off_consistency(self, seed, n, prob):
26
+ """Test for consistency of distance metrics when using usebounds=True.
27
+
28
+ We validate consistency for `networkx.diameter`, `networkx.radius`, `networkx.periphery`
29
+ and `networkx.center` when passing `usebounds=True`. Expectation is that method
30
+ returns the same result whether we pass usebounds=True or not.
31
+
32
+ For this we generate random connected graphs and validate method returns the same.
33
+ """
34
+ metrics = [nx.diameter, nx.radius, nx.periphery, nx.center]
35
+ max_weight = [5, 10, 1000]
36
+ rng = Random(seed)
37
+ # we compose it with a random tree to ensure graph is connected
38
+ G = nx.compose(
39
+ nx.random_labeled_tree(n, seed=rng),
40
+ nx.erdos_renyi_graph(n, prob, seed=rng),
41
+ )
42
+ for metric in metrics:
43
+ # checking unweighted case
44
+ assert metric(G) == metric(G, usebounds=True)
45
+ for w in max_weight:
46
+ for u, v in G.edges():
47
+ G[u][v]["w"] = rng.randint(0, w)
48
+ # checking weighted case
49
+ assert metric(G, weight="w") == metric(G, weight="w", usebounds=True)
50
+
51
+ def test_eccentricity(self):
52
+ assert nx.eccentricity(self.G, 1) == 6
53
+ e = nx.eccentricity(self.G)
54
+ assert e[1] == 6
55
+
56
+ sp = dict(nx.shortest_path_length(self.G))
57
+ e = nx.eccentricity(self.G, sp=sp)
58
+ assert e[1] == 6
59
+
60
+ e = nx.eccentricity(self.G, v=1)
61
+ assert e == 6
62
+
63
+ # This behavior changed in version 1.8 (ticket #739)
64
+ e = nx.eccentricity(self.G, v=[1, 1])
65
+ assert e[1] == 6
66
+ e = nx.eccentricity(self.G, v=[1, 2])
67
+ assert e[1] == 6
68
+
69
+ # test against graph with one node
70
+ G = nx.path_graph(1)
71
+ e = nx.eccentricity(G)
72
+ assert e[0] == 0
73
+ e = nx.eccentricity(G, v=0)
74
+ assert e == 0
75
+ pytest.raises(nx.NetworkXError, nx.eccentricity, G, 1)
76
+
77
+ # test against empty graph
78
+ G = nx.empty_graph()
79
+ e = nx.eccentricity(G)
80
+ assert e == {}
81
+
82
+ def test_diameter(self):
83
+ assert nx.diameter(self.G) == 6
84
+
85
+ def test_harmonic_diameter(self):
86
+ assert nx.harmonic_diameter(self.G) == pytest.approx(2.0477815699658715)
87
+ assert nx.harmonic_diameter(nx.star_graph(3)) == pytest.approx(1.333333)
88
+
89
+ def test_harmonic_diameter_empty(self):
90
+ assert math.isnan(nx.harmonic_diameter(nx.empty_graph()))
91
+
92
+ def test_harmonic_diameter_single_node(self):
93
+ assert math.isnan(nx.harmonic_diameter(nx.empty_graph(1)))
94
+
95
+ def test_harmonic_diameter_discrete(self):
96
+ assert math.isinf(nx.harmonic_diameter(nx.empty_graph(3)))
97
+
98
+ def test_harmonic_diameter_not_strongly_connected(self):
99
+ DG = nx.DiGraph()
100
+ DG.add_edge(0, 1)
101
+ assert nx.harmonic_diameter(DG) == 2
102
+
103
+ def test_harmonic_diameter_weighted_paths(self):
104
+ G = nx.star_graph(3)
105
+ # check defaults
106
+ G.add_weighted_edges_from([(*e, 1) for i, e in enumerate(G.edges)], "weight")
107
+ assert nx.harmonic_diameter(G) == pytest.approx(1.333333)
108
+ assert nx.harmonic_diameter(G, weight="weight") == pytest.approx(1.333333)
109
+
110
+ # check impact of weights and alternate weight name
111
+ G.add_weighted_edges_from([(*e, i) for i, e in enumerate(G.edges)], "dist")
112
+ assert nx.harmonic_diameter(G, weight="dist") == pytest.approx(1.8)
113
+
114
+ def test_radius(self):
115
+ assert nx.radius(self.G) == 4
116
+
117
+ def test_periphery(self):
118
+ assert set(nx.periphery(self.G)) == {1, 4, 13, 16}
119
+
120
+ def test_center_simple_tree(self):
121
+ G = nx.Graph([(1, 2), (1, 3), (2, 4), (2, 5)])
122
+ assert nx.center(G) == [1, 2]
123
+
124
+ @pytest.mark.parametrize("r", range(2, 5))
125
+ @pytest.mark.parametrize("h", range(1, 5))
126
+ def test_center_balanced_tree(self, r, h):
127
+ G = nx.balanced_tree(r, h)
128
+ assert nx.center(G) == [0]
129
+
130
+ def test_center(self):
131
+ assert set(nx.center(self.G)) == {6, 7, 10, 11}
132
+
133
+ @pytest.mark.parametrize("n", [1, 2, 99, 100])
134
+ def test_center_path_graphs(self, n):
135
+ G = nx.path_graph(n)
136
+ expected = {(n - 1) // 2, math.ceil((n - 1) / 2)}
137
+ assert set(nx.center(G)) == expected
138
+
139
+ def test_bound_diameter(self):
140
+ assert nx.diameter(self.G, usebounds=True) == 6
141
+
142
+ def test_bound_radius(self):
143
+ assert nx.radius(self.G, usebounds=True) == 4
144
+
145
+ def test_bound_periphery(self):
146
+ result = {1, 4, 13, 16}
147
+ assert set(nx.periphery(self.G, usebounds=True)) == result
148
+
149
+ def test_bound_center(self):
150
+ result = {6, 7, 10, 11}
151
+ assert set(nx.center(self.G, usebounds=True)) == result
152
+
153
+ def test_radius_exception(self):
154
+ G = nx.Graph()
155
+ G.add_edge(1, 2)
156
+ G.add_edge(3, 4)
157
+ pytest.raises(nx.NetworkXError, nx.diameter, G)
158
+
159
+ def test_eccentricity_infinite(self):
160
+ with pytest.raises(nx.NetworkXError):
161
+ G = nx.Graph([(1, 2), (3, 4)])
162
+ e = nx.eccentricity(G)
163
+
164
+ def test_eccentricity_undirected_not_connected(self):
165
+ with pytest.raises(nx.NetworkXError):
166
+ G = nx.Graph([(1, 2), (3, 4)])
167
+ e = nx.eccentricity(G, sp=1)
168
+
169
+ def test_eccentricity_directed_weakly_connected(self):
170
+ with pytest.raises(nx.NetworkXError):
171
+ DG = nx.DiGraph([(1, 2), (1, 3)])
172
+ nx.eccentricity(DG)
173
+
174
+
175
+ class TestWeightedDistance:
176
+ def setup_method(self):
177
+ G = nx.Graph()
178
+ G.add_edge(0, 1, weight=0.6, cost=0.6, high_cost=6)
179
+ G.add_edge(0, 2, weight=0.2, cost=0.2, high_cost=2)
180
+ G.add_edge(2, 3, weight=0.1, cost=0.1, high_cost=1)
181
+ G.add_edge(2, 4, weight=0.7, cost=0.7, high_cost=7)
182
+ G.add_edge(2, 5, weight=0.9, cost=0.9, high_cost=9)
183
+ G.add_edge(1, 5, weight=0.3, cost=0.3, high_cost=3)
184
+ self.G = G
185
+ self.weight_fn = lambda v, u, e: 2
186
+
187
+ def test_eccentricity_weight_None(self):
188
+ assert nx.eccentricity(self.G, 1, weight=None) == 3
189
+ e = nx.eccentricity(self.G, weight=None)
190
+ assert e[1] == 3
191
+
192
+ e = nx.eccentricity(self.G, v=1, weight=None)
193
+ assert e == 3
194
+
195
+ # This behavior changed in version 1.8 (ticket #739)
196
+ e = nx.eccentricity(self.G, v=[1, 1], weight=None)
197
+ assert e[1] == 3
198
+ e = nx.eccentricity(self.G, v=[1, 2], weight=None)
199
+ assert e[1] == 3
200
+
201
+ def test_eccentricity_weight_attr(self):
202
+ assert nx.eccentricity(self.G, 1, weight="weight") == 1.5
203
+ e = nx.eccentricity(self.G, weight="weight")
204
+ assert (
205
+ e
206
+ == nx.eccentricity(self.G, weight="cost")
207
+ != nx.eccentricity(self.G, weight="high_cost")
208
+ )
209
+ assert e[1] == 1.5
210
+
211
+ e = nx.eccentricity(self.G, v=1, weight="weight")
212
+ assert e == 1.5
213
+
214
+ # This behavior changed in version 1.8 (ticket #739)
215
+ e = nx.eccentricity(self.G, v=[1, 1], weight="weight")
216
+ assert e[1] == 1.5
217
+ e = nx.eccentricity(self.G, v=[1, 2], weight="weight")
218
+ assert e[1] == 1.5
219
+
220
+ def test_eccentricity_weight_fn(self):
221
+ assert nx.eccentricity(self.G, 1, weight=self.weight_fn) == 6
222
+ e = nx.eccentricity(self.G, weight=self.weight_fn)
223
+ assert e[1] == 6
224
+
225
+ e = nx.eccentricity(self.G, v=1, weight=self.weight_fn)
226
+ assert e == 6
227
+
228
+ # This behavior changed in version 1.8 (ticket #739)
229
+ e = nx.eccentricity(self.G, v=[1, 1], weight=self.weight_fn)
230
+ assert e[1] == 6
231
+ e = nx.eccentricity(self.G, v=[1, 2], weight=self.weight_fn)
232
+ assert e[1] == 6
233
+
234
+ def test_diameter_weight_None(self):
235
+ assert nx.diameter(self.G, weight=None) == 3
236
+
237
+ def test_diameter_weight_attr(self):
238
+ assert (
239
+ nx.diameter(self.G, weight="weight")
240
+ == nx.diameter(self.G, weight="cost")
241
+ == 1.6
242
+ != nx.diameter(self.G, weight="high_cost")
243
+ )
244
+
245
+ def test_diameter_weight_fn(self):
246
+ assert nx.diameter(self.G, weight=self.weight_fn) == 6
247
+
248
+ def test_radius_weight_None(self):
249
+ assert pytest.approx(nx.radius(self.G, weight=None)) == 2
250
+
251
+ def test_radius_weight_attr(self):
252
+ assert (
253
+ pytest.approx(nx.radius(self.G, weight="weight"))
254
+ == pytest.approx(nx.radius(self.G, weight="cost"))
255
+ == 0.9
256
+ != nx.radius(self.G, weight="high_cost")
257
+ )
258
+
259
+ def test_radius_weight_fn(self):
260
+ assert nx.radius(self.G, weight=self.weight_fn) == 4
261
+
262
+ def test_periphery_weight_None(self):
263
+ for v in set(nx.periphery(self.G, weight=None)):
264
+ assert nx.eccentricity(self.G, v, weight=None) == nx.diameter(
265
+ self.G, weight=None
266
+ )
267
+
268
+ def test_periphery_weight_attr(self):
269
+ periphery = set(nx.periphery(self.G, weight="weight"))
270
+ assert (
271
+ periphery
272
+ == set(nx.periphery(self.G, weight="cost"))
273
+ == set(nx.periphery(self.G, weight="high_cost"))
274
+ )
275
+ for v in periphery:
276
+ assert (
277
+ nx.eccentricity(self.G, v, weight="high_cost")
278
+ != nx.eccentricity(self.G, v, weight="weight")
279
+ == nx.eccentricity(self.G, v, weight="cost")
280
+ == nx.diameter(self.G, weight="weight")
281
+ == nx.diameter(self.G, weight="cost")
282
+ != nx.diameter(self.G, weight="high_cost")
283
+ )
284
+ assert nx.eccentricity(self.G, v, weight="high_cost") == nx.diameter(
285
+ self.G, weight="high_cost"
286
+ )
287
+
288
+ def test_periphery_weight_fn(self):
289
+ for v in set(nx.periphery(self.G, weight=self.weight_fn)):
290
+ assert nx.eccentricity(self.G, v, weight=self.weight_fn) == nx.diameter(
291
+ self.G, weight=self.weight_fn
292
+ )
293
+
294
+ def test_center_weight_None(self):
295
+ for v in set(nx.center(self.G, weight=None)):
296
+ assert pytest.approx(nx.eccentricity(self.G, v, weight=None)) == nx.radius(
297
+ self.G, weight=None
298
+ )
299
+
300
+ def test_center_weight_attr(self):
301
+ center = set(nx.center(self.G, weight="weight"))
302
+ assert (
303
+ center
304
+ == set(nx.center(self.G, weight="cost"))
305
+ != set(nx.center(self.G, weight="high_cost"))
306
+ )
307
+ for v in center:
308
+ assert (
309
+ nx.eccentricity(self.G, v, weight="high_cost")
310
+ != pytest.approx(nx.eccentricity(self.G, v, weight="weight"))
311
+ == pytest.approx(nx.eccentricity(self.G, v, weight="cost"))
312
+ == nx.radius(self.G, weight="weight")
313
+ == nx.radius(self.G, weight="cost")
314
+ != nx.radius(self.G, weight="high_cost")
315
+ )
316
+ assert nx.eccentricity(self.G, v, weight="high_cost") == nx.radius(
317
+ self.G, weight="high_cost"
318
+ )
319
+
320
+ def test_center_weight_fn(self):
321
+ for v in set(nx.center(self.G, weight=self.weight_fn)):
322
+ assert nx.eccentricity(self.G, v, weight=self.weight_fn) == nx.radius(
323
+ self.G, weight=self.weight_fn
324
+ )
325
+
326
+ def test_bound_diameter_weight_None(self):
327
+ assert nx.diameter(self.G, usebounds=True, weight=None) == 3
328
+
329
+ def test_bound_diameter_weight_attr(self):
330
+ assert (
331
+ nx.diameter(self.G, usebounds=True, weight="high_cost")
332
+ != nx.diameter(self.G, usebounds=True, weight="weight")
333
+ == nx.diameter(self.G, usebounds=True, weight="cost")
334
+ == 1.6
335
+ != nx.diameter(self.G, usebounds=True, weight="high_cost")
336
+ )
337
+ assert nx.diameter(self.G, usebounds=True, weight="high_cost") == nx.diameter(
338
+ self.G, usebounds=True, weight="high_cost"
339
+ )
340
+
341
+ def test_bound_diameter_weight_fn(self):
342
+ assert nx.diameter(self.G, usebounds=True, weight=self.weight_fn) == 6
343
+
344
+ def test_bound_radius_weight_None(self):
345
+ assert pytest.approx(nx.radius(self.G, usebounds=True, weight=None)) == 2
346
+
347
+ def test_bound_radius_weight_attr(self):
348
+ assert (
349
+ nx.radius(self.G, usebounds=True, weight="high_cost")
350
+ != pytest.approx(nx.radius(self.G, usebounds=True, weight="weight"))
351
+ == pytest.approx(nx.radius(self.G, usebounds=True, weight="cost"))
352
+ == 0.9
353
+ != nx.radius(self.G, usebounds=True, weight="high_cost")
354
+ )
355
+ assert nx.radius(self.G, usebounds=True, weight="high_cost") == nx.radius(
356
+ self.G, usebounds=True, weight="high_cost"
357
+ )
358
+
359
+ def test_bound_radius_weight_fn(self):
360
+ assert nx.radius(self.G, usebounds=True, weight=self.weight_fn) == 4
361
+
362
+ def test_bound_periphery_weight_None(self):
363
+ result = {1, 3, 4}
364
+ assert set(nx.periphery(self.G, usebounds=True, weight=None)) == result
365
+
366
+ def test_bound_periphery_weight_attr(self):
367
+ result = {4, 5}
368
+ assert (
369
+ set(nx.periphery(self.G, usebounds=True, weight="weight"))
370
+ == set(nx.periphery(self.G, usebounds=True, weight="cost"))
371
+ == result
372
+ )
373
+
374
+ def test_bound_periphery_weight_fn(self):
375
+ result = {1, 3, 4}
376
+ assert (
377
+ set(nx.periphery(self.G, usebounds=True, weight=self.weight_fn)) == result
378
+ )
379
+
380
+ def test_bound_center_weight_None(self):
381
+ result = {0, 2, 5}
382
+ assert set(nx.center(self.G, usebounds=True, weight=None)) == result
383
+
384
+ def test_bound_center_weight_attr(self):
385
+ result = {0}
386
+ assert (
387
+ set(nx.center(self.G, usebounds=True, weight="weight"))
388
+ == set(nx.center(self.G, usebounds=True, weight="cost"))
389
+ == result
390
+ )
391
+
392
+ def test_bound_center_weight_fn(self):
393
+ result = {0, 2, 5}
394
+ assert set(nx.center(self.G, usebounds=True, weight=self.weight_fn)) == result
395
+
396
+
397
+ class TestResistanceDistance:
398
+ @classmethod
399
+ def setup_class(cls):
400
+ global np
401
+ np = pytest.importorskip("numpy")
402
+ sp = pytest.importorskip("scipy")
403
+
404
+ def setup_method(self):
405
+ G = nx.Graph()
406
+ G.add_edge(1, 2, weight=2)
407
+ G.add_edge(2, 3, weight=4)
408
+ G.add_edge(3, 4, weight=1)
409
+ G.add_edge(1, 4, weight=3)
410
+ self.G = G
411
+
412
+ def test_resistance_distance_directed_graph(self):
413
+ G = nx.DiGraph()
414
+ with pytest.raises(nx.NetworkXNotImplemented):
415
+ nx.resistance_distance(G)
416
+
417
+ def test_resistance_distance_empty(self):
418
+ G = nx.Graph()
419
+ with pytest.raises(nx.NetworkXError):
420
+ nx.resistance_distance(G)
421
+
422
+ def test_resistance_distance_not_connected(self):
423
+ with pytest.raises(nx.NetworkXError):
424
+ self.G.add_node(5)
425
+ nx.resistance_distance(self.G, 1, 5)
426
+
427
+ def test_resistance_distance_nodeA_not_in_graph(self):
428
+ with pytest.raises(nx.NetworkXError):
429
+ nx.resistance_distance(self.G, 9, 1)
430
+
431
+ def test_resistance_distance_nodeB_not_in_graph(self):
432
+ with pytest.raises(nx.NetworkXError):
433
+ nx.resistance_distance(self.G, 1, 9)
434
+
435
+ def test_resistance_distance(self):
436
+ rd = nx.resistance_distance(self.G, 1, 3, "weight", True)
437
+ test_data = 1 / (1 / (2 + 4) + 1 / (1 + 3))
438
+ assert round(rd, 5) == round(test_data, 5)
439
+
440
+ def test_resistance_distance_noinv(self):
441
+ rd = nx.resistance_distance(self.G, 1, 3, "weight", False)
442
+ test_data = 1 / (1 / (1 / 2 + 1 / 4) + 1 / (1 / 1 + 1 / 3))
443
+ assert round(rd, 5) == round(test_data, 5)
444
+
445
+ def test_resistance_distance_no_weight(self):
446
+ rd = nx.resistance_distance(self.G, 1, 3)
447
+ assert round(rd, 5) == 1
448
+
449
+ def test_resistance_distance_neg_weight(self):
450
+ self.G[2][3]["weight"] = -4
451
+ rd = nx.resistance_distance(self.G, 1, 3, "weight", True)
452
+ test_data = 1 / (1 / (2 + -4) + 1 / (1 + 3))
453
+ assert round(rd, 5) == round(test_data, 5)
454
+
455
+ def test_multigraph(self):
456
+ G = nx.MultiGraph()
457
+ G.add_edge(1, 2, weight=2)
458
+ G.add_edge(2, 3, weight=4)
459
+ G.add_edge(3, 4, weight=1)
460
+ G.add_edge(1, 4, weight=3)
461
+ rd = nx.resistance_distance(G, 1, 3, "weight", True)
462
+ assert np.isclose(rd, 1 / (1 / (2 + 4) + 1 / (1 + 3)))
463
+
464
+ def test_resistance_distance_div0(self):
465
+ with pytest.raises(ZeroDivisionError):
466
+ self.G[1][2]["weight"] = 0
467
+ nx.resistance_distance(self.G, 1, 3, "weight")
468
+
469
+ def test_resistance_distance_same_node(self):
470
+ assert nx.resistance_distance(self.G, 1, 1) == 0
471
+
472
+ def test_resistance_distance_only_nodeA(self):
473
+ rd = nx.resistance_distance(self.G, nodeA=1)
474
+ test_data = {}
475
+ test_data[1] = 0
476
+ test_data[2] = 0.75
477
+ test_data[3] = 1
478
+ test_data[4] = 0.75
479
+ assert isinstance(rd, dict)
480
+ assert sorted(rd.keys()) == sorted(test_data.keys())
481
+ for key in rd:
482
+ assert np.isclose(rd[key], test_data[key])
483
+
484
+ def test_resistance_distance_only_nodeB(self):
485
+ rd = nx.resistance_distance(self.G, nodeB=1)
486
+ test_data = {}
487
+ test_data[1] = 0
488
+ test_data[2] = 0.75
489
+ test_data[3] = 1
490
+ test_data[4] = 0.75
491
+ assert isinstance(rd, dict)
492
+ assert sorted(rd.keys()) == sorted(test_data.keys())
493
+ for key in rd:
494
+ assert np.isclose(rd[key], test_data[key])
495
+
496
+ def test_resistance_distance_all(self):
497
+ rd = nx.resistance_distance(self.G)
498
+ assert isinstance(rd, dict)
499
+ assert round(rd[1][3], 5) == 1
500
+
501
+
502
+ class TestEffectiveGraphResistance:
503
+ @classmethod
504
+ def setup_class(cls):
505
+ global np
506
+ np = pytest.importorskip("numpy")
507
+ sp = pytest.importorskip("scipy")
508
+
509
+ def setup_method(self):
510
+ G = nx.Graph()
511
+ G.add_edge(1, 2, weight=2)
512
+ G.add_edge(1, 3, weight=1)
513
+ G.add_edge(2, 3, weight=4)
514
+ self.G = G
515
+
516
+ def test_effective_graph_resistance_directed_graph(self):
517
+ G = nx.DiGraph()
518
+ with pytest.raises(nx.NetworkXNotImplemented):
519
+ nx.effective_graph_resistance(G)
520
+
521
+ def test_effective_graph_resistance_empty(self):
522
+ G = nx.Graph()
523
+ with pytest.raises(nx.NetworkXError):
524
+ nx.effective_graph_resistance(G)
525
+
526
+ def test_effective_graph_resistance_not_connected(self):
527
+ G = nx.Graph([(1, 2), (3, 4)])
528
+ RG = nx.effective_graph_resistance(G)
529
+ assert np.isinf(RG)
530
+
531
+ def test_effective_graph_resistance(self):
532
+ RG = nx.effective_graph_resistance(self.G, "weight", True)
533
+ rd12 = 1 / (1 / (1 + 4) + 1 / 2)
534
+ rd13 = 1 / (1 / (1 + 2) + 1 / 4)
535
+ rd23 = 1 / (1 / (2 + 4) + 1 / 1)
536
+ assert np.isclose(RG, rd12 + rd13 + rd23)
537
+
538
+ def test_effective_graph_resistance_noinv(self):
539
+ RG = nx.effective_graph_resistance(self.G, "weight", False)
540
+ rd12 = 1 / (1 / (1 / 1 + 1 / 4) + 1 / (1 / 2))
541
+ rd13 = 1 / (1 / (1 / 1 + 1 / 2) + 1 / (1 / 4))
542
+ rd23 = 1 / (1 / (1 / 2 + 1 / 4) + 1 / (1 / 1))
543
+ assert np.isclose(RG, rd12 + rd13 + rd23)
544
+
545
+ def test_effective_graph_resistance_no_weight(self):
546
+ RG = nx.effective_graph_resistance(self.G)
547
+ assert np.isclose(RG, 2)
548
+
549
+ def test_effective_graph_resistance_neg_weight(self):
550
+ self.G[2][3]["weight"] = -4
551
+ RG = nx.effective_graph_resistance(self.G, "weight", True)
552
+ rd12 = 1 / (1 / (1 + -4) + 1 / 2)
553
+ rd13 = 1 / (1 / (1 + 2) + 1 / (-4))
554
+ rd23 = 1 / (1 / (2 + -4) + 1 / 1)
555
+ assert np.isclose(RG, rd12 + rd13 + rd23)
556
+
557
+ def test_effective_graph_resistance_multigraph(self):
558
+ G = nx.MultiGraph()
559
+ G.add_edge(1, 2, weight=2)
560
+ G.add_edge(1, 3, weight=1)
561
+ G.add_edge(2, 3, weight=1)
562
+ G.add_edge(2, 3, weight=3)
563
+ RG = nx.effective_graph_resistance(G, "weight", True)
564
+ edge23 = 1 / (1 / 1 + 1 / 3)
565
+ rd12 = 1 / (1 / (1 + edge23) + 1 / 2)
566
+ rd13 = 1 / (1 / (1 + 2) + 1 / edge23)
567
+ rd23 = 1 / (1 / (2 + edge23) + 1 / 1)
568
+ assert np.isclose(RG, rd12 + rd13 + rd23)
569
+
570
+ def test_effective_graph_resistance_div0(self):
571
+ with pytest.raises(ZeroDivisionError):
572
+ self.G[1][2]["weight"] = 0
573
+ nx.effective_graph_resistance(self.G, "weight")
574
+
575
+ def test_effective_graph_resistance_complete_graph(self):
576
+ N = 10
577
+ G = nx.complete_graph(N)
578
+ RG = nx.effective_graph_resistance(G)
579
+ assert np.isclose(RG, N - 1)
580
+
581
+ def test_effective_graph_resistance_path_graph(self):
582
+ N = 10
583
+ G = nx.path_graph(N)
584
+ RG = nx.effective_graph_resistance(G)
585
+ assert np.isclose(RG, (N - 1) * N * (N + 1) // 6)
586
+
587
+
588
+ class TestBarycenter:
589
+ """Test :func:`networkx.algorithms.distance_measures.barycenter`."""
590
+
591
+ def barycenter_as_subgraph(self, g, **kwargs):
592
+ """Return the subgraph induced on the barycenter of g"""
593
+ b = nx.barycenter(g, **kwargs)
594
+ assert isinstance(b, list)
595
+ assert set(b) <= set(g)
596
+ return g.subgraph(b)
597
+
598
+ def test_must_be_connected(self):
599
+ pytest.raises(nx.NetworkXNoPath, nx.barycenter, nx.empty_graph(5))
600
+
601
+ def test_sp_kwarg(self):
602
+ # Complete graph K_5. Normally it works...
603
+ K_5 = nx.complete_graph(5)
604
+ sp = dict(nx.shortest_path_length(K_5))
605
+ assert nx.barycenter(K_5, sp=sp) == list(K_5)
606
+
607
+ # ...but not with the weight argument
608
+ for u, v, data in K_5.edges.data():
609
+ data["weight"] = 1
610
+ pytest.raises(ValueError, nx.barycenter, K_5, sp=sp, weight="weight")
611
+
612
+ # ...and a corrupted sp can make it seem like K_5 is disconnected
613
+ del sp[0][1]
614
+ pytest.raises(nx.NetworkXNoPath, nx.barycenter, K_5, sp=sp)
615
+
616
+ def test_trees(self):
617
+ """The barycenter of a tree is a single vertex or an edge.
618
+
619
+ See [West01]_, p. 78.
620
+ """
621
+ prng = Random(0xDEADBEEF)
622
+ for i in range(50):
623
+ RT = nx.random_labeled_tree(prng.randint(1, 75), seed=prng)
624
+ b = self.barycenter_as_subgraph(RT)
625
+ if len(b) == 2:
626
+ assert b.size() == 1
627
+ else:
628
+ assert len(b) == 1
629
+ assert b.size() == 0
630
+
631
+ def test_this_one_specific_tree(self):
632
+ """Test the tree pictured at the bottom of [West01]_, p. 78."""
633
+ g = nx.Graph(
634
+ {
635
+ "a": ["b"],
636
+ "b": ["a", "x"],
637
+ "x": ["b", "y"],
638
+ "y": ["x", "z"],
639
+ "z": ["y", 0, 1, 2, 3, 4],
640
+ 0: ["z"],
641
+ 1: ["z"],
642
+ 2: ["z"],
643
+ 3: ["z"],
644
+ 4: ["z"],
645
+ }
646
+ )
647
+ b = self.barycenter_as_subgraph(g, attr="barycentricity")
648
+ assert list(b) == ["z"]
649
+ assert not b.edges
650
+ expected_barycentricity = {
651
+ 0: 23,
652
+ 1: 23,
653
+ 2: 23,
654
+ 3: 23,
655
+ 4: 23,
656
+ "a": 35,
657
+ "b": 27,
658
+ "x": 21,
659
+ "y": 17,
660
+ "z": 15,
661
+ }
662
+ for node, barycentricity in expected_barycentricity.items():
663
+ assert g.nodes[node]["barycentricity"] == barycentricity
664
+
665
+ # Doubling weights should do nothing but double the barycentricities
666
+ for edge in g.edges:
667
+ g.edges[edge]["weight"] = 2
668
+ b = self.barycenter_as_subgraph(g, weight="weight", attr="barycentricity2")
669
+ assert list(b) == ["z"]
670
+ assert not b.edges
671
+ for node, barycentricity in expected_barycentricity.items():
672
+ assert g.nodes[node]["barycentricity2"] == barycentricity * 2
673
+
674
+
675
+ class TestKemenyConstant:
676
+ @classmethod
677
+ def setup_class(cls):
678
+ global np
679
+ np = pytest.importorskip("numpy")
680
+ sp = pytest.importorskip("scipy")
681
+
682
+ def setup_method(self):
683
+ G = nx.Graph()
684
+ w12 = 2
685
+ w13 = 3
686
+ w23 = 4
687
+ G.add_edge(1, 2, weight=w12)
688
+ G.add_edge(1, 3, weight=w13)
689
+ G.add_edge(2, 3, weight=w23)
690
+ self.G = G
691
+
692
+ def test_kemeny_constant_directed(self):
693
+ G = nx.DiGraph()
694
+ G.add_edge(1, 2)
695
+ G.add_edge(1, 3)
696
+ G.add_edge(2, 3)
697
+ with pytest.raises(nx.NetworkXNotImplemented):
698
+ nx.kemeny_constant(G)
699
+
700
+ def test_kemeny_constant_not_connected(self):
701
+ self.G.add_node(5)
702
+ with pytest.raises(nx.NetworkXError):
703
+ nx.kemeny_constant(self.G)
704
+
705
+ def test_kemeny_constant_no_nodes(self):
706
+ G = nx.Graph()
707
+ with pytest.raises(nx.NetworkXError):
708
+ nx.kemeny_constant(G)
709
+
710
+ def test_kemeny_constant_negative_weight(self):
711
+ G = nx.Graph()
712
+ w12 = 2
713
+ w13 = 3
714
+ w23 = -10
715
+ G.add_edge(1, 2, weight=w12)
716
+ G.add_edge(1, 3, weight=w13)
717
+ G.add_edge(2, 3, weight=w23)
718
+ with pytest.raises(nx.NetworkXError):
719
+ nx.kemeny_constant(G, weight="weight")
720
+
721
+ def test_kemeny_constant(self):
722
+ K = nx.kemeny_constant(self.G, weight="weight")
723
+ w12 = 2
724
+ w13 = 3
725
+ w23 = 4
726
+ test_data = (
727
+ 3
728
+ / 2
729
+ * (w12 + w13)
730
+ * (w12 + w23)
731
+ * (w13 + w23)
732
+ / (
733
+ w12**2 * (w13 + w23)
734
+ + w13**2 * (w12 + w23)
735
+ + w23**2 * (w12 + w13)
736
+ + 3 * w12 * w13 * w23
737
+ )
738
+ )
739
+ assert np.isclose(K, test_data)
740
+
741
+ def test_kemeny_constant_no_weight(self):
742
+ K = nx.kemeny_constant(self.G)
743
+ assert np.isclose(K, 4 / 3)
744
+
745
+ def test_kemeny_constant_multigraph(self):
746
+ G = nx.MultiGraph()
747
+ w12_1 = 2
748
+ w12_2 = 1
749
+ w13 = 3
750
+ w23 = 4
751
+ G.add_edge(1, 2, weight=w12_1)
752
+ G.add_edge(1, 2, weight=w12_2)
753
+ G.add_edge(1, 3, weight=w13)
754
+ G.add_edge(2, 3, weight=w23)
755
+ K = nx.kemeny_constant(G, weight="weight")
756
+ w12 = w12_1 + w12_2
757
+ test_data = (
758
+ 3
759
+ / 2
760
+ * (w12 + w13)
761
+ * (w12 + w23)
762
+ * (w13 + w23)
763
+ / (
764
+ w12**2 * (w13 + w23)
765
+ + w13**2 * (w12 + w23)
766
+ + w23**2 * (w12 + w13)
767
+ + 3 * w12 * w13 * w23
768
+ )
769
+ )
770
+ assert np.isclose(K, test_data)
771
+
772
+ def test_kemeny_constant_weight0(self):
773
+ G = nx.Graph()
774
+ w12 = 0
775
+ w13 = 3
776
+ w23 = 4
777
+ G.add_edge(1, 2, weight=w12)
778
+ G.add_edge(1, 3, weight=w13)
779
+ G.add_edge(2, 3, weight=w23)
780
+ K = nx.kemeny_constant(G, weight="weight")
781
+ test_data = (
782
+ 3
783
+ / 2
784
+ * (w12 + w13)
785
+ * (w12 + w23)
786
+ * (w13 + w23)
787
+ / (
788
+ w12**2 * (w13 + w23)
789
+ + w13**2 * (w12 + w23)
790
+ + w23**2 * (w12 + w13)
791
+ + 3 * w12 * w13 * w23
792
+ )
793
+ )
794
+ assert np.isclose(K, test_data)
795
+
796
+ def test_kemeny_constant_selfloop(self):
797
+ G = nx.Graph()
798
+ w11 = 1
799
+ w12 = 2
800
+ w13 = 3
801
+ w23 = 4
802
+ G.add_edge(1, 1, weight=w11)
803
+ G.add_edge(1, 2, weight=w12)
804
+ G.add_edge(1, 3, weight=w13)
805
+ G.add_edge(2, 3, weight=w23)
806
+ K = nx.kemeny_constant(G, weight="weight")
807
+ test_data = (
808
+ (2 * w11 + 3 * w12 + 3 * w13)
809
+ * (w12 + w23)
810
+ * (w13 + w23)
811
+ / (
812
+ (w12 * w13 + w12 * w23 + w13 * w23)
813
+ * (w11 + 2 * w12 + 2 * w13 + 2 * w23)
814
+ )
815
+ )
816
+ assert np.isclose(K, test_data)
817
+
818
+ def test_kemeny_constant_complete_bipartite_graph(self):
819
+ # Theorem 1 in https://www.sciencedirect.com/science/article/pii/S0166218X20302912
820
+ n1 = 5
821
+ n2 = 4
822
+ G = nx.complete_bipartite_graph(n1, n2)
823
+ K = nx.kemeny_constant(G)
824
+ assert np.isclose(K, n1 + n2 - 3 / 2)
825
+
826
+ def test_kemeny_constant_path_graph(self):
827
+ # Theorem 2 in https://www.sciencedirect.com/science/article/pii/S0166218X20302912
828
+ n = 10
829
+ G = nx.path_graph(n)
830
+ K = nx.kemeny_constant(G)
831
+ assert np.isclose(K, n**2 / 3 - 2 * n / 3 + 1 / 2)
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_distance_regular.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import networkx as nx
4
+ from networkx import is_strongly_regular
5
+
6
+
7
+ @pytest.mark.parametrize(
8
+ "f", (nx.is_distance_regular, nx.intersection_array, nx.is_strongly_regular)
9
+ )
10
+ @pytest.mark.parametrize("graph_constructor", (nx.DiGraph, nx.MultiGraph))
11
+ def test_raises_on_directed_and_multigraphs(f, graph_constructor):
12
+ G = graph_constructor([(0, 1), (1, 2)])
13
+ with pytest.raises(nx.NetworkXNotImplemented):
14
+ f(G)
15
+
16
+
17
+ class TestDistanceRegular:
18
+ def test_is_distance_regular(self):
19
+ assert nx.is_distance_regular(nx.icosahedral_graph())
20
+ assert nx.is_distance_regular(nx.petersen_graph())
21
+ assert nx.is_distance_regular(nx.cubical_graph())
22
+ assert nx.is_distance_regular(nx.complete_bipartite_graph(3, 3))
23
+ assert nx.is_distance_regular(nx.tetrahedral_graph())
24
+ assert nx.is_distance_regular(nx.dodecahedral_graph())
25
+ assert nx.is_distance_regular(nx.pappus_graph())
26
+ assert nx.is_distance_regular(nx.heawood_graph())
27
+ assert nx.is_distance_regular(nx.cycle_graph(3))
28
+ # no distance regular
29
+ assert not nx.is_distance_regular(nx.path_graph(4))
30
+
31
+ def test_not_connected(self):
32
+ G = nx.cycle_graph(4)
33
+ nx.add_cycle(G, [5, 6, 7])
34
+ assert not nx.is_distance_regular(G)
35
+
36
+ def test_global_parameters(self):
37
+ b, c = nx.intersection_array(nx.cycle_graph(5))
38
+ g = nx.global_parameters(b, c)
39
+ assert list(g) == [(0, 0, 2), (1, 0, 1), (1, 1, 0)]
40
+ b, c = nx.intersection_array(nx.cycle_graph(3))
41
+ g = nx.global_parameters(b, c)
42
+ assert list(g) == [(0, 0, 2), (1, 1, 0)]
43
+
44
+ def test_intersection_array(self):
45
+ b, c = nx.intersection_array(nx.cycle_graph(5))
46
+ assert b == [2, 1]
47
+ assert c == [1, 1]
48
+ b, c = nx.intersection_array(nx.dodecahedral_graph())
49
+ assert b == [3, 2, 1, 1, 1]
50
+ assert c == [1, 1, 1, 2, 3]
51
+ b, c = nx.intersection_array(nx.icosahedral_graph())
52
+ assert b == [5, 2, 1]
53
+ assert c == [1, 2, 5]
54
+
55
+
56
+ @pytest.mark.parametrize("f", (nx.is_distance_regular, nx.is_strongly_regular))
57
+ def test_empty_graph_raises(f):
58
+ G = nx.Graph()
59
+ with pytest.raises(nx.NetworkXPointlessConcept, match="Graph has no nodes"):
60
+ f(G)
61
+
62
+
63
+ class TestStronglyRegular:
64
+ """Unit tests for the :func:`~networkx.is_strongly_regular`
65
+ function.
66
+
67
+ """
68
+
69
+ def test_cycle_graph(self):
70
+ """Tests that the cycle graph on five vertices is strongly
71
+ regular.
72
+
73
+ """
74
+ G = nx.cycle_graph(5)
75
+ assert is_strongly_regular(G)
76
+
77
+ def test_petersen_graph(self):
78
+ """Tests that the Petersen graph is strongly regular."""
79
+ G = nx.petersen_graph()
80
+ assert is_strongly_regular(G)
81
+
82
+ def test_path_graph(self):
83
+ """Tests that the path graph is not strongly regular."""
84
+ G = nx.path_graph(4)
85
+ assert not is_strongly_regular(G)
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_dominance.py ADDED
@@ -0,0 +1,299 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import networkx as nx
4
+
5
+
6
+ class TestImmediateDominators:
7
+ @pytest.mark.parametrize("G", [nx.Graph(), nx.MultiGraph()])
8
+ def test_raises_undirected(self, G):
9
+ """Check that `immediate_dominators` raises for undirected graphs."""
10
+ with pytest.raises(
11
+ nx.NetworkXNotImplemented, match=r"not implemented for undirected"
12
+ ):
13
+ nx.immediate_dominators(G, 0)
14
+
15
+ def test_raises_node(self):
16
+ """Check that `immediate_dominators` raises when `start` is not in the graph."""
17
+ G = nx.empty_graph(1, create_using=nx.DiGraph)
18
+ with pytest.raises(nx.NetworkXError, match=r"not in G"):
19
+ nx.immediate_dominators(G, 1)
20
+
21
+ def test_singleton(self):
22
+ G = nx.DiGraph()
23
+ G.add_node(0)
24
+ assert nx.immediate_dominators(G, 0) == {}
25
+ G.add_edge(0, 0)
26
+ assert nx.immediate_dominators(G, 0) == {}
27
+
28
+ @pytest.mark.parametrize("gen", [nx.path_graph, nx.cycle_graph])
29
+ @pytest.mark.parametrize("n", [5, 10, 20])
30
+ def test_path_and_cycle(self, gen, n):
31
+ """Check `immediate_dominators` is correct for path and cycle graphs."""
32
+ G = gen(n, create_using=nx.DiGraph())
33
+ idom = nx.immediate_dominators(G, 0)
34
+ assert idom == {i: i - 1 for i in range(1, n)}
35
+
36
+ def test_unreachable(self):
37
+ n = 5
38
+ G = nx.path_graph(n, create_using=nx.DiGraph())
39
+ idom = nx.immediate_dominators(G, 1)
40
+ assert idom == {i: i - 1 for i in range(2, n)}
41
+
42
+ @pytest.mark.parametrize(
43
+ ["edgelist", "start"],
44
+ [
45
+ ([(1, 2), (2, 1), (3, 2), (4, 1), (5, 3), (5, 4)], 5),
46
+ (
47
+ [
48
+ (1, 2),
49
+ (2, 1),
50
+ (2, 3),
51
+ (3, 2),
52
+ (4, 2),
53
+ (4, 3),
54
+ (5, 1),
55
+ (6, 4),
56
+ (6, 5),
57
+ ],
58
+ 6,
59
+ ),
60
+ ],
61
+ )
62
+ def test_irreducible(self, edgelist, start):
63
+ """
64
+ Check `immediate_dominators` on irreducible reference graphs.
65
+
66
+ Graphs taken from figures 2 and 4 of "A simple, fast dominance algorithm." (2006).
67
+ https://hdl.handle.net/1911/96345
68
+ """
69
+ G = nx.DiGraph(edgelist)
70
+ idom = nx.immediate_dominators(G, start)
71
+ assert idom == dict.fromkeys(range(1, start), start)
72
+
73
+ def test_domrel_png(self):
74
+ # Graph taken from https://commons.wikipedia.org/wiki/File:Domrel.png
75
+ edges = [(1, 2), (2, 3), (2, 4), (2, 6), (3, 5), (4, 5), (5, 2)]
76
+ G = nx.DiGraph(edges)
77
+ result = nx.immediate_dominators(G, 1)
78
+ assert result == {2: 1, 3: 2, 4: 2, 5: 2, 6: 2}
79
+ # Test postdominance.
80
+ result = nx.immediate_dominators(G.reverse(copy=False), 6)
81
+ assert result == {1: 2, 2: 6, 3: 5, 4: 5, 5: 2}
82
+
83
+ def test_boost_example(self):
84
+ # Graph taken from Figure 1 of
85
+ # http://www.boost.org/doc/libs/1_56_0/libs/graph/doc/lengauer_tarjan_dominator.htm
86
+ edges = [(0, 1), (1, 2), (1, 3), (2, 7), (3, 4), (4, 5), (4, 6), (5, 7), (6, 4)]
87
+ G = nx.DiGraph(edges)
88
+ result = nx.immediate_dominators(G, 0)
89
+ assert result == {1: 0, 2: 1, 3: 1, 4: 3, 5: 4, 6: 4, 7: 1}
90
+ # Test postdominance.
91
+ result = nx.immediate_dominators(G.reverse(copy=False), 7)
92
+ assert result == {0: 1, 1: 7, 2: 7, 3: 4, 4: 5, 5: 7, 6: 4}
93
+
94
+
95
+ class TestDominanceFrontiers:
96
+ @pytest.mark.parametrize("G", [nx.Graph(), nx.MultiGraph()])
97
+ def test_raises_undirected(self, G):
98
+ """Check that `dominance_frontiers` raises for undirected graphs."""
99
+ with pytest.raises(
100
+ nx.NetworkXNotImplemented, match=r"not implemented for undirected"
101
+ ):
102
+ nx.dominance_frontiers(G, 0)
103
+
104
+ def test_raises_node(self):
105
+ """Check that `dominance_frontiers` raises when `start` is not in the graph."""
106
+ G = nx.empty_graph(1, create_using=nx.DiGraph)
107
+ with pytest.raises(nx.NetworkXError, match=r"not in G"):
108
+ nx.dominance_frontiers(G, 1)
109
+
110
+ def test_singleton(self):
111
+ G = nx.DiGraph()
112
+ G.add_node(0)
113
+ assert nx.dominance_frontiers(G, 0) == {0: set()}
114
+ G.add_edge(0, 0)
115
+ assert nx.dominance_frontiers(G, 0) == {0: {0}}
116
+
117
+ @pytest.mark.parametrize("gen, df", [(nx.path_graph, set()), (nx.cycle_graph, {0})])
118
+ @pytest.mark.parametrize("n", [5, 10, 20])
119
+ def test_path_and_cycle(self, gen, df, n):
120
+ """Check that `dominance_frontiers` is correct for path and cycle graphs."""
121
+ G = gen(n, create_using=nx.DiGraph())
122
+ assert nx.dominance_frontiers(G, 0) == dict.fromkeys(range(n), df)
123
+
124
+ def test_unreachable(self):
125
+ n = 5
126
+ G = nx.path_graph(n, create_using=nx.DiGraph())
127
+ assert nx.dominance_frontiers(G, 1) == dict.fromkeys(range(1, n), set())
128
+
129
+ def test_irreducible1(self):
130
+ """
131
+ Graph taken from figure 2 of "A simple, fast dominance algorithm." (2006).
132
+ https://hdl.handle.net/1911/96345
133
+ """
134
+ edges = [(1, 2), (2, 1), (3, 2), (4, 1), (5, 3), (5, 4)]
135
+ G = nx.DiGraph(edges)
136
+ assert nx.dominance_frontiers(G, 5) == {
137
+ 1: {2},
138
+ 2: {1},
139
+ 3: {2},
140
+ 4: {1},
141
+ 5: set(),
142
+ }
143
+
144
+ def test_irreducible2(self):
145
+ """
146
+ Graph taken from figure 4 of "A simple, fast dominance algorithm." (2006).
147
+ https://hdl.handle.net/1911/96345
148
+ """
149
+ edges = [(1, 2), (2, 1), (2, 3), (3, 2), (4, 2), (4, 3), (5, 1), (6, 4), (6, 5)]
150
+ G = nx.DiGraph(edges)
151
+ assert nx.dominance_frontiers(G, 6) == {
152
+ 1: {2},
153
+ 2: {1, 3},
154
+ 3: {2},
155
+ 4: {2, 3},
156
+ 5: {1},
157
+ 6: set(),
158
+ }
159
+
160
+ def test_domrel_png(self):
161
+ # Graph taken from https://commons.wikipedia.org/wiki/File:Domrel.png
162
+ edges = [(1, 2), (2, 3), (2, 4), (2, 6), (3, 5), (4, 5), (5, 2)]
163
+ G = nx.DiGraph(edges)
164
+ assert nx.dominance_frontiers(G, 1) == {
165
+ 1: set(),
166
+ 2: {2},
167
+ 3: {5},
168
+ 4: {5},
169
+ 5: {2},
170
+ 6: set(),
171
+ }
172
+ # Test postdominance.
173
+ result = nx.dominance_frontiers(G.reverse(copy=False), 6)
174
+ assert result == {1: set(), 2: {2}, 3: {2}, 4: {2}, 5: {2}, 6: set()}
175
+
176
+ def test_boost_example(self):
177
+ # Graph taken from Figure 1 of
178
+ # http://www.boost.org/doc/libs/1_56_0/libs/graph/doc/lengauer_tarjan_dominator.htm
179
+ edges = [(0, 1), (1, 2), (1, 3), (2, 7), (3, 4), (4, 5), (4, 6), (5, 7), (6, 4)]
180
+ G = nx.DiGraph(edges)
181
+ assert nx.dominance_frontiers(G, 0) == {
182
+ 0: set(),
183
+ 1: set(),
184
+ 2: {7},
185
+ 3: {7},
186
+ 4: {4, 7},
187
+ 5: {7},
188
+ 6: {4},
189
+ 7: set(),
190
+ }
191
+ # Test postdominance.
192
+ result = nx.dominance_frontiers(G.reverse(copy=False), 7)
193
+ expected = {
194
+ 0: set(),
195
+ 1: set(),
196
+ 2: {1},
197
+ 3: {1},
198
+ 4: {1, 4},
199
+ 5: {1},
200
+ 6: {4},
201
+ 7: set(),
202
+ }
203
+ assert result == expected
204
+
205
+ def test_discard_issue(self):
206
+ # https://github.com/networkx/networkx/issues/2071
207
+ g = nx.DiGraph()
208
+ g.add_edges_from(
209
+ [
210
+ ("b0", "b1"),
211
+ ("b1", "b2"),
212
+ ("b2", "b3"),
213
+ ("b3", "b1"),
214
+ ("b1", "b5"),
215
+ ("b5", "b6"),
216
+ ("b5", "b8"),
217
+ ("b6", "b7"),
218
+ ("b8", "b7"),
219
+ ("b7", "b3"),
220
+ ("b3", "b4"),
221
+ ]
222
+ )
223
+ df = nx.dominance_frontiers(g, "b0")
224
+ assert df == {
225
+ "b4": set(),
226
+ "b5": {"b3"},
227
+ "b6": {"b7"},
228
+ "b7": {"b3"},
229
+ "b0": set(),
230
+ "b1": {"b1"},
231
+ "b2": {"b3"},
232
+ "b3": {"b1"},
233
+ "b8": {"b7"},
234
+ }
235
+
236
+ def test_loop(self):
237
+ g = nx.DiGraph()
238
+ g.add_edges_from([("a", "b"), ("b", "c"), ("b", "a")])
239
+ df = nx.dominance_frontiers(g, "a")
240
+ assert df == {"a": {"a"}, "b": {"a"}, "c": set()}
241
+
242
+ def test_missing_immediate_doms(self):
243
+ # see https://github.com/networkx/networkx/issues/2070
244
+ g = nx.DiGraph()
245
+ edges = [
246
+ ("entry_1", "b1"),
247
+ ("b1", "b2"),
248
+ ("b2", "b3"),
249
+ ("b3", "exit"),
250
+ ("entry_2", "b3"),
251
+ ]
252
+
253
+ # entry_1
254
+ # |
255
+ # b1
256
+ # |
257
+ # b2 entry_2
258
+ # | /
259
+ # b3
260
+ # |
261
+ # exit
262
+
263
+ g.add_edges_from(edges)
264
+ # formerly raised KeyError on entry_2 when parsing b3
265
+ # because entry_2 does not have immediate doms (no path)
266
+ nx.dominance_frontiers(g, "entry_1")
267
+
268
+ def test_loops_larger(self):
269
+ # from
270
+ # http://ecee.colorado.edu/~waite/Darmstadt/motion.html
271
+ g = nx.DiGraph()
272
+ edges = [
273
+ ("entry", "exit"),
274
+ ("entry", "1"),
275
+ ("1", "2"),
276
+ ("2", "3"),
277
+ ("3", "4"),
278
+ ("4", "5"),
279
+ ("5", "6"),
280
+ ("6", "exit"),
281
+ ("6", "2"),
282
+ ("5", "3"),
283
+ ("4", "4"),
284
+ ]
285
+
286
+ g.add_edges_from(edges)
287
+ df = nx.dominance_frontiers(g, "entry")
288
+ answer = {
289
+ "entry": set(),
290
+ "1": {"exit"},
291
+ "2": {"exit", "2"},
292
+ "3": {"exit", "3", "2"},
293
+ "4": {"exit", "4", "3", "2"},
294
+ "5": {"exit", "3", "2"},
295
+ "6": {"exit", "2"},
296
+ "exit": set(),
297
+ }
298
+ for n in df:
299
+ assert set(df[n]) == set(answer[n])
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_dominating.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import networkx as nx
4
+
5
+
6
+ def test_dominating_set():
7
+ G = nx.gnp_random_graph(100, 0.1)
8
+ D = nx.dominating_set(G)
9
+ assert nx.is_dominating_set(G, D)
10
+ D = nx.dominating_set(G, start_with=0)
11
+ assert nx.is_dominating_set(G, D)
12
+
13
+
14
+ def test_complete():
15
+ """In complete graphs each node is a dominating set.
16
+ Thus the dominating set has to be of cardinality 1.
17
+ """
18
+ K4 = nx.complete_graph(4)
19
+ assert len(nx.dominating_set(K4)) == 1
20
+ K5 = nx.complete_graph(5)
21
+ assert len(nx.dominating_set(K5)) == 1
22
+
23
+
24
+ def test_raise_dominating_set():
25
+ with pytest.raises(nx.NetworkXError):
26
+ G = nx.path_graph(4)
27
+ D = nx.dominating_set(G, start_with=10)
28
+
29
+
30
+ def test_is_dominating_set():
31
+ G = nx.path_graph(4)
32
+ d = {1, 3}
33
+ assert nx.is_dominating_set(G, d)
34
+ d = {0, 2}
35
+ assert nx.is_dominating_set(G, d)
36
+ d = {1}
37
+ assert not nx.is_dominating_set(G, d)
38
+
39
+
40
+ def test_wikipedia_is_dominating_set():
41
+ """Example from https://en.wikipedia.org/wiki/Dominating_set"""
42
+ G = nx.cycle_graph(4)
43
+ G.add_edges_from([(0, 4), (1, 4), (2, 5)])
44
+ assert nx.is_dominating_set(G, {4, 3, 5})
45
+ assert nx.is_dominating_set(G, {0, 2})
46
+ assert nx.is_dominating_set(G, {1, 2})
47
+
48
+
49
+ def test_is_connected_dominating_set():
50
+ G = nx.path_graph(4)
51
+ D = {1, 2}
52
+ assert nx.is_connected_dominating_set(G, D)
53
+ D = {1, 3}
54
+ assert not nx.is_connected_dominating_set(G, D)
55
+ D = {2, 3}
56
+ assert nx.is_connected(nx.subgraph(G, D))
57
+ assert not nx.is_connected_dominating_set(G, D)
58
+
59
+
60
+ def test_null_graph_connected_dominating_set():
61
+ G = nx.Graph()
62
+ assert 0 == len(nx.connected_dominating_set(G))
63
+
64
+
65
+ def test_single_node_graph_connected_dominating_set():
66
+ G = nx.Graph()
67
+ G.add_node(1)
68
+ CD = nx.connected_dominating_set(G)
69
+ assert nx.is_connected_dominating_set(G, CD)
70
+
71
+
72
+ def test_raise_disconnected_graph_connected_dominating_set():
73
+ with pytest.raises(nx.NetworkXError):
74
+ G = nx.Graph()
75
+ G.add_node(1)
76
+ G.add_node(2)
77
+ nx.connected_dominating_set(G)
78
+
79
+
80
+ def test_complete_graph_connected_dominating_set():
81
+ K5 = nx.complete_graph(5)
82
+ assert 1 == len(nx.connected_dominating_set(K5))
83
+ K7 = nx.complete_graph(7)
84
+ assert 1 == len(nx.connected_dominating_set(K7))
85
+
86
+
87
+ def test_docstring_example_connected_dominating_set():
88
+ G = nx.Graph(
89
+ [
90
+ (1, 2),
91
+ (1, 3),
92
+ (1, 4),
93
+ (1, 5),
94
+ (1, 6),
95
+ (2, 7),
96
+ (3, 8),
97
+ (4, 9),
98
+ (5, 10),
99
+ (6, 11),
100
+ (7, 12),
101
+ (8, 12),
102
+ (9, 12),
103
+ (10, 12),
104
+ (11, 12),
105
+ ]
106
+ )
107
+ assert {1, 2, 3, 4, 5, 6, 7} == nx.connected_dominating_set(G)
108
+
109
+
110
+ @pytest.mark.parametrize("seed", [1, 13, 29])
111
+ @pytest.mark.parametrize("n,k,p", [(10, 3, 0.2), (100, 10, 0.7), (1000, 50, 0.5)])
112
+ def test_connected_watts_strogatz_graph_connected_dominating_set(n, k, p, seed):
113
+ G = nx.connected_watts_strogatz_graph(n, k, p, seed=seed)
114
+ D = nx.connected_dominating_set(G)
115
+ assert nx.is_connected_dominating_set(G, D)
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_efficiency.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Unit tests for the :mod:`networkx.algorithms.efficiency` module."""
2
+
3
+ import networkx as nx
4
+
5
+
6
+ class TestEfficiency:
7
+ def setup_method(self):
8
+ # G1 is a disconnected graph
9
+ self.G1 = nx.Graph()
10
+ self.G1.add_nodes_from([1, 2, 3])
11
+ # G2 is a cycle graph
12
+ self.G2 = nx.cycle_graph(4)
13
+ # G3 is the triangle graph with one additional edge
14
+ self.G3 = nx.lollipop_graph(3, 1)
15
+
16
+ def test_efficiency_disconnected_nodes(self):
17
+ """
18
+ When nodes are disconnected, efficiency is 0
19
+ """
20
+ assert nx.efficiency(self.G1, 1, 2) == 0
21
+
22
+ def test_local_efficiency_disconnected_graph(self):
23
+ """
24
+ In a disconnected graph the efficiency is 0
25
+ """
26
+ assert nx.local_efficiency(self.G1) == 0
27
+
28
+ def test_efficiency(self):
29
+ assert nx.efficiency(self.G2, 0, 1) == 1
30
+ assert nx.efficiency(self.G2, 0, 2) == 1 / 2
31
+
32
+ def test_global_efficiency(self):
33
+ assert nx.global_efficiency(self.G2) == 5 / 6
34
+
35
+ def test_global_efficiency_complete_graph(self):
36
+ """
37
+ Tests that the average global efficiency of the complete graph is one.
38
+ """
39
+ for n in range(2, 10):
40
+ G = nx.complete_graph(n)
41
+ assert nx.global_efficiency(G) == 1
42
+
43
+ def test_local_efficiency_complete_graph(self):
44
+ """
45
+ Test that the local efficiency for a complete graph with at least 3
46
+ nodes should be one. For a graph with only 2 nodes, the induced
47
+ subgraph has no edges.
48
+ """
49
+ for n in range(3, 10):
50
+ G = nx.complete_graph(n)
51
+ assert nx.local_efficiency(G) == 1
52
+
53
+ def test_using_ego_graph(self):
54
+ """
55
+ Test that the ego graph is used when computing local efficiency.
56
+ For more information, see GitHub issue #2710.
57
+ """
58
+ assert nx.local_efficiency(self.G3) == 7 / 12
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_euler.py ADDED
@@ -0,0 +1,314 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import collections
2
+
3
+ import pytest
4
+
5
+ import networkx as nx
6
+
7
+
8
+ @pytest.mark.parametrize("f", (nx.is_eulerian, nx.is_semieulerian))
9
+ def test_empty_graph_raises(f):
10
+ G = nx.Graph()
11
+ with pytest.raises(nx.NetworkXPointlessConcept, match="Connectivity is undefined"):
12
+ f(G)
13
+
14
+
15
+ class TestIsEulerian:
16
+ def test_is_eulerian(self):
17
+ assert nx.is_eulerian(nx.complete_graph(5))
18
+ assert nx.is_eulerian(nx.complete_graph(7))
19
+ assert nx.is_eulerian(nx.hypercube_graph(4))
20
+ assert nx.is_eulerian(nx.hypercube_graph(6))
21
+
22
+ assert not nx.is_eulerian(nx.complete_graph(4))
23
+ assert not nx.is_eulerian(nx.complete_graph(6))
24
+ assert not nx.is_eulerian(nx.hypercube_graph(3))
25
+ assert not nx.is_eulerian(nx.hypercube_graph(5))
26
+
27
+ assert not nx.is_eulerian(nx.petersen_graph())
28
+ assert not nx.is_eulerian(nx.path_graph(4))
29
+
30
+ def test_is_eulerian2(self):
31
+ # not connected
32
+ G = nx.Graph()
33
+ G.add_nodes_from([1, 2, 3])
34
+ assert not nx.is_eulerian(G)
35
+ # not strongly connected
36
+ G = nx.DiGraph()
37
+ G.add_nodes_from([1, 2, 3])
38
+ assert not nx.is_eulerian(G)
39
+ G = nx.MultiDiGraph()
40
+ G.add_edge(1, 2)
41
+ G.add_edge(2, 3)
42
+ G.add_edge(2, 3)
43
+ G.add_edge(3, 1)
44
+ assert not nx.is_eulerian(G)
45
+
46
+
47
+ class TestEulerianCircuit:
48
+ def test_eulerian_circuit_cycle(self):
49
+ G = nx.cycle_graph(4)
50
+
51
+ edges = list(nx.eulerian_circuit(G, source=0))
52
+ nodes = [u for u, v in edges]
53
+ assert nodes == [0, 3, 2, 1]
54
+ assert edges == [(0, 3), (3, 2), (2, 1), (1, 0)]
55
+
56
+ edges = list(nx.eulerian_circuit(G, source=1))
57
+ nodes = [u for u, v in edges]
58
+ assert nodes == [1, 2, 3, 0]
59
+ assert edges == [(1, 2), (2, 3), (3, 0), (0, 1)]
60
+
61
+ G = nx.complete_graph(3)
62
+
63
+ edges = list(nx.eulerian_circuit(G, source=0))
64
+ nodes = [u for u, v in edges]
65
+ assert nodes == [0, 2, 1]
66
+ assert edges == [(0, 2), (2, 1), (1, 0)]
67
+
68
+ edges = list(nx.eulerian_circuit(G, source=1))
69
+ nodes = [u for u, v in edges]
70
+ assert nodes == [1, 2, 0]
71
+ assert edges == [(1, 2), (2, 0), (0, 1)]
72
+
73
+ def test_eulerian_circuit_digraph(self):
74
+ G = nx.DiGraph()
75
+ nx.add_cycle(G, [0, 1, 2, 3])
76
+
77
+ edges = list(nx.eulerian_circuit(G, source=0))
78
+ nodes = [u for u, v in edges]
79
+ assert nodes == [0, 1, 2, 3]
80
+ assert edges == [(0, 1), (1, 2), (2, 3), (3, 0)]
81
+
82
+ edges = list(nx.eulerian_circuit(G, source=1))
83
+ nodes = [u for u, v in edges]
84
+ assert nodes == [1, 2, 3, 0]
85
+ assert edges == [(1, 2), (2, 3), (3, 0), (0, 1)]
86
+
87
+ def test_multigraph(self):
88
+ G = nx.MultiGraph()
89
+ nx.add_cycle(G, [0, 1, 2, 3])
90
+ G.add_edge(1, 2)
91
+ G.add_edge(1, 2)
92
+ edges = list(nx.eulerian_circuit(G, source=0))
93
+ nodes = [u for u, v in edges]
94
+ assert nodes == [0, 3, 2, 1, 2, 1]
95
+ assert edges == [(0, 3), (3, 2), (2, 1), (1, 2), (2, 1), (1, 0)]
96
+
97
+ def test_multigraph_with_keys(self):
98
+ G = nx.MultiGraph()
99
+ nx.add_cycle(G, [0, 1, 2, 3])
100
+ G.add_edge(1, 2)
101
+ G.add_edge(1, 2)
102
+ edges = list(nx.eulerian_circuit(G, source=0, keys=True))
103
+ nodes = [u for u, v, k in edges]
104
+ assert nodes == [0, 3, 2, 1, 2, 1]
105
+ assert edges[:2] == [(0, 3, 0), (3, 2, 0)]
106
+ assert collections.Counter(edges[2:5]) == collections.Counter(
107
+ [(2, 1, 0), (1, 2, 1), (2, 1, 2)]
108
+ )
109
+ assert edges[5:] == [(1, 0, 0)]
110
+
111
+ def test_not_eulerian(self):
112
+ with pytest.raises(nx.NetworkXError):
113
+ f = list(nx.eulerian_circuit(nx.complete_graph(4)))
114
+
115
+
116
+ class TestIsSemiEulerian:
117
+ def test_is_semieulerian(self):
118
+ # Test graphs with Eulerian paths but no cycles return True.
119
+ assert nx.is_semieulerian(nx.path_graph(4))
120
+ G = nx.path_graph(6, create_using=nx.DiGraph)
121
+ assert nx.is_semieulerian(G)
122
+
123
+ # Test graphs with Eulerian cycles return False.
124
+ assert not nx.is_semieulerian(nx.complete_graph(5))
125
+ assert not nx.is_semieulerian(nx.complete_graph(7))
126
+ assert not nx.is_semieulerian(nx.hypercube_graph(4))
127
+ assert not nx.is_semieulerian(nx.hypercube_graph(6))
128
+
129
+
130
+ class TestHasEulerianPath:
131
+ def test_has_eulerian_path_cyclic(self):
132
+ # Test graphs with Eulerian cycles return True.
133
+ assert nx.has_eulerian_path(nx.complete_graph(5))
134
+ assert nx.has_eulerian_path(nx.complete_graph(7))
135
+ assert nx.has_eulerian_path(nx.hypercube_graph(4))
136
+ assert nx.has_eulerian_path(nx.hypercube_graph(6))
137
+
138
+ def test_has_eulerian_path_non_cyclic(self):
139
+ # Test graphs with Eulerian paths but no cycles return True.
140
+ assert nx.has_eulerian_path(nx.path_graph(4))
141
+ G = nx.path_graph(6, create_using=nx.DiGraph)
142
+ assert nx.has_eulerian_path(G)
143
+
144
+ def test_has_eulerian_path_directed_graph(self):
145
+ # Test directed graphs and returns False
146
+ G = nx.DiGraph()
147
+ G.add_edges_from([(0, 1), (1, 2), (0, 2)])
148
+ assert not nx.has_eulerian_path(G)
149
+
150
+ # Test directed graphs without isolated node returns True
151
+ G = nx.DiGraph()
152
+ G.add_edges_from([(0, 1), (1, 2), (2, 0)])
153
+ assert nx.has_eulerian_path(G)
154
+
155
+ # Test directed graphs with isolated node returns False
156
+ G.add_node(3)
157
+ assert not nx.has_eulerian_path(G)
158
+
159
+ @pytest.mark.parametrize("G", (nx.Graph(), nx.DiGraph()))
160
+ def test_has_eulerian_path_not_weakly_connected(self, G):
161
+ G.add_edges_from([(0, 1), (2, 3), (3, 2)])
162
+ assert not nx.has_eulerian_path(G)
163
+
164
+ @pytest.mark.parametrize("G", (nx.Graph(), nx.DiGraph()))
165
+ def test_has_eulerian_path_unbalancedins_more_than_one(self, G):
166
+ G.add_edges_from([(0, 1), (2, 3)])
167
+ assert not nx.has_eulerian_path(G)
168
+
169
+
170
+ class TestFindPathStart:
171
+ def testfind_path_start(self):
172
+ find_path_start = nx.algorithms.euler._find_path_start
173
+ # Test digraphs return correct starting node.
174
+ G = nx.path_graph(6, create_using=nx.DiGraph)
175
+ assert find_path_start(G) == 0
176
+ edges = [(0, 1), (1, 2), (2, 0), (4, 0)]
177
+ assert find_path_start(nx.DiGraph(edges)) == 4
178
+
179
+ # Test graph with no Eulerian path return None.
180
+ edges = [(0, 1), (1, 2), (2, 3), (2, 4)]
181
+ assert find_path_start(nx.DiGraph(edges)) is None
182
+
183
+
184
+ class TestEulerianPath:
185
+ def test_eulerian_path(self):
186
+ x = [(4, 0), (0, 1), (1, 2), (2, 0)]
187
+ for e1, e2 in zip(x, nx.eulerian_path(nx.DiGraph(x))):
188
+ assert e1 == e2
189
+
190
+ def test_eulerian_path_straight_link(self):
191
+ G = nx.DiGraph()
192
+ result = [(1, 2), (2, 3), (3, 4), (4, 5)]
193
+ G.add_edges_from(result)
194
+ assert result == list(nx.eulerian_path(G))
195
+ assert result == list(nx.eulerian_path(G, source=1))
196
+ with pytest.raises(nx.NetworkXError):
197
+ list(nx.eulerian_path(G, source=3))
198
+ with pytest.raises(nx.NetworkXError):
199
+ list(nx.eulerian_path(G, source=4))
200
+ with pytest.raises(nx.NetworkXError):
201
+ list(nx.eulerian_path(G, source=5))
202
+
203
+ def test_eulerian_path_multigraph(self):
204
+ G = nx.MultiDiGraph()
205
+ result = [(2, 1), (1, 2), (2, 1), (1, 2), (2, 3), (3, 4), (4, 3)]
206
+ G.add_edges_from(result)
207
+ assert result == list(nx.eulerian_path(G))
208
+ assert result == list(nx.eulerian_path(G, source=2))
209
+ with pytest.raises(nx.NetworkXError):
210
+ list(nx.eulerian_path(G, source=3))
211
+ with pytest.raises(nx.NetworkXError):
212
+ list(nx.eulerian_path(G, source=4))
213
+
214
+ def test_eulerian_path_eulerian_circuit(self):
215
+ G = nx.DiGraph()
216
+ result = [(1, 2), (2, 3), (3, 4), (4, 1)]
217
+ result2 = [(2, 3), (3, 4), (4, 1), (1, 2)]
218
+ result3 = [(3, 4), (4, 1), (1, 2), (2, 3)]
219
+ G.add_edges_from(result)
220
+ assert result == list(nx.eulerian_path(G))
221
+ assert result == list(nx.eulerian_path(G, source=1))
222
+ assert result2 == list(nx.eulerian_path(G, source=2))
223
+ assert result3 == list(nx.eulerian_path(G, source=3))
224
+
225
+ def test_eulerian_path_undirected(self):
226
+ G = nx.Graph()
227
+ result = [(1, 2), (2, 3), (3, 4), (4, 5)]
228
+ result2 = [(5, 4), (4, 3), (3, 2), (2, 1)]
229
+ G.add_edges_from(result)
230
+ assert list(nx.eulerian_path(G)) in (result, result2)
231
+ assert result == list(nx.eulerian_path(G, source=1))
232
+ assert result2 == list(nx.eulerian_path(G, source=5))
233
+ with pytest.raises(nx.NetworkXError):
234
+ list(nx.eulerian_path(G, source=3))
235
+ with pytest.raises(nx.NetworkXError):
236
+ list(nx.eulerian_path(G, source=2))
237
+
238
+ def test_eulerian_path_multigraph_undirected(self):
239
+ G = nx.MultiGraph()
240
+ result = [(2, 1), (1, 2), (2, 1), (1, 2), (2, 3), (3, 4)]
241
+ G.add_edges_from(result)
242
+ assert result == list(nx.eulerian_path(G))
243
+ assert result == list(nx.eulerian_path(G, source=2))
244
+ with pytest.raises(nx.NetworkXError):
245
+ list(nx.eulerian_path(G, source=3))
246
+ with pytest.raises(nx.NetworkXError):
247
+ list(nx.eulerian_path(G, source=1))
248
+
249
+ @pytest.mark.parametrize(
250
+ ("graph_type", "result"),
251
+ (
252
+ (nx.MultiGraph, [(0, 1, 0), (1, 0, 1)]),
253
+ (nx.MultiDiGraph, [(0, 1, 0), (1, 0, 0)]),
254
+ ),
255
+ )
256
+ def test_eulerian_with_keys(self, graph_type, result):
257
+ G = graph_type([(0, 1), (1, 0)])
258
+ answer = nx.eulerian_path(G, keys=True)
259
+ assert list(answer) == result
260
+
261
+
262
+ class TestEulerize:
263
+ def test_disconnected(self):
264
+ with pytest.raises(nx.NetworkXError):
265
+ G = nx.from_edgelist([(0, 1), (2, 3)])
266
+ nx.eulerize(G)
267
+
268
+ def test_null_graph(self):
269
+ with pytest.raises(nx.NetworkXPointlessConcept):
270
+ nx.eulerize(nx.Graph())
271
+
272
+ def test_null_multigraph(self):
273
+ with pytest.raises(nx.NetworkXPointlessConcept):
274
+ nx.eulerize(nx.MultiGraph())
275
+
276
+ def test_on_empty_graph(self):
277
+ with pytest.raises(nx.NetworkXError):
278
+ nx.eulerize(nx.empty_graph(3))
279
+
280
+ def test_on_eulerian(self):
281
+ G = nx.cycle_graph(3)
282
+ H = nx.eulerize(G)
283
+ assert nx.is_isomorphic(G, H)
284
+
285
+ def test_on_eulerian_multigraph(self):
286
+ G = nx.MultiGraph(nx.cycle_graph(3))
287
+ G.add_edge(0, 1)
288
+ H = nx.eulerize(G)
289
+ assert nx.is_eulerian(H)
290
+
291
+ def test_on_complete_graph(self):
292
+ G = nx.complete_graph(4)
293
+ assert nx.is_eulerian(nx.eulerize(G))
294
+ assert nx.is_eulerian(nx.eulerize(nx.MultiGraph(G)))
295
+
296
+ def test_on_non_eulerian_graph(self):
297
+ G = nx.cycle_graph(18)
298
+ G.add_edge(0, 18)
299
+ G.add_edge(18, 19)
300
+ G.add_edge(17, 19)
301
+ G.add_edge(4, 20)
302
+ G.add_edge(20, 21)
303
+ G.add_edge(21, 22)
304
+ G.add_edge(22, 23)
305
+ G.add_edge(23, 24)
306
+ G.add_edge(24, 25)
307
+ G.add_edge(25, 26)
308
+ G.add_edge(26, 27)
309
+ G.add_edge(27, 28)
310
+ G.add_edge(28, 13)
311
+ assert not nx.is_eulerian(G)
312
+ G = nx.eulerize(G)
313
+ assert nx.is_eulerian(G)
314
+ assert nx.number_of_edges(G) == 39
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_graph_hashing.py ADDED
@@ -0,0 +1,872 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import copy
2
+
3
+ import pytest
4
+
5
+ import networkx as nx
6
+
7
+ # Unit tests relevant for both functions in this module.
8
+
9
+
10
+ def test_positive_iters():
11
+ G1 = nx.empty_graph()
12
+ with pytest.raises(
13
+ ValueError,
14
+ match="The WL algorithm requires that `iterations` be positive",
15
+ ):
16
+ nx.weisfeiler_lehman_graph_hash(G1, iterations=-3)
17
+ with pytest.raises(
18
+ ValueError,
19
+ match="The WL algorithm requires that `iterations` be positive",
20
+ ):
21
+ nx.weisfeiler_lehman_subgraph_hashes(G1, iterations=-3)
22
+ with pytest.raises(
23
+ ValueError,
24
+ match="The WL algorithm requires that `iterations` be positive",
25
+ ):
26
+ nx.weisfeiler_lehman_graph_hash(G1, iterations=0)
27
+ with pytest.raises(
28
+ ValueError,
29
+ match="The WL algorithm requires that `iterations` be positive",
30
+ ):
31
+ nx.weisfeiler_lehman_subgraph_hashes(G1, iterations=0)
32
+
33
+
34
+ # Unit tests for the :func:`~networkx.weisfeiler_lehman_graph_hash` function
35
+
36
+
37
+ def test_empty_graph_hash():
38
+ """
39
+ empty graphs should give hashes regardless of other params
40
+ """
41
+ G1 = nx.empty_graph()
42
+ G2 = nx.empty_graph()
43
+
44
+ h1 = nx.weisfeiler_lehman_graph_hash(G1)
45
+ h2 = nx.weisfeiler_lehman_graph_hash(G2)
46
+ h3 = nx.weisfeiler_lehman_graph_hash(G2, edge_attr="edge_attr1")
47
+ h4 = nx.weisfeiler_lehman_graph_hash(G2, node_attr="node_attr1")
48
+ h5 = nx.weisfeiler_lehman_graph_hash(
49
+ G2, edge_attr="edge_attr1", node_attr="node_attr1"
50
+ )
51
+ h6 = nx.weisfeiler_lehman_graph_hash(G2, iterations=10)
52
+
53
+ assert h1 == h2
54
+ assert h1 == h3
55
+ assert h1 == h4
56
+ assert h1 == h5
57
+ assert h1 == h6
58
+
59
+
60
+ def test_directed():
61
+ """
62
+ A directed graph with no bi-directional edges should yield different a graph hash
63
+ to the same graph taken as undirected if there are no hash collisions.
64
+ """
65
+ r = 10
66
+ for i in range(r):
67
+ G_directed = nx.gn_graph(10 + r, seed=100 + i)
68
+ G_undirected = nx.to_undirected(G_directed)
69
+
70
+ h_directed = nx.weisfeiler_lehman_graph_hash(G_directed)
71
+ h_undirected = nx.weisfeiler_lehman_graph_hash(G_undirected)
72
+
73
+ assert h_directed != h_undirected
74
+
75
+
76
+ def test_reversed():
77
+ """
78
+ A directed graph with no bi-directional edges should yield different a graph hash
79
+ to the same graph taken with edge directions reversed if there are no hash
80
+ collisions. Here we test a cycle graph which is the minimal counterexample
81
+ """
82
+ G = nx.cycle_graph(5, create_using=nx.DiGraph)
83
+ nx.set_node_attributes(G, {n: str(n) for n in G.nodes()}, name="label")
84
+
85
+ G_reversed = G.reverse()
86
+
87
+ h = nx.weisfeiler_lehman_graph_hash(G, node_attr="label")
88
+ h_reversed = nx.weisfeiler_lehman_graph_hash(G_reversed, node_attr="label")
89
+
90
+ assert h != h_reversed
91
+
92
+
93
+ def test_isomorphic():
94
+ """
95
+ graph hashes should be invariant to node-relabeling (when the output is reindexed
96
+ by the same mapping)
97
+ """
98
+ n, r = 100, 10
99
+ p = 1.0 / r
100
+ for i in range(1, r + 1):
101
+ G1 = nx.erdos_renyi_graph(n, p * i, seed=200 + i)
102
+ G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
103
+
104
+ g1_hash = nx.weisfeiler_lehman_graph_hash(G1)
105
+ g2_hash = nx.weisfeiler_lehman_graph_hash(G2)
106
+
107
+ assert g1_hash == g2_hash
108
+
109
+
110
+ def test_isomorphic_edge_attr():
111
+ """
112
+ Isomorphic graphs with differing edge attributes should yield different graph
113
+ hashes if the 'edge_attr' argument is supplied and populated in the graph,
114
+ and there are no hash collisions.
115
+ The output should still be invariant to node-relabeling
116
+ """
117
+ n, r = 100, 10
118
+ p = 1.0 / r
119
+ for i in range(1, r + 1):
120
+ G1 = nx.erdos_renyi_graph(n, p * i, seed=300 + i)
121
+
122
+ for a, b in G1.edges:
123
+ G1[a][b]["edge_attr1"] = f"{a}-{b}-1"
124
+ G1[a][b]["edge_attr2"] = f"{a}-{b}-2"
125
+
126
+ g1_hash_with_edge_attr1 = nx.weisfeiler_lehman_graph_hash(
127
+ G1, edge_attr="edge_attr1"
128
+ )
129
+ g1_hash_with_edge_attr2 = nx.weisfeiler_lehman_graph_hash(
130
+ G1, edge_attr="edge_attr2"
131
+ )
132
+ g1_hash_no_edge_attr = nx.weisfeiler_lehman_graph_hash(G1, edge_attr=None)
133
+
134
+ assert g1_hash_with_edge_attr1 != g1_hash_no_edge_attr
135
+ assert g1_hash_with_edge_attr2 != g1_hash_no_edge_attr
136
+ assert g1_hash_with_edge_attr1 != g1_hash_with_edge_attr2
137
+
138
+ G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
139
+
140
+ g2_hash_with_edge_attr1 = nx.weisfeiler_lehman_graph_hash(
141
+ G2, edge_attr="edge_attr1"
142
+ )
143
+ g2_hash_with_edge_attr2 = nx.weisfeiler_lehman_graph_hash(
144
+ G2, edge_attr="edge_attr2"
145
+ )
146
+
147
+ assert g1_hash_with_edge_attr1 == g2_hash_with_edge_attr1
148
+ assert g1_hash_with_edge_attr2 == g2_hash_with_edge_attr2
149
+
150
+
151
+ def test_missing_edge_attr():
152
+ """
153
+ If the 'edge_attr' argument is supplied but is missing from an edge in the graph,
154
+ we should raise a KeyError
155
+ """
156
+ G = nx.Graph()
157
+ G.add_edges_from([(1, 2, {"edge_attr1": "a"}), (1, 3, {})])
158
+ pytest.raises(KeyError, nx.weisfeiler_lehman_graph_hash, G, edge_attr="edge_attr1")
159
+
160
+
161
+ def test_isomorphic_node_attr():
162
+ """
163
+ Isomorphic graphs with differing node attributes should yield different graph
164
+ hashes if the 'node_attr' argument is supplied and populated in the graph, and
165
+ there are no hash collisions.
166
+ The output should still be invariant to node-relabeling
167
+ """
168
+ n, r = 100, 10
169
+ p = 1.0 / r
170
+ for i in range(1, r + 1):
171
+ G1 = nx.erdos_renyi_graph(n, p * i, seed=400 + i)
172
+
173
+ for u in G1.nodes():
174
+ G1.nodes[u]["node_attr1"] = f"{u}-1"
175
+ G1.nodes[u]["node_attr2"] = f"{u}-2"
176
+
177
+ g1_hash_with_node_attr1 = nx.weisfeiler_lehman_graph_hash(
178
+ G1, node_attr="node_attr1"
179
+ )
180
+ g1_hash_with_node_attr2 = nx.weisfeiler_lehman_graph_hash(
181
+ G1, node_attr="node_attr2"
182
+ )
183
+ g1_hash_no_node_attr = nx.weisfeiler_lehman_graph_hash(G1, node_attr=None)
184
+
185
+ assert g1_hash_with_node_attr1 != g1_hash_no_node_attr
186
+ assert g1_hash_with_node_attr2 != g1_hash_no_node_attr
187
+ assert g1_hash_with_node_attr1 != g1_hash_with_node_attr2
188
+
189
+ G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
190
+
191
+ g2_hash_with_node_attr1 = nx.weisfeiler_lehman_graph_hash(
192
+ G2, node_attr="node_attr1"
193
+ )
194
+ g2_hash_with_node_attr2 = nx.weisfeiler_lehman_graph_hash(
195
+ G2, node_attr="node_attr2"
196
+ )
197
+
198
+ assert g1_hash_with_node_attr1 == g2_hash_with_node_attr1
199
+ assert g1_hash_with_node_attr2 == g2_hash_with_node_attr2
200
+
201
+
202
+ def test_missing_node_attr():
203
+ """
204
+ If the 'node_attr' argument is supplied but is missing from a node in the graph,
205
+ we should raise a KeyError
206
+ """
207
+ G = nx.Graph()
208
+ G.add_nodes_from([(1, {"node_attr1": "a"}), (2, {})])
209
+ G.add_edges_from([(1, 2), (2, 3), (3, 1), (1, 4)])
210
+ pytest.raises(KeyError, nx.weisfeiler_lehman_graph_hash, G, node_attr="node_attr1")
211
+
212
+
213
+ def test_isomorphic_edge_attr_and_node_attr():
214
+ """
215
+ Isomorphic graphs with differing node attributes should yield different graph
216
+ hashes if the 'node_attr' and 'edge_attr' argument is supplied and populated in
217
+ the graph, and there are no hash collisions.
218
+ The output should still be invariant to node-relabeling
219
+ """
220
+ n, r = 100, 10
221
+ p = 1.0 / r
222
+ for i in range(1, r + 1):
223
+ G1 = nx.erdos_renyi_graph(n, p * i, seed=500 + i)
224
+
225
+ for u in G1.nodes():
226
+ G1.nodes[u]["node_attr1"] = f"{u}-1"
227
+ G1.nodes[u]["node_attr2"] = f"{u}-2"
228
+
229
+ for a, b in G1.edges:
230
+ G1[a][b]["edge_attr1"] = f"{a}-{b}-1"
231
+ G1[a][b]["edge_attr2"] = f"{a}-{b}-2"
232
+
233
+ g1_hash_edge1_node1 = nx.weisfeiler_lehman_graph_hash(
234
+ G1, edge_attr="edge_attr1", node_attr="node_attr1"
235
+ )
236
+ g1_hash_edge2_node2 = nx.weisfeiler_lehman_graph_hash(
237
+ G1, edge_attr="edge_attr2", node_attr="node_attr2"
238
+ )
239
+ g1_hash_edge1_node2 = nx.weisfeiler_lehman_graph_hash(
240
+ G1, edge_attr="edge_attr1", node_attr="node_attr2"
241
+ )
242
+ g1_hash_no_attr = nx.weisfeiler_lehman_graph_hash(G1)
243
+
244
+ assert g1_hash_edge1_node1 != g1_hash_no_attr
245
+ assert g1_hash_edge2_node2 != g1_hash_no_attr
246
+ assert g1_hash_edge1_node1 != g1_hash_edge2_node2
247
+ assert g1_hash_edge1_node2 != g1_hash_edge2_node2
248
+ assert g1_hash_edge1_node2 != g1_hash_edge1_node1
249
+
250
+ G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
251
+
252
+ g2_hash_edge1_node1 = nx.weisfeiler_lehman_graph_hash(
253
+ G2, edge_attr="edge_attr1", node_attr="node_attr1"
254
+ )
255
+ g2_hash_edge2_node2 = nx.weisfeiler_lehman_graph_hash(
256
+ G2, edge_attr="edge_attr2", node_attr="node_attr2"
257
+ )
258
+
259
+ assert g1_hash_edge1_node1 == g2_hash_edge1_node1
260
+ assert g1_hash_edge2_node2 == g2_hash_edge2_node2
261
+
262
+
263
+ def test_digest_size():
264
+ """
265
+ The hash string lengths should be as expected for a variety of graphs and
266
+ digest sizes
267
+ """
268
+ n, r = 100, 10
269
+ p = 1.0 / r
270
+ for i in range(1, r + 1):
271
+ G = nx.erdos_renyi_graph(n, p * i, seed=1000 + i)
272
+
273
+ h16 = nx.weisfeiler_lehman_graph_hash(G)
274
+ h32 = nx.weisfeiler_lehman_graph_hash(G, digest_size=32)
275
+
276
+ assert h16 != h32
277
+ assert len(h16) == 16 * 2
278
+ assert len(h32) == 32 * 2
279
+
280
+
281
+ def test_directed_bugs():
282
+ """
283
+ These were bugs for directed graphs as discussed in issue #7806
284
+ """
285
+ Ga = nx.DiGraph()
286
+ Gb = nx.DiGraph()
287
+ Ga.add_nodes_from([1, 2, 3, 4])
288
+ Gb.add_nodes_from([1, 2, 3, 4])
289
+ Ga.add_edges_from([(1, 2), (3, 2)])
290
+ Gb.add_edges_from([(1, 2), (3, 4)])
291
+ Ga_hash = nx.weisfeiler_lehman_graph_hash(Ga)
292
+ Gb_hash = nx.weisfeiler_lehman_graph_hash(Gb)
293
+ assert Ga_hash != Gb_hash
294
+
295
+ Tree1 = nx.DiGraph()
296
+ Tree1.add_edges_from([(0, 4), (1, 5), (2, 6), (3, 7)])
297
+ Tree1.add_edges_from([(4, 8), (5, 8), (6, 9), (7, 9)])
298
+ Tree1.add_edges_from([(8, 10), (9, 10)])
299
+ nx.set_node_attributes(
300
+ Tree1, {10: "s", 8: "a", 9: "a", 4: "b", 5: "b", 6: "b", 7: "b"}, "weight"
301
+ )
302
+ Tree2 = copy.deepcopy(Tree1)
303
+ nx.set_node_attributes(Tree1, {0: "d", 1: "c", 2: "d", 3: "c"}, "weight")
304
+ nx.set_node_attributes(Tree2, {0: "d", 1: "d", 2: "c", 3: "c"}, "weight")
305
+ Tree1_hash_short = nx.weisfeiler_lehman_graph_hash(
306
+ Tree1, iterations=1, node_attr="weight"
307
+ )
308
+ Tree2_hash_short = nx.weisfeiler_lehman_graph_hash(
309
+ Tree2, iterations=1, node_attr="weight"
310
+ )
311
+ assert Tree1_hash_short == Tree2_hash_short
312
+ Tree1_hash = nx.weisfeiler_lehman_graph_hash(
313
+ Tree1, node_attr="weight"
314
+ ) # Default is 3 iterations
315
+ Tree2_hash = nx.weisfeiler_lehman_graph_hash(Tree2, node_attr="weight")
316
+ assert Tree1_hash != Tree2_hash
317
+
318
+
319
+ def test_trivial_labels_isomorphism():
320
+ """
321
+ Trivial labelling of the graph should not change isomorphism verdicts.
322
+ """
323
+ n, r = 100, 10
324
+ p = 1.0 / r
325
+ for i in range(1, r + 1):
326
+ G1 = nx.erdos_renyi_graph(n, p * i, seed=500 + i)
327
+ G2 = nx.erdos_renyi_graph(n, p * i, seed=42 + i)
328
+ G1_hash = nx.weisfeiler_lehman_graph_hash(G1)
329
+ G2_hash = nx.weisfeiler_lehman_graph_hash(G2)
330
+ equal = G1_hash == G2_hash
331
+
332
+ nx.set_node_attributes(G1, values=1, name="weight")
333
+ nx.set_node_attributes(G2, values=1, name="weight")
334
+ G1_hash_node = nx.weisfeiler_lehman_graph_hash(G1, node_attr="weight")
335
+ G2_hash_node = nx.weisfeiler_lehman_graph_hash(G2, node_attr="weight")
336
+ equal_node = G1_hash_node == G2_hash_node
337
+
338
+ nx.set_edge_attributes(G1, values="a", name="e_weight")
339
+ nx.set_edge_attributes(G2, values="a", name="e_weight")
340
+ G1_hash_edge = nx.weisfeiler_lehman_graph_hash(G1, edge_attr="e_weight")
341
+ G2_hash_edge = nx.weisfeiler_lehman_graph_hash(G2, edge_attr="e_weight")
342
+ equal_edge = G1_hash_edge == G2_hash_edge
343
+
344
+ G1_hash_both = nx.weisfeiler_lehman_graph_hash(
345
+ G1, edge_attr="e_weight", node_attr="weight"
346
+ )
347
+ G2_hash_both = nx.weisfeiler_lehman_graph_hash(
348
+ G2, edge_attr="e_weight", node_attr="weight"
349
+ )
350
+ equal_both = G1_hash_both == G2_hash_both
351
+
352
+ assert equal == equal_node
353
+ assert equal_node == equal_edge
354
+ assert equal_edge == equal_both
355
+
356
+
357
+ def test_trivial_labels_isomorphism_directed():
358
+ """
359
+ Trivial labelling of the graph should not change isomorphism verdicts on digraphs.
360
+ """
361
+ n, r = 100, 10
362
+ p = 1.0 / r
363
+ for i in range(1, r + 1):
364
+ G1 = nx.erdos_renyi_graph(n, p * i, directed=True, seed=500 + i)
365
+ G2 = nx.erdos_renyi_graph(n, p * i, directed=True, seed=42 + i)
366
+ G1_hash = nx.weisfeiler_lehman_graph_hash(G1)
367
+ G2_hash = nx.weisfeiler_lehman_graph_hash(G2)
368
+ equal = G1_hash == G2_hash
369
+
370
+ nx.set_node_attributes(G1, values=1, name="weight")
371
+ nx.set_node_attributes(G2, values=1, name="weight")
372
+ G1_hash_node = nx.weisfeiler_lehman_graph_hash(G1, node_attr="weight")
373
+ G2_hash_node = nx.weisfeiler_lehman_graph_hash(G2, node_attr="weight")
374
+ equal_node = G1_hash_node == G2_hash_node
375
+
376
+ nx.set_edge_attributes(G1, values="a", name="e_weight")
377
+ nx.set_edge_attributes(G2, values="a", name="e_weight")
378
+ G1_hash_edge = nx.weisfeiler_lehman_graph_hash(G1, edge_attr="e_weight")
379
+ G2_hash_edge = nx.weisfeiler_lehman_graph_hash(G2, edge_attr="e_weight")
380
+ equal_edge = G1_hash_edge == G2_hash_edge
381
+
382
+ G1_hash_both = nx.weisfeiler_lehman_graph_hash(
383
+ G1, edge_attr="e_weight", node_attr="weight"
384
+ )
385
+ G2_hash_both = nx.weisfeiler_lehman_graph_hash(
386
+ G2, edge_attr="e_weight", node_attr="weight"
387
+ )
388
+ equal_both = G1_hash_both == G2_hash_both
389
+
390
+ assert equal == equal_node
391
+ assert equal_node == equal_edge
392
+ assert equal_edge == equal_both
393
+
394
+ # Specific case that was found to be a bug in issue #7806
395
+ # Without weights worked
396
+ Ga = nx.DiGraph()
397
+ Ga.add_nodes_from([1, 2, 3, 4])
398
+ Gb = copy.deepcopy(Ga)
399
+ Ga.add_edges_from([(1, 2), (3, 2)])
400
+ Gb.add_edges_from([(1, 2), (3, 4)])
401
+ Ga_hash = nx.weisfeiler_lehman_graph_hash(Ga)
402
+ Gb_hash = nx.weisfeiler_lehman_graph_hash(Gb)
403
+ assert Ga_hash != Gb_hash
404
+
405
+ # Now with trivial weights
406
+ nx.set_node_attributes(Ga, values=1, name="weight")
407
+ nx.set_node_attributes(Gb, values=1, name="weight")
408
+ Ga_hash = nx.weisfeiler_lehman_graph_hash(Ga, node_attr="weight")
409
+ Gb_hash = nx.weisfeiler_lehman_graph_hash(Gb, node_attr="weight")
410
+ assert Ga_hash != Gb_hash
411
+
412
+
413
+ def test_trivial_labels_hashes():
414
+ """
415
+ Test that 'empty' labelling of nodes or edges shouldn't have a different impact
416
+ on the calculated hash. Note that we cannot assume that trivial weights have no
417
+ impact at all. Without (trivial) weights, a node will start with hashing its
418
+ degree. This step is omitted when there are weights.
419
+ """
420
+ n, r = 100, 10
421
+ p = 1.0 / r
422
+ for i in range(1, r + 1):
423
+ G1 = nx.erdos_renyi_graph(n, p * i, seed=500 + i)
424
+ nx.set_node_attributes(G1, values="", name="weight")
425
+ first = nx.weisfeiler_lehman_graph_hash(G1, node_attr="weight")
426
+ nx.set_edge_attributes(G1, values="", name="e_weight")
427
+ second = nx.weisfeiler_lehman_graph_hash(G1, edge_attr="e_weight")
428
+ assert first == second
429
+ third = nx.weisfeiler_lehman_graph_hash(
430
+ G1, edge_attr="e_weight", node_attr="weight"
431
+ )
432
+ assert second == third
433
+
434
+
435
+ # Unit tests for the :func:`~networkx.weisfeiler_lehman_subgraph_hashes` function
436
+
437
+
438
+ def is_subiteration(a, b):
439
+ """
440
+ returns True if that each hash sequence in 'a' is a prefix for
441
+ the corresponding sequence indexed by the same node in 'b'.
442
+ """
443
+ return all(b[node][: len(hashes)] == hashes for node, hashes in a.items())
444
+
445
+
446
+ def hexdigest_sizes_correct(a, digest_size):
447
+ """
448
+ returns True if all hex digest sizes are the expected length in a
449
+ node:subgraph-hashes dictionary. Hex digest string length == 2 * bytes digest
450
+ length since each pair of hex digits encodes 1 byte
451
+ (https://docs.python.org/3/library/hashlib.html)
452
+ """
453
+ hexdigest_size = digest_size * 2
454
+
455
+ def list_digest_sizes_correct(l):
456
+ return all(len(x) == hexdigest_size for x in l)
457
+
458
+ return all(list_digest_sizes_correct(hashes) for hashes in a.values())
459
+
460
+
461
+ def test_empty_graph_subgraph_hash():
462
+ """ "
463
+ empty graphs should give empty dict subgraph hashes regardless of other params
464
+ """
465
+ G = nx.empty_graph()
466
+
467
+ subgraph_hashes1 = nx.weisfeiler_lehman_subgraph_hashes(G)
468
+ subgraph_hashes2 = nx.weisfeiler_lehman_subgraph_hashes(G, edge_attr="edge_attr")
469
+ subgraph_hashes3 = nx.weisfeiler_lehman_subgraph_hashes(G, node_attr="edge_attr")
470
+ subgraph_hashes4 = nx.weisfeiler_lehman_subgraph_hashes(G, iterations=2)
471
+ subgraph_hashes5 = nx.weisfeiler_lehman_subgraph_hashes(G, digest_size=64)
472
+
473
+ assert subgraph_hashes1 == {}
474
+ assert subgraph_hashes2 == {}
475
+ assert subgraph_hashes3 == {}
476
+ assert subgraph_hashes4 == {}
477
+ assert subgraph_hashes5 == {}
478
+
479
+
480
+ def test_directed_subgraph_hash():
481
+ """
482
+ A directed graph with no bi-directional edges should yield different subgraph
483
+ hashes to the same graph taken as undirected, if all hashes don't collide.
484
+ """
485
+ r = 10
486
+ for i in range(r):
487
+ G_directed = nx.gn_graph(10 + r, seed=100 + i)
488
+ G_undirected = nx.to_undirected(G_directed)
489
+
490
+ directed_subgraph_hashes = nx.weisfeiler_lehman_subgraph_hashes(G_directed)
491
+ undirected_subgraph_hashes = nx.weisfeiler_lehman_subgraph_hashes(G_undirected)
492
+
493
+ assert directed_subgraph_hashes != undirected_subgraph_hashes
494
+
495
+
496
+ def test_reversed_subgraph_hash():
497
+ """
498
+ A directed graph with no bi-directional edges should yield different subgraph
499
+ hashes to the same graph taken with edge directions reversed if there are no
500
+ hash collisions. Here we test a cycle graph which is the minimal counterexample
501
+ """
502
+ G = nx.cycle_graph(5, create_using=nx.DiGraph)
503
+ nx.set_node_attributes(G, {n: str(n) for n in G.nodes()}, name="label")
504
+
505
+ G_reversed = G.reverse()
506
+
507
+ h = nx.weisfeiler_lehman_subgraph_hashes(G, node_attr="label")
508
+ h_reversed = nx.weisfeiler_lehman_subgraph_hashes(G_reversed, node_attr="label")
509
+
510
+ assert h != h_reversed
511
+
512
+
513
+ def test_isomorphic_subgraph_hash():
514
+ """
515
+ the subgraph hashes should be invariant to node-relabeling when the output is
516
+ reindexed by the same mapping and all hashes don't collide.
517
+ """
518
+ n, r = 100, 10
519
+ p = 1.0 / r
520
+ for i in range(1, r + 1):
521
+ G1 = nx.erdos_renyi_graph(n, p * i, seed=200 + i)
522
+ G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
523
+
524
+ g1_subgraph_hashes = nx.weisfeiler_lehman_subgraph_hashes(G1)
525
+ g2_subgraph_hashes = nx.weisfeiler_lehman_subgraph_hashes(G2)
526
+
527
+ assert g1_subgraph_hashes == {-1 * k: v for k, v in g2_subgraph_hashes.items()}
528
+
529
+
530
+ def test_isomorphic_edge_attr_subgraph_hash():
531
+ """
532
+ Isomorphic graphs with differing edge attributes should yield different subgraph
533
+ hashes if the 'edge_attr' argument is supplied and populated in the graph, and
534
+ all hashes don't collide.
535
+ The output should still be invariant to node-relabeling
536
+ """
537
+ n, r = 100, 10
538
+ p = 1.0 / r
539
+ for i in range(1, r + 1):
540
+ G1 = nx.erdos_renyi_graph(n, p * i, seed=300 + i)
541
+
542
+ for a, b in G1.edges:
543
+ G1[a][b]["edge_attr1"] = f"{a}-{b}-1"
544
+ G1[a][b]["edge_attr2"] = f"{a}-{b}-2"
545
+
546
+ g1_hash_with_edge_attr1 = nx.weisfeiler_lehman_subgraph_hashes(
547
+ G1, edge_attr="edge_attr1"
548
+ )
549
+ g1_hash_with_edge_attr2 = nx.weisfeiler_lehman_subgraph_hashes(
550
+ G1, edge_attr="edge_attr2"
551
+ )
552
+ g1_hash_no_edge_attr = nx.weisfeiler_lehman_subgraph_hashes(G1, edge_attr=None)
553
+
554
+ assert g1_hash_with_edge_attr1 != g1_hash_no_edge_attr
555
+ assert g1_hash_with_edge_attr2 != g1_hash_no_edge_attr
556
+ assert g1_hash_with_edge_attr1 != g1_hash_with_edge_attr2
557
+
558
+ G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
559
+
560
+ g2_hash_with_edge_attr1 = nx.weisfeiler_lehman_subgraph_hashes(
561
+ G2, edge_attr="edge_attr1"
562
+ )
563
+ g2_hash_with_edge_attr2 = nx.weisfeiler_lehman_subgraph_hashes(
564
+ G2, edge_attr="edge_attr2"
565
+ )
566
+
567
+ assert g1_hash_with_edge_attr1 == {
568
+ -1 * k: v for k, v in g2_hash_with_edge_attr1.items()
569
+ }
570
+ assert g1_hash_with_edge_attr2 == {
571
+ -1 * k: v for k, v in g2_hash_with_edge_attr2.items()
572
+ }
573
+
574
+
575
+ def test_missing_edge_attr_subgraph_hash():
576
+ """
577
+ If the 'edge_attr' argument is supplied but is missing from an edge in the graph,
578
+ we should raise a KeyError
579
+ """
580
+ G = nx.Graph()
581
+ G.add_edges_from([(1, 2, {"edge_attr1": "a"}), (1, 3, {})])
582
+ pytest.raises(
583
+ KeyError, nx.weisfeiler_lehman_subgraph_hashes, G, edge_attr="edge_attr1"
584
+ )
585
+
586
+
587
+ def test_isomorphic_node_attr_subgraph_hash():
588
+ """
589
+ Isomorphic graphs with differing node attributes should yield different subgraph
590
+ hashes if the 'node_attr' argument is supplied and populated in the graph, and
591
+ all hashes don't collide.
592
+ The output should still be invariant to node-relabeling
593
+ """
594
+ n, r = 100, 10
595
+ p = 1.0 / r
596
+ for i in range(1, r + 1):
597
+ G1 = nx.erdos_renyi_graph(n, p * i, seed=400 + i)
598
+
599
+ for u in G1.nodes():
600
+ G1.nodes[u]["node_attr1"] = f"{u}-1"
601
+ G1.nodes[u]["node_attr2"] = f"{u}-2"
602
+
603
+ g1_hash_with_node_attr1 = nx.weisfeiler_lehman_subgraph_hashes(
604
+ G1, node_attr="node_attr1"
605
+ )
606
+ g1_hash_with_node_attr2 = nx.weisfeiler_lehman_subgraph_hashes(
607
+ G1, node_attr="node_attr2"
608
+ )
609
+ g1_hash_no_node_attr = nx.weisfeiler_lehman_subgraph_hashes(G1, node_attr=None)
610
+
611
+ assert g1_hash_with_node_attr1 != g1_hash_no_node_attr
612
+ assert g1_hash_with_node_attr2 != g1_hash_no_node_attr
613
+ assert g1_hash_with_node_attr1 != g1_hash_with_node_attr2
614
+
615
+ G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
616
+
617
+ g2_hash_with_node_attr1 = nx.weisfeiler_lehman_subgraph_hashes(
618
+ G2, node_attr="node_attr1"
619
+ )
620
+ g2_hash_with_node_attr2 = nx.weisfeiler_lehman_subgraph_hashes(
621
+ G2, node_attr="node_attr2"
622
+ )
623
+
624
+ assert g1_hash_with_node_attr1 == {
625
+ -1 * k: v for k, v in g2_hash_with_node_attr1.items()
626
+ }
627
+ assert g1_hash_with_node_attr2 == {
628
+ -1 * k: v for k, v in g2_hash_with_node_attr2.items()
629
+ }
630
+
631
+
632
+ def test_missing_node_attr_subgraph_hash():
633
+ """
634
+ If the 'node_attr' argument is supplied but is missing from a node in the graph,
635
+ we should raise a KeyError
636
+ """
637
+ G = nx.Graph()
638
+ G.add_nodes_from([(1, {"node_attr1": "a"}), (2, {})])
639
+ G.add_edges_from([(1, 2), (2, 3), (3, 1), (1, 4)])
640
+ pytest.raises(
641
+ KeyError, nx.weisfeiler_lehman_subgraph_hashes, G, node_attr="node_attr1"
642
+ )
643
+
644
+
645
+ def test_isomorphic_edge_attr_and_node_attr_subgraph_hash():
646
+ """
647
+ Isomorphic graphs with differing node attributes should yield different subgraph
648
+ hashes if the 'node_attr' and 'edge_attr' argument is supplied and populated in
649
+ the graph, and all hashes don't collide
650
+ The output should still be invariant to node-relabeling
651
+ """
652
+ n, r = 100, 10
653
+ p = 1.0 / r
654
+ for i in range(1, r + 1):
655
+ G1 = nx.erdos_renyi_graph(n, p * i, seed=500 + i)
656
+
657
+ for u in G1.nodes():
658
+ G1.nodes[u]["node_attr1"] = f"{u}-1"
659
+ G1.nodes[u]["node_attr2"] = f"{u}-2"
660
+
661
+ for a, b in G1.edges:
662
+ G1[a][b]["edge_attr1"] = f"{a}-{b}-1"
663
+ G1[a][b]["edge_attr2"] = f"{a}-{b}-2"
664
+
665
+ g1_hash_edge1_node1 = nx.weisfeiler_lehman_subgraph_hashes(
666
+ G1, edge_attr="edge_attr1", node_attr="node_attr1"
667
+ )
668
+ g1_hash_edge2_node2 = nx.weisfeiler_lehman_subgraph_hashes(
669
+ G1, edge_attr="edge_attr2", node_attr="node_attr2"
670
+ )
671
+ g1_hash_edge1_node2 = nx.weisfeiler_lehman_subgraph_hashes(
672
+ G1, edge_attr="edge_attr1", node_attr="node_attr2"
673
+ )
674
+ g1_hash_no_attr = nx.weisfeiler_lehman_subgraph_hashes(G1)
675
+
676
+ assert g1_hash_edge1_node1 != g1_hash_no_attr
677
+ assert g1_hash_edge2_node2 != g1_hash_no_attr
678
+ assert g1_hash_edge1_node1 != g1_hash_edge2_node2
679
+ assert g1_hash_edge1_node2 != g1_hash_edge2_node2
680
+ assert g1_hash_edge1_node2 != g1_hash_edge1_node1
681
+
682
+ G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
683
+
684
+ g2_hash_edge1_node1 = nx.weisfeiler_lehman_subgraph_hashes(
685
+ G2, edge_attr="edge_attr1", node_attr="node_attr1"
686
+ )
687
+ g2_hash_edge2_node2 = nx.weisfeiler_lehman_subgraph_hashes(
688
+ G2, edge_attr="edge_attr2", node_attr="node_attr2"
689
+ )
690
+
691
+ assert g1_hash_edge1_node1 == {
692
+ -1 * k: v for k, v in g2_hash_edge1_node1.items()
693
+ }
694
+ assert g1_hash_edge2_node2 == {
695
+ -1 * k: v for k, v in g2_hash_edge2_node2.items()
696
+ }
697
+
698
+
699
+ def test_iteration_depth():
700
+ """
701
+ All nodes should have the correct number of subgraph hashes in the output when
702
+ using degree as initial node labels.
703
+ Subsequent iteration depths for the same graph should be additive for each node
704
+ """
705
+ n, r = 100, 10
706
+ p = 1.0 / r
707
+ for i in range(1, r + 1):
708
+ G = nx.erdos_renyi_graph(n, p * i, seed=600 + i)
709
+
710
+ depth3 = nx.weisfeiler_lehman_subgraph_hashes(G, iterations=3)
711
+ depth4 = nx.weisfeiler_lehman_subgraph_hashes(G, iterations=4)
712
+ depth5 = nx.weisfeiler_lehman_subgraph_hashes(G, iterations=5)
713
+
714
+ assert all(len(hashes) == 3 for hashes in depth3.values())
715
+ assert all(len(hashes) == 4 for hashes in depth4.values())
716
+ assert all(len(hashes) == 5 for hashes in depth5.values())
717
+
718
+ assert is_subiteration(depth3, depth4)
719
+ assert is_subiteration(depth4, depth5)
720
+ assert is_subiteration(depth3, depth5)
721
+
722
+
723
+ def test_iteration_depth_edge_attr():
724
+ """
725
+ All nodes should have the correct number of subgraph hashes in the output when
726
+ setting initial node labels empty and using an edge attribute when aggregating
727
+ neighborhoods.
728
+ Subsequent iteration depths for the same graph should be additive for each node
729
+ """
730
+ n, r = 100, 10
731
+ p = 1.0 / r
732
+ for i in range(1, r + 1):
733
+ G = nx.erdos_renyi_graph(n, p * i, seed=700 + i)
734
+
735
+ for a, b in G.edges:
736
+ G[a][b]["edge_attr1"] = f"{a}-{b}-1"
737
+
738
+ depth3 = nx.weisfeiler_lehman_subgraph_hashes(
739
+ G, edge_attr="edge_attr1", iterations=3
740
+ )
741
+ depth4 = nx.weisfeiler_lehman_subgraph_hashes(
742
+ G, edge_attr="edge_attr1", iterations=4
743
+ )
744
+ depth5 = nx.weisfeiler_lehman_subgraph_hashes(
745
+ G, edge_attr="edge_attr1", iterations=5
746
+ )
747
+
748
+ assert all(len(hashes) == 3 for hashes in depth3.values())
749
+ assert all(len(hashes) == 4 for hashes in depth4.values())
750
+ assert all(len(hashes) == 5 for hashes in depth5.values())
751
+
752
+ assert is_subiteration(depth3, depth4)
753
+ assert is_subiteration(depth4, depth5)
754
+ assert is_subiteration(depth3, depth5)
755
+
756
+
757
+ def test_iteration_depth_node_attr():
758
+ """
759
+ All nodes should have the correct number of subgraph hashes in the output when
760
+ setting initial node labels to an attribute.
761
+ Subsequent iteration depths for the same graph should be additive for each node
762
+ """
763
+ n, r = 100, 10
764
+ p = 1.0 / r
765
+ for i in range(1, r + 1):
766
+ G = nx.erdos_renyi_graph(n, p * i, seed=800 + i)
767
+
768
+ for u in G.nodes():
769
+ G.nodes[u]["node_attr1"] = f"{u}-1"
770
+
771
+ depth3 = nx.weisfeiler_lehman_subgraph_hashes(
772
+ G, node_attr="node_attr1", iterations=3
773
+ )
774
+ depth4 = nx.weisfeiler_lehman_subgraph_hashes(
775
+ G, node_attr="node_attr1", iterations=4
776
+ )
777
+ depth5 = nx.weisfeiler_lehman_subgraph_hashes(
778
+ G, node_attr="node_attr1", iterations=5
779
+ )
780
+
781
+ assert all(len(hashes) == 3 for hashes in depth3.values())
782
+ assert all(len(hashes) == 4 for hashes in depth4.values())
783
+ assert all(len(hashes) == 5 for hashes in depth5.values())
784
+
785
+ assert is_subiteration(depth3, depth4)
786
+ assert is_subiteration(depth4, depth5)
787
+ assert is_subiteration(depth3, depth5)
788
+
789
+
790
+ def test_iteration_depth_node_edge_attr():
791
+ """
792
+ All nodes should have the correct number of subgraph hashes in the output when
793
+ setting initial node labels to an attribute and also using an edge attribute when
794
+ aggregating neighborhoods.
795
+ Subsequent iteration depths for the same graph should be additive for each node
796
+ """
797
+ n, r = 100, 10
798
+ p = 1.0 / r
799
+ for i in range(1, r + 1):
800
+ G = nx.erdos_renyi_graph(n, p * i, seed=900 + i)
801
+
802
+ for u in G.nodes():
803
+ G.nodes[u]["node_attr1"] = f"{u}-1"
804
+
805
+ for a, b in G.edges:
806
+ G[a][b]["edge_attr1"] = f"{a}-{b}-1"
807
+
808
+ depth3 = nx.weisfeiler_lehman_subgraph_hashes(
809
+ G, edge_attr="edge_attr1", node_attr="node_attr1", iterations=3
810
+ )
811
+ depth4 = nx.weisfeiler_lehman_subgraph_hashes(
812
+ G, edge_attr="edge_attr1", node_attr="node_attr1", iterations=4
813
+ )
814
+ depth5 = nx.weisfeiler_lehman_subgraph_hashes(
815
+ G, edge_attr="edge_attr1", node_attr="node_attr1", iterations=5
816
+ )
817
+
818
+ assert all(len(hashes) == 3 for hashes in depth3.values())
819
+ assert all(len(hashes) == 4 for hashes in depth4.values())
820
+ assert all(len(hashes) == 5 for hashes in depth5.values())
821
+
822
+ assert is_subiteration(depth3, depth4)
823
+ assert is_subiteration(depth4, depth5)
824
+ assert is_subiteration(depth3, depth5)
825
+
826
+
827
+ def test_digest_size_subgraph_hash():
828
+ """
829
+ The hash string lengths should be as expected for a variety of graphs and
830
+ digest sizes
831
+ """
832
+ n, r = 100, 10
833
+ p = 1.0 / r
834
+ for i in range(1, r + 1):
835
+ G = nx.erdos_renyi_graph(n, p * i, seed=1000 + i)
836
+
837
+ digest_size16_hashes = nx.weisfeiler_lehman_subgraph_hashes(G)
838
+ digest_size32_hashes = nx.weisfeiler_lehman_subgraph_hashes(G, digest_size=32)
839
+
840
+ assert digest_size16_hashes != digest_size32_hashes
841
+
842
+ assert hexdigest_sizes_correct(digest_size16_hashes, 16)
843
+ assert hexdigest_sizes_correct(digest_size32_hashes, 32)
844
+
845
+
846
+ def test_initial_node_labels_subgraph_hash():
847
+ """
848
+ Including the hashed initial label prepends an extra hash to the lists
849
+ """
850
+ G = nx.path_graph(5)
851
+ nx.set_node_attributes(G, {i: int(0 < i < 4) for i in G}, "label")
852
+ # initial node labels:
853
+ # 0--1--1--1--0
854
+
855
+ without_initial_label = nx.weisfeiler_lehman_subgraph_hashes(G, node_attr="label")
856
+ assert all(len(v) == 3 for v in without_initial_label.values())
857
+ # 3 different 1 hop nhds
858
+ assert len({v[0] for v in without_initial_label.values()}) == 3
859
+
860
+ with_initial_label = nx.weisfeiler_lehman_subgraph_hashes(
861
+ G, node_attr="label", include_initial_labels=True
862
+ )
863
+ assert all(len(v) == 4 for v in with_initial_label.values())
864
+ # 2 different initial labels
865
+ assert len({v[0] for v in with_initial_label.values()}) == 2
866
+
867
+ # check hashes match otherwise
868
+ for u in G:
869
+ for a, b in zip(
870
+ with_initial_label[u][1:], without_initial_label[u], strict=True
871
+ ):
872
+ assert a == b
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_graphical.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import networkx as nx
4
+
5
+
6
+ def test_valid_degree_sequence1():
7
+ n = 100
8
+ p = 0.3
9
+ for i in range(10):
10
+ G = nx.erdos_renyi_graph(n, p)
11
+ deg = (d for n, d in G.degree())
12
+ assert nx.is_graphical(deg, method="eg")
13
+ assert nx.is_graphical(deg, method="hh")
14
+
15
+
16
+ def test_valid_degree_sequence2():
17
+ n = 100
18
+ for i in range(10):
19
+ G = nx.barabasi_albert_graph(n, 1)
20
+ deg = (d for n, d in G.degree())
21
+ assert nx.is_graphical(deg, method="eg")
22
+ assert nx.is_graphical(deg, method="hh")
23
+
24
+
25
+ def test_string_input():
26
+ pytest.raises(nx.NetworkXException, nx.is_graphical, [], "foo")
27
+ pytest.raises(nx.NetworkXException, nx.is_graphical, ["red"], "hh")
28
+ pytest.raises(nx.NetworkXException, nx.is_graphical, ["red"], "eg")
29
+
30
+
31
+ def test_non_integer_input():
32
+ pytest.raises(nx.NetworkXException, nx.is_graphical, [72.5], "eg")
33
+ pytest.raises(nx.NetworkXException, nx.is_graphical, [72.5], "hh")
34
+
35
+
36
+ def test_negative_input():
37
+ assert not nx.is_graphical([-1], "hh")
38
+ assert not nx.is_graphical([-1], "eg")
39
+
40
+
41
+ class TestAtlas:
42
+ @classmethod
43
+ def setup_class(cls):
44
+ global atlas
45
+ from networkx.generators import atlas
46
+
47
+ cls.GAG = atlas.graph_atlas_g()
48
+
49
+ def test_atlas(self):
50
+ for graph in self.GAG:
51
+ deg = (d for n, d in graph.degree())
52
+ assert nx.is_graphical(deg, method="eg")
53
+ assert nx.is_graphical(deg, method="hh")
54
+
55
+
56
+ def test_small_graph_true():
57
+ z = [5, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1]
58
+ assert nx.is_graphical(z, method="hh")
59
+ assert nx.is_graphical(z, method="eg")
60
+ z = [10, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2]
61
+ assert nx.is_graphical(z, method="hh")
62
+ assert nx.is_graphical(z, method="eg")
63
+ z = [1, 1, 1, 1, 1, 2, 2, 2, 3, 4]
64
+ assert nx.is_graphical(z, method="hh")
65
+ assert nx.is_graphical(z, method="eg")
66
+
67
+
68
+ def test_small_graph_false():
69
+ z = [1000, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1]
70
+ assert not nx.is_graphical(z, method="hh")
71
+ assert not nx.is_graphical(z, method="eg")
72
+ z = [6, 5, 4, 4, 2, 1, 1, 1]
73
+ assert not nx.is_graphical(z, method="hh")
74
+ assert not nx.is_graphical(z, method="eg")
75
+ z = [1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4]
76
+ assert not nx.is_graphical(z, method="hh")
77
+ assert not nx.is_graphical(z, method="eg")
78
+
79
+
80
+ def test_directed_degree_sequence():
81
+ # Test a range of valid directed degree sequences
82
+ n, r = 100, 10
83
+ p = 1.0 / r
84
+ for i in range(r):
85
+ G = nx.erdos_renyi_graph(n, p * (i + 1), None, True)
86
+ din = (d for n, d in G.in_degree())
87
+ dout = (d for n, d in G.out_degree())
88
+ assert nx.is_digraphical(din, dout)
89
+
90
+
91
+ def test_small_directed_sequences():
92
+ dout = [5, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1]
93
+ din = [3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1]
94
+ assert nx.is_digraphical(din, dout)
95
+ # Test nongraphical directed sequence
96
+ dout = [1000, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1]
97
+ din = [103, 102, 102, 102, 102, 102, 102, 102, 102, 102]
98
+ assert not nx.is_digraphical(din, dout)
99
+ # Test digraphical small sequence
100
+ dout = [1, 1, 1, 1, 1, 2, 2, 2, 3, 4]
101
+ din = [2, 2, 2, 2, 2, 2, 2, 2, 1, 1]
102
+ assert nx.is_digraphical(din, dout)
103
+ # Test nonmatching sum
104
+ din = [2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1]
105
+ assert not nx.is_digraphical(din, dout)
106
+ # Test for negative integer in sequence
107
+ din = [2, 2, 2, -2, 2, 2, 2, 2, 1, 1, 4]
108
+ assert not nx.is_digraphical(din, dout)
109
+ # Test for noninteger
110
+ din = dout = [1, 1, 1.1, 1]
111
+ assert not nx.is_digraphical(din, dout)
112
+ din = dout = [1, 1, "rer", 1]
113
+ assert not nx.is_digraphical(din, dout)
114
+
115
+
116
+ def test_multi_sequence():
117
+ # Test nongraphical multi sequence
118
+ seq = [1000, 3, 3, 3, 3, 2, 2, 2, 1, 1]
119
+ assert not nx.is_multigraphical(seq)
120
+ # Test small graphical multi sequence
121
+ seq = [6, 5, 4, 4, 2, 1, 1, 1]
122
+ assert nx.is_multigraphical(seq)
123
+ # Test for negative integer in sequence
124
+ seq = [6, 5, 4, -4, 2, 1, 1, 1]
125
+ assert not nx.is_multigraphical(seq)
126
+ # Test for sequence with odd sum
127
+ seq = [1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4]
128
+ assert not nx.is_multigraphical(seq)
129
+ # Test for noninteger
130
+ seq = [1, 1, 1.1, 1]
131
+ assert not nx.is_multigraphical(seq)
132
+ seq = [1, 1, "rer", 1]
133
+ assert not nx.is_multigraphical(seq)
134
+
135
+
136
+ def test_pseudo_sequence():
137
+ # Test small valid pseudo sequence
138
+ seq = [1000, 3, 3, 3, 3, 2, 2, 2, 1, 1]
139
+ assert nx.is_pseudographical(seq)
140
+ # Test for sequence with odd sum
141
+ seq = [1000, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1]
142
+ assert not nx.is_pseudographical(seq)
143
+ # Test for negative integer in sequence
144
+ seq = [1000, 3, 3, 3, 3, 2, 2, -2, 1, 1]
145
+ assert not nx.is_pseudographical(seq)
146
+ # Test for noninteger
147
+ seq = [1, 1, 1.1, 1]
148
+ assert not nx.is_pseudographical(seq)
149
+ seq = [1, 1, "rer", 1]
150
+ assert not nx.is_pseudographical(seq)
151
+
152
+
153
+ def test_numpy_degree_sequence():
154
+ np = pytest.importorskip("numpy")
155
+ ds = np.array([1, 2, 2, 2, 1], dtype=np.int64)
156
+ assert nx.is_graphical(ds, "eg")
157
+ assert nx.is_graphical(ds, "hh")
158
+ ds = np.array([1, 2, 2, 2, 1], dtype=np.float64)
159
+ assert nx.is_graphical(ds, "eg")
160
+ assert nx.is_graphical(ds, "hh")
161
+ ds = np.array([1.1, 2, 2, 2, 1], dtype=np.float64)
162
+ pytest.raises(nx.NetworkXException, nx.is_graphical, ds, "eg")
163
+ pytest.raises(nx.NetworkXException, nx.is_graphical, ds, "hh")
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_hierarchy.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import networkx as nx
4
+
5
+
6
+ def test_hierarchy_undirected():
7
+ G = nx.cycle_graph(5)
8
+ pytest.raises(nx.NetworkXError, nx.flow_hierarchy, G)
9
+
10
+
11
+ def test_hierarchy_cycle():
12
+ G = nx.cycle_graph(5, create_using=nx.DiGraph())
13
+ assert nx.flow_hierarchy(G) == 0.0
14
+
15
+
16
+ def test_hierarchy_tree():
17
+ G = nx.full_rary_tree(2, 16, create_using=nx.DiGraph())
18
+ assert nx.flow_hierarchy(G) == 1.0
19
+
20
+
21
+ def test_hierarchy_1():
22
+ G = nx.DiGraph()
23
+ G.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 1), (3, 4), (0, 4)])
24
+ assert nx.flow_hierarchy(G) == 0.5
25
+
26
+
27
+ def test_hierarchy_weight():
28
+ G = nx.DiGraph()
29
+ G.add_edges_from(
30
+ [
31
+ (0, 1, {"weight": 0.3}),
32
+ (1, 2, {"weight": 0.1}),
33
+ (2, 3, {"weight": 0.1}),
34
+ (3, 1, {"weight": 0.1}),
35
+ (3, 4, {"weight": 0.3}),
36
+ (0, 4, {"weight": 0.3}),
37
+ ]
38
+ )
39
+ assert nx.flow_hierarchy(G, weight="weight") == 0.75
40
+
41
+
42
+ @pytest.mark.parametrize("n", (0, 1, 3))
43
+ def test_hierarchy_empty_graph(n):
44
+ G = nx.empty_graph(n, create_using=nx.DiGraph)
45
+ with pytest.raises(nx.NetworkXError, match=".*not applicable to empty graphs"):
46
+ nx.flow_hierarchy(G)
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_hybrid.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import networkx as nx
2
+
3
+
4
+ def test_2d_grid_graph():
5
+ # FC article claims 2d grid graph of size n is (3,3)-connected
6
+ # and (5,9)-connected, but I don't think it is (5,9)-connected
7
+ G = nx.grid_2d_graph(8, 8, periodic=True)
8
+ assert nx.is_kl_connected(G, 3, 3)
9
+ assert not nx.is_kl_connected(G, 5, 9)
10
+ (H, graphOK) = nx.kl_connected_subgraph(G, 5, 9, same_as_graph=True)
11
+ assert not graphOK
12
+
13
+
14
+ def test_small_graph():
15
+ G = nx.Graph()
16
+ G.add_edge(1, 2)
17
+ G.add_edge(1, 3)
18
+ G.add_edge(2, 3)
19
+ assert nx.is_kl_connected(G, 2, 2)
20
+ H = nx.kl_connected_subgraph(G, 2, 2)
21
+ (H, graphOK) = nx.kl_connected_subgraph(
22
+ G, 2, 2, low_memory=True, same_as_graph=True
23
+ )
24
+ assert graphOK
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_isolate.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Unit tests for the :mod:`networkx.algorithms.isolates` module."""
2
+
3
+ import networkx as nx
4
+
5
+
6
+ def test_is_isolate():
7
+ G = nx.Graph()
8
+ G.add_edge(0, 1)
9
+ G.add_node(2)
10
+ assert not nx.is_isolate(G, 0)
11
+ assert not nx.is_isolate(G, 1)
12
+ assert nx.is_isolate(G, 2)
13
+
14
+
15
+ def test_isolates():
16
+ G = nx.Graph()
17
+ G.add_edge(0, 1)
18
+ G.add_nodes_from([2, 3])
19
+ assert sorted(nx.isolates(G)) == [2, 3]
20
+
21
+
22
+ def test_number_of_isolates():
23
+ G = nx.Graph()
24
+ G.add_edge(0, 1)
25
+ G.add_nodes_from([2, 3])
26
+ assert nx.number_of_isolates(G) == 2
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_link_prediction.py ADDED
@@ -0,0 +1,615 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ from functools import partial
3
+
4
+ import pytest
5
+
6
+ import networkx as nx
7
+
8
+
9
+ def _test_func(G, ebunch, expected, predict_func, **kwargs):
10
+ result = predict_func(G, ebunch, **kwargs)
11
+ exp_dict = {tuple(sorted([u, v])): score for u, v, score in expected}
12
+ res_dict = {tuple(sorted([u, v])): score for u, v, score in result}
13
+
14
+ assert len(exp_dict) == len(res_dict)
15
+ for p in exp_dict:
16
+ assert exp_dict[p] == pytest.approx(res_dict[p], abs=1e-7)
17
+
18
+
19
+ class TestResourceAllocationIndex:
20
+ @classmethod
21
+ def setup_class(cls):
22
+ cls.func = staticmethod(nx.resource_allocation_index)
23
+ cls.test = staticmethod(partial(_test_func, predict_func=cls.func))
24
+
25
+ def test_K5(self):
26
+ G = nx.complete_graph(5)
27
+ self.test(G, [(0, 1)], [(0, 1, 0.75)])
28
+
29
+ def test_P3(self):
30
+ G = nx.path_graph(3)
31
+ self.test(G, [(0, 2)], [(0, 2, 0.5)])
32
+
33
+ def test_S4(self):
34
+ G = nx.star_graph(4)
35
+ self.test(G, [(1, 2)], [(1, 2, 0.25)])
36
+
37
+ @pytest.mark.parametrize("graph_type", (nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph))
38
+ def test_notimplemented(self, graph_type):
39
+ G = graph_type([(0, 1), (1, 2)])
40
+ with pytest.raises(nx.NetworkXNotImplemented):
41
+ self.func(G, [(0, 2)])
42
+
43
+ def test_node_not_found(self):
44
+ G = nx.Graph()
45
+ G.add_edges_from([(0, 1), (0, 2), (2, 3)])
46
+ with pytest.raises(nx.NodeNotFound):
47
+ self.func(G, [(0, 4)])
48
+
49
+ def test_no_common_neighbor(self):
50
+ G = nx.Graph()
51
+ G.add_nodes_from([0, 1])
52
+ self.test(G, [(0, 1)], [(0, 1, 0)])
53
+
54
+ def test_equal_nodes(self):
55
+ G = nx.complete_graph(4)
56
+ self.test(G, [(0, 0)], [(0, 0, 1)])
57
+
58
+ def test_all_nonexistent_edges(self):
59
+ G = nx.Graph()
60
+ G.add_edges_from([(0, 1), (0, 2), (2, 3)])
61
+ self.test(G, None, [(0, 3, 0.5), (1, 2, 0.5), (1, 3, 0)])
62
+
63
+
64
+ class TestJaccardCoefficient:
65
+ @classmethod
66
+ def setup_class(cls):
67
+ cls.func = staticmethod(nx.jaccard_coefficient)
68
+ cls.test = staticmethod(partial(_test_func, predict_func=cls.func))
69
+
70
+ def test_K5(self):
71
+ G = nx.complete_graph(5)
72
+ self.test(G, [(0, 1)], [(0, 1, 0.6)])
73
+
74
+ def test_P4(self):
75
+ G = nx.path_graph(4)
76
+ self.test(G, [(0, 2)], [(0, 2, 0.5)])
77
+
78
+ @pytest.mark.parametrize("graph_type", (nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph))
79
+ def test_notimplemented(self, graph_type):
80
+ G = graph_type([(0, 1), (1, 2)])
81
+ with pytest.raises(nx.NetworkXNotImplemented):
82
+ self.func(G, [(0, 2)])
83
+
84
+ def test_node_not_found(self):
85
+ G = nx.Graph()
86
+ G.add_edges_from([(0, 1), (0, 2), (2, 3)])
87
+ with pytest.raises(nx.NodeNotFound):
88
+ self.func(G, [(0, 4)])
89
+
90
+ def test_no_common_neighbor(self):
91
+ G = nx.Graph()
92
+ G.add_edges_from([(0, 1), (2, 3)])
93
+ self.test(G, [(0, 2)], [(0, 2, 0)])
94
+
95
+ def test_isolated_nodes(self):
96
+ G = nx.Graph()
97
+ G.add_nodes_from([0, 1])
98
+ self.test(G, [(0, 1)], [(0, 1, 0)])
99
+
100
+ def test_all_nonexistent_edges(self):
101
+ G = nx.Graph()
102
+ G.add_edges_from([(0, 1), (0, 2), (2, 3)])
103
+ self.test(G, None, [(0, 3, 0.5), (1, 2, 0.5), (1, 3, 0)])
104
+
105
+
106
+ class TestAdamicAdarIndex:
107
+ @classmethod
108
+ def setup_class(cls):
109
+ cls.func = staticmethod(nx.adamic_adar_index)
110
+ cls.test = staticmethod(partial(_test_func, predict_func=cls.func))
111
+
112
+ def test_K5(self):
113
+ G = nx.complete_graph(5)
114
+ self.test(G, [(0, 1)], [(0, 1, 3 / math.log(4))])
115
+
116
+ def test_P3(self):
117
+ G = nx.path_graph(3)
118
+ self.test(G, [(0, 2)], [(0, 2, 1 / math.log(2))])
119
+
120
+ def test_S4(self):
121
+ G = nx.star_graph(4)
122
+ self.test(G, [(1, 2)], [(1, 2, 1 / math.log(4))])
123
+
124
+ @pytest.mark.parametrize("graph_type", (nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph))
125
+ def test_notimplemented(self, graph_type):
126
+ with pytest.raises(nx.NetworkXNotImplemented):
127
+ G = graph_type([(0, 1), (1, 2)])
128
+ self.func(G, [(0, 2)])
129
+
130
+ def test_node_not_found(self):
131
+ G = nx.Graph()
132
+ G.add_edges_from([(0, 1), (0, 2), (2, 3)])
133
+ with pytest.raises(nx.NodeNotFound):
134
+ self.func(G, [(0, 4)])
135
+
136
+ def test_no_common_neighbor(self):
137
+ G = nx.Graph()
138
+ G.add_nodes_from([0, 1])
139
+ self.test(G, [(0, 1)], [(0, 1, 0)])
140
+
141
+ def test_equal_nodes(self):
142
+ G = nx.complete_graph(4)
143
+ self.test(G, [(0, 0)], [(0, 0, 3 / math.log(3))])
144
+
145
+ def test_all_nonexistent_edges(self):
146
+ G = nx.Graph()
147
+ G.add_edges_from([(0, 1), (0, 2), (2, 3)])
148
+ self.test(
149
+ G, None, [(0, 3, 1 / math.log(2)), (1, 2, 1 / math.log(2)), (1, 3, 0)]
150
+ )
151
+
152
+
153
+ class TestCommonNeighborCentrality:
154
+ @classmethod
155
+ def setup_class(cls):
156
+ cls.func = staticmethod(nx.common_neighbor_centrality)
157
+ cls.test = staticmethod(partial(_test_func, predict_func=cls.func))
158
+
159
+ def test_K5(self):
160
+ G = nx.complete_graph(5)
161
+ self.test(G, [(0, 1)], [(0, 1, 3.0)], alpha=1)
162
+ self.test(G, [(0, 1)], [(0, 1, 5.0)], alpha=0)
163
+
164
+ def test_P3(self):
165
+ G = nx.path_graph(3)
166
+ self.test(G, [(0, 2)], [(0, 2, 1.25)], alpha=0.5)
167
+
168
+ def test_S4(self):
169
+ G = nx.star_graph(4)
170
+ self.test(G, [(1, 2)], [(1, 2, 1.75)], alpha=0.5)
171
+
172
+ @pytest.mark.parametrize("graph_type", (nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph))
173
+ def test_notimplemented(self, graph_type):
174
+ G = graph_type([(0, 1), (1, 2)])
175
+ with pytest.raises(nx.NetworkXNotImplemented):
176
+ self.func(G, [(0, 2)])
177
+
178
+ def test_node_u_not_found(self):
179
+ G = nx.Graph()
180
+ G.add_edges_from([(1, 3), (2, 3)])
181
+ with pytest.raises(nx.NodeNotFound):
182
+ self.func(G, [(0, 1)])
183
+
184
+ def test_node_v_not_found(self):
185
+ G = nx.Graph()
186
+ G.add_edges_from([(0, 1), (0, 2), (2, 3)])
187
+ with pytest.raises(nx.NodeNotFound):
188
+ self.func(G, [(0, 4)])
189
+
190
+ def test_no_common_neighbor(self):
191
+ G = nx.Graph()
192
+ G.add_nodes_from([0, 1])
193
+ self.test(G, [(0, 1)], [(0, 1, 0)])
194
+
195
+ def test_equal_nodes(self):
196
+ G = nx.complete_graph(4)
197
+ with pytest.raises(nx.NetworkXAlgorithmError):
198
+ self.test(G, [(0, 0)], [])
199
+
200
+ def test_equal_nodes_with_alpha_one_raises_error(self):
201
+ G = nx.complete_graph(4)
202
+ with pytest.raises(nx.NetworkXAlgorithmError):
203
+ self.test(G, [(0, 0)], [], alpha=1.0)
204
+
205
+ def test_all_nonexistent_edges(self):
206
+ G = nx.Graph()
207
+ G.add_edges_from([(0, 1), (0, 2), (2, 3)])
208
+ self.test(G, None, [(0, 3, 1.5), (1, 2, 1.5), (1, 3, 2 / 3)], alpha=0.5)
209
+
210
+
211
+ class TestPreferentialAttachment:
212
+ @classmethod
213
+ def setup_class(cls):
214
+ cls.func = staticmethod(nx.preferential_attachment)
215
+ cls.test = staticmethod(partial(_test_func, predict_func=cls.func))
216
+
217
+ def test_K5(self):
218
+ G = nx.complete_graph(5)
219
+ self.test(G, [(0, 1)], [(0, 1, 16)])
220
+
221
+ def test_P3(self):
222
+ G = nx.path_graph(3)
223
+ self.test(G, [(0, 1)], [(0, 1, 2)])
224
+
225
+ def test_S4(self):
226
+ G = nx.star_graph(4)
227
+ self.test(G, [(0, 2)], [(0, 2, 4)])
228
+
229
+ @pytest.mark.parametrize("graph_type", (nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph))
230
+ def test_notimplemented(self, graph_type):
231
+ G = graph_type([(0, 1), (1, 2)])
232
+ with pytest.raises(nx.NetworkXNotImplemented):
233
+ self.func(G, [(0, 2)])
234
+
235
+ def test_node_not_found(self):
236
+ G = nx.Graph()
237
+ G.add_edges_from([(0, 1), (0, 2), (2, 3)])
238
+ with pytest.raises(nx.NodeNotFound):
239
+ self.func(G, [(0, 4)])
240
+
241
+ def test_zero_degrees(self):
242
+ G = nx.Graph()
243
+ G.add_nodes_from([0, 1])
244
+ self.test(G, [(0, 1)], [(0, 1, 0)])
245
+
246
+ def test_all_nonexistent_edges(self):
247
+ G = nx.Graph()
248
+ G.add_edges_from([(0, 1), (0, 2), (2, 3)])
249
+ self.test(G, None, [(0, 3, 2), (1, 2, 2), (1, 3, 1)])
250
+
251
+
252
+ class TestCNSoundarajanHopcroft:
253
+ @classmethod
254
+ def setup_class(cls):
255
+ cls.func = staticmethod(nx.cn_soundarajan_hopcroft)
256
+ cls.test = staticmethod(
257
+ partial(_test_func, predict_func=cls.func, community="community")
258
+ )
259
+
260
+ def test_K5(self):
261
+ G = nx.complete_graph(5)
262
+ G.nodes[0]["community"] = 0
263
+ G.nodes[1]["community"] = 0
264
+ G.nodes[2]["community"] = 0
265
+ G.nodes[3]["community"] = 0
266
+ G.nodes[4]["community"] = 1
267
+ self.test(G, [(0, 1)], [(0, 1, 5)])
268
+
269
+ def test_P3(self):
270
+ G = nx.path_graph(3)
271
+ G.nodes[0]["community"] = 0
272
+ G.nodes[1]["community"] = 1
273
+ G.nodes[2]["community"] = 0
274
+ self.test(G, [(0, 2)], [(0, 2, 1)])
275
+
276
+ def test_S4(self):
277
+ G = nx.star_graph(4)
278
+ G.nodes[0]["community"] = 1
279
+ G.nodes[1]["community"] = 1
280
+ G.nodes[2]["community"] = 1
281
+ G.nodes[3]["community"] = 0
282
+ G.nodes[4]["community"] = 0
283
+ self.test(G, [(1, 2)], [(1, 2, 2)])
284
+
285
+ @pytest.mark.parametrize("graph_type", (nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph))
286
+ def test_notimplemented(self, graph_type):
287
+ G = graph_type([(0, 1), (1, 2)])
288
+ G.add_nodes_from([0, 1, 2], community=0)
289
+ with pytest.raises(nx.NetworkXNotImplemented):
290
+ self.func(G, [(0, 2)])
291
+
292
+ def test_node_not_found(self):
293
+ G = nx.Graph()
294
+ G.add_edges_from([(0, 1), (0, 2), (2, 3)])
295
+ G.nodes[0]["community"] = 0
296
+ G.nodes[1]["community"] = 1
297
+ G.nodes[2]["community"] = 0
298
+ G.nodes[3]["community"] = 0
299
+ with pytest.raises(nx.NodeNotFound):
300
+ self.func(G, [(0, 4)])
301
+
302
+ def test_no_common_neighbor(self):
303
+ G = nx.Graph()
304
+ G.add_nodes_from([0, 1])
305
+ G.nodes[0]["community"] = 0
306
+ G.nodes[1]["community"] = 0
307
+ self.test(G, [(0, 1)], [(0, 1, 0)])
308
+
309
+ def test_equal_nodes(self):
310
+ G = nx.complete_graph(3)
311
+ G.nodes[0]["community"] = 0
312
+ G.nodes[1]["community"] = 0
313
+ G.nodes[2]["community"] = 0
314
+ self.test(G, [(0, 0)], [(0, 0, 4)])
315
+
316
+ def test_different_community(self):
317
+ G = nx.Graph()
318
+ G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
319
+ G.nodes[0]["community"] = 0
320
+ G.nodes[1]["community"] = 0
321
+ G.nodes[2]["community"] = 0
322
+ G.nodes[3]["community"] = 1
323
+ self.test(G, [(0, 3)], [(0, 3, 2)])
324
+
325
+ def test_no_community_information(self):
326
+ G = nx.complete_graph(5)
327
+ with pytest.raises(nx.NetworkXAlgorithmError):
328
+ list(self.func(G, [(0, 1)]))
329
+
330
+ def test_insufficient_community_information(self):
331
+ G = nx.Graph()
332
+ G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
333
+ G.nodes[0]["community"] = 0
334
+ G.nodes[1]["community"] = 0
335
+ G.nodes[3]["community"] = 0
336
+ with pytest.raises(nx.NetworkXAlgorithmError):
337
+ list(self.func(G, [(0, 3)]))
338
+
339
+ def test_sufficient_community_information(self):
340
+ G = nx.Graph()
341
+ G.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4), (4, 5)])
342
+ G.nodes[1]["community"] = 0
343
+ G.nodes[2]["community"] = 0
344
+ G.nodes[3]["community"] = 0
345
+ G.nodes[4]["community"] = 0
346
+ self.test(G, [(1, 4)], [(1, 4, 4)])
347
+
348
+ def test_custom_community_attribute_name(self):
349
+ G = nx.Graph()
350
+ G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
351
+ G.nodes[0]["cmty"] = 0
352
+ G.nodes[1]["cmty"] = 0
353
+ G.nodes[2]["cmty"] = 0
354
+ G.nodes[3]["cmty"] = 1
355
+ self.test(G, [(0, 3)], [(0, 3, 2)], community="cmty")
356
+
357
+ def test_all_nonexistent_edges(self):
358
+ G = nx.Graph()
359
+ G.add_edges_from([(0, 1), (0, 2), (2, 3)])
360
+ G.nodes[0]["community"] = 0
361
+ G.nodes[1]["community"] = 1
362
+ G.nodes[2]["community"] = 0
363
+ G.nodes[3]["community"] = 0
364
+ self.test(G, None, [(0, 3, 2), (1, 2, 1), (1, 3, 0)])
365
+
366
+
367
+ class TestRAIndexSoundarajanHopcroft:
368
+ @classmethod
369
+ def setup_class(cls):
370
+ cls.func = staticmethod(nx.ra_index_soundarajan_hopcroft)
371
+ cls.test = staticmethod(
372
+ partial(_test_func, predict_func=cls.func, community="community")
373
+ )
374
+
375
+ def test_K5(self):
376
+ G = nx.complete_graph(5)
377
+ G.nodes[0]["community"] = 0
378
+ G.nodes[1]["community"] = 0
379
+ G.nodes[2]["community"] = 0
380
+ G.nodes[3]["community"] = 0
381
+ G.nodes[4]["community"] = 1
382
+ self.test(G, [(0, 1)], [(0, 1, 0.5)])
383
+
384
+ def test_P3(self):
385
+ G = nx.path_graph(3)
386
+ G.nodes[0]["community"] = 0
387
+ G.nodes[1]["community"] = 1
388
+ G.nodes[2]["community"] = 0
389
+ self.test(G, [(0, 2)], [(0, 2, 0)])
390
+
391
+ def test_S4(self):
392
+ G = nx.star_graph(4)
393
+ G.nodes[0]["community"] = 1
394
+ G.nodes[1]["community"] = 1
395
+ G.nodes[2]["community"] = 1
396
+ G.nodes[3]["community"] = 0
397
+ G.nodes[4]["community"] = 0
398
+ self.test(G, [(1, 2)], [(1, 2, 0.25)])
399
+
400
+ @pytest.mark.parametrize("graph_type", (nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph))
401
+ def test_notimplemented(self, graph_type):
402
+ G = graph_type([(0, 1), (1, 2)])
403
+ G.add_nodes_from([0, 1, 2], community=0)
404
+ with pytest.raises(nx.NetworkXNotImplemented):
405
+ self.func(G, [(0, 2)])
406
+
407
+ def test_node_not_found(self):
408
+ G = nx.Graph()
409
+ G.add_edges_from([(0, 1), (0, 2), (2, 3)])
410
+ G.nodes[0]["community"] = 0
411
+ G.nodes[1]["community"] = 1
412
+ G.nodes[2]["community"] = 0
413
+ G.nodes[3]["community"] = 0
414
+ with pytest.raises(nx.NodeNotFound):
415
+ self.func(G, [(0, 4)])
416
+
417
+ def test_no_common_neighbor(self):
418
+ G = nx.Graph()
419
+ G.add_nodes_from([0, 1])
420
+ G.nodes[0]["community"] = 0
421
+ G.nodes[1]["community"] = 0
422
+ self.test(G, [(0, 1)], [(0, 1, 0)])
423
+
424
+ def test_equal_nodes(self):
425
+ G = nx.complete_graph(3)
426
+ G.nodes[0]["community"] = 0
427
+ G.nodes[1]["community"] = 0
428
+ G.nodes[2]["community"] = 0
429
+ self.test(G, [(0, 0)], [(0, 0, 1)])
430
+
431
+ def test_different_community(self):
432
+ G = nx.Graph()
433
+ G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
434
+ G.nodes[0]["community"] = 0
435
+ G.nodes[1]["community"] = 0
436
+ G.nodes[2]["community"] = 0
437
+ G.nodes[3]["community"] = 1
438
+ self.test(G, [(0, 3)], [(0, 3, 0)])
439
+
440
+ def test_no_community_information(self):
441
+ G = nx.complete_graph(5)
442
+ with pytest.raises(nx.NetworkXAlgorithmError):
443
+ list(self.func(G, [(0, 1)]))
444
+
445
+ def test_insufficient_community_information(self):
446
+ G = nx.Graph()
447
+ G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
448
+ G.nodes[0]["community"] = 0
449
+ G.nodes[1]["community"] = 0
450
+ G.nodes[3]["community"] = 0
451
+ with pytest.raises(nx.NetworkXAlgorithmError):
452
+ list(self.func(G, [(0, 3)]))
453
+
454
+ def test_sufficient_community_information(self):
455
+ G = nx.Graph()
456
+ G.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4), (4, 5)])
457
+ G.nodes[1]["community"] = 0
458
+ G.nodes[2]["community"] = 0
459
+ G.nodes[3]["community"] = 0
460
+ G.nodes[4]["community"] = 0
461
+ self.test(G, [(1, 4)], [(1, 4, 1)])
462
+
463
+ def test_custom_community_attribute_name(self):
464
+ G = nx.Graph()
465
+ G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
466
+ G.nodes[0]["cmty"] = 0
467
+ G.nodes[1]["cmty"] = 0
468
+ G.nodes[2]["cmty"] = 0
469
+ G.nodes[3]["cmty"] = 1
470
+ self.test(G, [(0, 3)], [(0, 3, 0)], community="cmty")
471
+
472
+ def test_all_nonexistent_edges(self):
473
+ G = nx.Graph()
474
+ G.add_edges_from([(0, 1), (0, 2), (2, 3)])
475
+ G.nodes[0]["community"] = 0
476
+ G.nodes[1]["community"] = 1
477
+ G.nodes[2]["community"] = 0
478
+ G.nodes[3]["community"] = 0
479
+ self.test(G, None, [(0, 3, 0.5), (1, 2, 0), (1, 3, 0)])
480
+
481
+
482
+ class TestWithinInterCluster:
483
+ @classmethod
484
+ def setup_class(cls):
485
+ cls.delta = 0.001
486
+ cls.func = staticmethod(nx.within_inter_cluster)
487
+ cls.test = staticmethod(
488
+ partial(
489
+ _test_func,
490
+ predict_func=cls.func,
491
+ delta=cls.delta,
492
+ community="community",
493
+ )
494
+ )
495
+
496
+ def test_K5(self):
497
+ G = nx.complete_graph(5)
498
+ G.nodes[0]["community"] = 0
499
+ G.nodes[1]["community"] = 0
500
+ G.nodes[2]["community"] = 0
501
+ G.nodes[3]["community"] = 0
502
+ G.nodes[4]["community"] = 1
503
+ self.test(G, [(0, 1)], [(0, 1, 2 / (1 + self.delta))])
504
+
505
+ def test_P3(self):
506
+ G = nx.path_graph(3)
507
+ G.nodes[0]["community"] = 0
508
+ G.nodes[1]["community"] = 1
509
+ G.nodes[2]["community"] = 0
510
+ self.test(G, [(0, 2)], [(0, 2, 0)])
511
+
512
+ def test_S4(self):
513
+ G = nx.star_graph(4)
514
+ G.nodes[0]["community"] = 1
515
+ G.nodes[1]["community"] = 1
516
+ G.nodes[2]["community"] = 1
517
+ G.nodes[3]["community"] = 0
518
+ G.nodes[4]["community"] = 0
519
+ self.test(G, [(1, 2)], [(1, 2, 1 / self.delta)])
520
+
521
+ @pytest.mark.parametrize("graph_type", (nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph))
522
+ def test_notimplemented(self, graph_type):
523
+ G = graph_type([(0, 1), (1, 2)])
524
+ G.add_nodes_from([0, 1, 2], community=0)
525
+ with pytest.raises(nx.NetworkXNotImplemented):
526
+ self.func(G, [(0, 2)])
527
+
528
+ def test_node_not_found(self):
529
+ G = nx.Graph()
530
+ G.add_edges_from([(0, 1), (0, 2), (2, 3)])
531
+ G.nodes[0]["community"] = 0
532
+ G.nodes[1]["community"] = 1
533
+ G.nodes[2]["community"] = 0
534
+ G.nodes[3]["community"] = 0
535
+ with pytest.raises(nx.NodeNotFound):
536
+ self.func(G, [(0, 4)])
537
+
538
+ def test_no_common_neighbor(self):
539
+ G = nx.Graph()
540
+ G.add_nodes_from([0, 1])
541
+ G.nodes[0]["community"] = 0
542
+ G.nodes[1]["community"] = 0
543
+ self.test(G, [(0, 1)], [(0, 1, 0)])
544
+
545
+ def test_equal_nodes(self):
546
+ G = nx.complete_graph(3)
547
+ G.nodes[0]["community"] = 0
548
+ G.nodes[1]["community"] = 0
549
+ G.nodes[2]["community"] = 0
550
+ self.test(G, [(0, 0)], [(0, 0, 2 / self.delta)])
551
+
552
+ def test_different_community(self):
553
+ G = nx.Graph()
554
+ G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
555
+ G.nodes[0]["community"] = 0
556
+ G.nodes[1]["community"] = 0
557
+ G.nodes[2]["community"] = 0
558
+ G.nodes[3]["community"] = 1
559
+ self.test(G, [(0, 3)], [(0, 3, 0)])
560
+
561
+ def test_no_inter_cluster_common_neighbor(self):
562
+ G = nx.complete_graph(4)
563
+ G.nodes[0]["community"] = 0
564
+ G.nodes[1]["community"] = 0
565
+ G.nodes[2]["community"] = 0
566
+ G.nodes[3]["community"] = 0
567
+ self.test(G, [(0, 3)], [(0, 3, 2 / self.delta)])
568
+
569
+ def test_no_community_information(self):
570
+ G = nx.complete_graph(5)
571
+ with pytest.raises(nx.NetworkXAlgorithmError):
572
+ list(self.func(G, [(0, 1)]))
573
+
574
+ def test_insufficient_community_information(self):
575
+ G = nx.Graph()
576
+ G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)])
577
+ G.nodes[0]["community"] = 0
578
+ G.nodes[1]["community"] = 0
579
+ G.nodes[3]["community"] = 0
580
+ with pytest.raises(nx.NetworkXAlgorithmError):
581
+ list(self.func(G, [(0, 3)]))
582
+
583
+ def test_sufficient_community_information(self):
584
+ G = nx.Graph()
585
+ G.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4), (4, 5)])
586
+ G.nodes[1]["community"] = 0
587
+ G.nodes[2]["community"] = 0
588
+ G.nodes[3]["community"] = 0
589
+ G.nodes[4]["community"] = 0
590
+ self.test(G, [(1, 4)], [(1, 4, 2 / self.delta)])
591
+
592
+ def test_invalid_delta(self):
593
+ G = nx.complete_graph(3)
594
+ G.add_nodes_from([0, 1, 2], community=0)
595
+ with pytest.raises(nx.NetworkXAlgorithmError):
596
+ self.func(G, [(0, 1)], 0)
597
+ with pytest.raises(nx.NetworkXAlgorithmError):
598
+ self.func(G, [(0, 1)], -0.5)
599
+
600
+ def test_custom_community_attribute_name(self):
601
+ G = nx.complete_graph(4)
602
+ G.nodes[0]["cmty"] = 0
603
+ G.nodes[1]["cmty"] = 0
604
+ G.nodes[2]["cmty"] = 0
605
+ G.nodes[3]["cmty"] = 0
606
+ self.test(G, [(0, 3)], [(0, 3, 2 / self.delta)], community="cmty")
607
+
608
+ def test_all_nonexistent_edges(self):
609
+ G = nx.Graph()
610
+ G.add_edges_from([(0, 1), (0, 2), (2, 3)])
611
+ G.nodes[0]["community"] = 0
612
+ G.nodes[1]["community"] = 1
613
+ G.nodes[2]["community"] = 0
614
+ G.nodes[3]["community"] = 0
615
+ self.test(G, None, [(0, 3, 1 / self.delta), (1, 2, 0), (1, 3, 0)])
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_lowest_common_ancestors.py ADDED
@@ -0,0 +1,459 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from itertools import chain, combinations, product
2
+
3
+ import pytest
4
+
5
+ import networkx as nx
6
+
7
+ tree_all_pairs_lca = nx.tree_all_pairs_lowest_common_ancestor
8
+ all_pairs_lca = nx.all_pairs_lowest_common_ancestor
9
+
10
+
11
+ def get_pair(dictionary, n1, n2):
12
+ if (n1, n2) in dictionary:
13
+ return dictionary[n1, n2]
14
+ else:
15
+ return dictionary[n2, n1]
16
+
17
+
18
+ class TestTreeLCA:
19
+ @classmethod
20
+ def setup_class(cls):
21
+ cls.DG = nx.DiGraph()
22
+ edges = [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]
23
+ cls.DG.add_edges_from(edges)
24
+ cls.ans = dict(tree_all_pairs_lca(cls.DG, 0))
25
+ gold = {(n, n): n for n in cls.DG}
26
+ gold.update({(0, i): 0 for i in range(1, 7)})
27
+ gold.update(
28
+ {
29
+ (1, 2): 0,
30
+ (1, 3): 1,
31
+ (1, 4): 1,
32
+ (1, 5): 0,
33
+ (1, 6): 0,
34
+ (2, 3): 0,
35
+ (2, 4): 0,
36
+ (2, 5): 2,
37
+ (2, 6): 2,
38
+ (3, 4): 1,
39
+ (3, 5): 0,
40
+ (3, 6): 0,
41
+ (4, 5): 0,
42
+ (4, 6): 0,
43
+ (5, 6): 2,
44
+ }
45
+ )
46
+
47
+ cls.gold = gold
48
+
49
+ @staticmethod
50
+ def assert_has_same_pairs(d1, d2):
51
+ for a, b in ((min(pair), max(pair)) for pair in chain(d1, d2)):
52
+ assert get_pair(d1, a, b) == get_pair(d2, a, b)
53
+
54
+ def test_tree_all_pairs_lca_default_root(self):
55
+ assert dict(tree_all_pairs_lca(self.DG)) == self.ans
56
+
57
+ def test_tree_all_pairs_lca_return_subset(self):
58
+ test_pairs = [(0, 1), (0, 1), (1, 0)]
59
+ ans = dict(tree_all_pairs_lca(self.DG, 0, test_pairs))
60
+ assert (0, 1) in ans and (1, 0) in ans
61
+ assert len(ans) == 2
62
+
63
+ def test_tree_all_pairs_lca(self):
64
+ all_pairs = chain(combinations(self.DG, 2), ((node, node) for node in self.DG))
65
+
66
+ ans = dict(tree_all_pairs_lca(self.DG, 0, all_pairs))
67
+ self.assert_has_same_pairs(ans, self.ans)
68
+
69
+ def test_tree_all_pairs_gold_example(self):
70
+ ans = dict(tree_all_pairs_lca(self.DG))
71
+ self.assert_has_same_pairs(self.gold, ans)
72
+
73
+ def test_tree_all_pairs_lca_invalid_input(self):
74
+ empty_digraph = tree_all_pairs_lca(nx.DiGraph())
75
+ pytest.raises(nx.NetworkXPointlessConcept, list, empty_digraph)
76
+
77
+ bad_pairs_digraph = tree_all_pairs_lca(self.DG, pairs=[(-1, -2)])
78
+ pytest.raises(nx.NodeNotFound, list, bad_pairs_digraph)
79
+
80
+ def test_tree_all_pairs_lca_subtrees(self):
81
+ ans = dict(tree_all_pairs_lca(self.DG, 1))
82
+ gold = {
83
+ pair: lca
84
+ for (pair, lca) in self.gold.items()
85
+ if all(n in (1, 3, 4) for n in pair)
86
+ }
87
+ self.assert_has_same_pairs(gold, ans)
88
+
89
+ def test_tree_all_pairs_lca_disconnected_nodes(self):
90
+ G = nx.DiGraph()
91
+ G.add_node(1)
92
+ assert {(1, 1): 1} == dict(tree_all_pairs_lca(G))
93
+
94
+ G.add_node(0)
95
+ assert {(1, 1): 1} == dict(tree_all_pairs_lca(G, 1))
96
+ assert {(0, 0): 0} == dict(tree_all_pairs_lca(G, 0))
97
+
98
+ pytest.raises(nx.NetworkXError, list, tree_all_pairs_lca(G))
99
+
100
+ def test_tree_all_pairs_lca_error_if_input_not_tree(self):
101
+ # Cycle
102
+ G = nx.DiGraph([(1, 2), (2, 1)])
103
+ pytest.raises(nx.NetworkXError, list, tree_all_pairs_lca(G))
104
+ # DAG
105
+ G = nx.DiGraph([(0, 2), (1, 2)])
106
+ pytest.raises(nx.NetworkXError, list, tree_all_pairs_lca(G))
107
+
108
+ def test_tree_all_pairs_lca_generator(self):
109
+ pairs = iter([(0, 1), (0, 1), (1, 0)])
110
+ some_pairs = dict(tree_all_pairs_lca(self.DG, 0, pairs))
111
+ assert (0, 1) in some_pairs and (1, 0) in some_pairs
112
+ assert len(some_pairs) == 2
113
+
114
+ def test_tree_all_pairs_lca_nonexisting_pairs_exception(self):
115
+ lca = tree_all_pairs_lca(self.DG, 0, [(-1, -1)])
116
+ pytest.raises(nx.NodeNotFound, list, lca)
117
+ # check if node is None
118
+ lca = tree_all_pairs_lca(self.DG, None, [(-1, -1)])
119
+ pytest.raises(nx.NodeNotFound, list, lca)
120
+
121
+ def test_tree_all_pairs_lca_routine_bails_on_DAGs(self):
122
+ G = nx.DiGraph([(3, 4), (5, 4)])
123
+ pytest.raises(nx.NetworkXError, list, tree_all_pairs_lca(G))
124
+
125
+ def test_tree_all_pairs_lca_not_implemented(self):
126
+ NNI = nx.NetworkXNotImplemented
127
+ G = nx.Graph([(0, 1)])
128
+ with pytest.raises(NNI):
129
+ next(tree_all_pairs_lca(G))
130
+ with pytest.raises(NNI):
131
+ next(all_pairs_lca(G))
132
+ pytest.raises(NNI, nx.lowest_common_ancestor, G, 0, 1)
133
+ G = nx.MultiGraph([(0, 1)])
134
+ with pytest.raises(NNI):
135
+ next(tree_all_pairs_lca(G))
136
+ with pytest.raises(NNI):
137
+ next(all_pairs_lca(G))
138
+ pytest.raises(NNI, nx.lowest_common_ancestor, G, 0, 1)
139
+
140
+ def test_tree_all_pairs_lca_trees_without_LCAs(self):
141
+ G = nx.DiGraph()
142
+ G.add_node(3)
143
+ ans = list(tree_all_pairs_lca(G))
144
+ assert ans == [((3, 3), 3)]
145
+
146
+
147
+ class TestMultiTreeLCA(TestTreeLCA):
148
+ @classmethod
149
+ def setup_class(cls):
150
+ cls.DG = nx.MultiDiGraph()
151
+ edges = [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]
152
+ cls.DG.add_edges_from(edges)
153
+ cls.ans = dict(tree_all_pairs_lca(cls.DG, 0))
154
+ # add multiedges
155
+ cls.DG.add_edges_from(edges)
156
+
157
+ gold = {(n, n): n for n in cls.DG}
158
+ gold.update({(0, i): 0 for i in range(1, 7)})
159
+ gold.update(
160
+ {
161
+ (1, 2): 0,
162
+ (1, 3): 1,
163
+ (1, 4): 1,
164
+ (1, 5): 0,
165
+ (1, 6): 0,
166
+ (2, 3): 0,
167
+ (2, 4): 0,
168
+ (2, 5): 2,
169
+ (2, 6): 2,
170
+ (3, 4): 1,
171
+ (3, 5): 0,
172
+ (3, 6): 0,
173
+ (4, 5): 0,
174
+ (4, 6): 0,
175
+ (5, 6): 2,
176
+ }
177
+ )
178
+
179
+ cls.gold = gold
180
+
181
+
182
+ class TestDAGLCA:
183
+ @classmethod
184
+ def setup_class(cls):
185
+ cls.DG = nx.DiGraph()
186
+ nx.add_path(cls.DG, (0, 1, 2, 3))
187
+ nx.add_path(cls.DG, (0, 4, 3))
188
+ nx.add_path(cls.DG, (0, 5, 6, 8, 3))
189
+ nx.add_path(cls.DG, (5, 7, 8))
190
+ cls.DG.add_edge(6, 2)
191
+ cls.DG.add_edge(7, 2)
192
+
193
+ cls.root_distance = nx.shortest_path_length(cls.DG, source=0)
194
+
195
+ cls.gold = {
196
+ (1, 1): 1,
197
+ (1, 2): 1,
198
+ (1, 3): 1,
199
+ (1, 4): 0,
200
+ (1, 5): 0,
201
+ (1, 6): 0,
202
+ (1, 7): 0,
203
+ (1, 8): 0,
204
+ (2, 2): 2,
205
+ (2, 3): 2,
206
+ (2, 4): 0,
207
+ (2, 5): 5,
208
+ (2, 6): 6,
209
+ (2, 7): 7,
210
+ (2, 8): 7,
211
+ (3, 3): 3,
212
+ (3, 4): 4,
213
+ (3, 5): 5,
214
+ (3, 6): 6,
215
+ (3, 7): 7,
216
+ (3, 8): 8,
217
+ (4, 4): 4,
218
+ (4, 5): 0,
219
+ (4, 6): 0,
220
+ (4, 7): 0,
221
+ (4, 8): 0,
222
+ (5, 5): 5,
223
+ (5, 6): 5,
224
+ (5, 7): 5,
225
+ (5, 8): 5,
226
+ (6, 6): 6,
227
+ (6, 7): 5,
228
+ (6, 8): 6,
229
+ (7, 7): 7,
230
+ (7, 8): 7,
231
+ (8, 8): 8,
232
+ }
233
+ cls.gold.update(((0, n), 0) for n in cls.DG)
234
+
235
+ def assert_lca_dicts_same(self, d1, d2, G=None):
236
+ """Checks if d1 and d2 contain the same pairs and
237
+ have a node at the same distance from root for each.
238
+ If G is None use self.DG."""
239
+ if G is None:
240
+ G = self.DG
241
+ root_distance = self.root_distance
242
+ else:
243
+ roots = [n for n, deg in G.in_degree if deg == 0]
244
+ assert len(roots) == 1
245
+ root_distance = nx.shortest_path_length(G, source=roots[0])
246
+
247
+ for a, b in ((min(pair), max(pair)) for pair in chain(d1, d2)):
248
+ assert (
249
+ root_distance[get_pair(d1, a, b)] == root_distance[get_pair(d2, a, b)]
250
+ )
251
+
252
+ def test_all_pairs_lca_gold_example(self):
253
+ self.assert_lca_dicts_same(dict(all_pairs_lca(self.DG)), self.gold)
254
+
255
+ def test_all_pairs_lca_all_pairs_given(self):
256
+ all_pairs = list(product(self.DG.nodes(), self.DG.nodes()))
257
+ ans = all_pairs_lca(self.DG, pairs=all_pairs)
258
+ self.assert_lca_dicts_same(dict(ans), self.gold)
259
+
260
+ def test_all_pairs_lca_generator(self):
261
+ all_pairs = product(self.DG.nodes(), self.DG.nodes())
262
+ ans = all_pairs_lca(self.DG, pairs=all_pairs)
263
+ self.assert_lca_dicts_same(dict(ans), self.gold)
264
+
265
+ def test_all_pairs_lca_input_graph_with_two_roots(self):
266
+ G = self.DG.copy()
267
+ G.add_edge(9, 10)
268
+ G.add_edge(9, 4)
269
+ gold = self.gold.copy()
270
+ gold[9, 9] = 9
271
+ gold[9, 10] = 9
272
+ gold[9, 4] = 9
273
+ gold[9, 3] = 9
274
+ gold[10, 4] = 9
275
+ gold[10, 3] = 9
276
+ gold[10, 10] = 10
277
+
278
+ testing = dict(all_pairs_lca(G))
279
+
280
+ G.add_edge(-1, 9)
281
+ G.add_edge(-1, 0)
282
+ self.assert_lca_dicts_same(testing, gold, G)
283
+
284
+ def test_all_pairs_lca_nonexisting_pairs_exception(self):
285
+ pytest.raises(nx.NodeNotFound, all_pairs_lca, self.DG, [(-1, -1)])
286
+
287
+ def test_all_pairs_lca_pairs_without_lca(self):
288
+ G = self.DG.copy()
289
+ G.add_node(-1)
290
+ gen = all_pairs_lca(G, [(-1, -1), (-1, 0)])
291
+ assert dict(gen) == {(-1, -1): -1}
292
+
293
+ def test_all_pairs_lca_null_graph(self):
294
+ pytest.raises(nx.NetworkXPointlessConcept, all_pairs_lca, nx.DiGraph())
295
+
296
+ def test_all_pairs_lca_non_dags(self):
297
+ pytest.raises(nx.NetworkXError, all_pairs_lca, nx.DiGraph([(3, 4), (4, 3)]))
298
+
299
+ def test_all_pairs_lca_nonempty_graph_without_lca(self):
300
+ G = nx.DiGraph()
301
+ G.add_node(3)
302
+ ans = list(all_pairs_lca(G))
303
+ assert ans == [((3, 3), 3)]
304
+
305
+ def test_all_pairs_lca_bug_gh4942(self):
306
+ G = nx.DiGraph([(0, 2), (1, 2), (2, 3)])
307
+ ans = list(all_pairs_lca(G))
308
+ assert len(ans) == 9
309
+
310
+ def test_all_pairs_lca_default_kwarg(self):
311
+ G = nx.DiGraph([(0, 1), (2, 1)])
312
+ sentinel = object()
313
+ assert nx.lowest_common_ancestor(G, 0, 2, default=sentinel) is sentinel
314
+
315
+ def test_all_pairs_lca_identity(self):
316
+ G = nx.DiGraph()
317
+ G.add_node(3)
318
+ assert nx.lowest_common_ancestor(G, 3, 3) == 3
319
+
320
+ def test_all_pairs_lca_issue_4574(self):
321
+ G = nx.DiGraph()
322
+ G.add_nodes_from(range(17))
323
+ G.add_edges_from(
324
+ [
325
+ (2, 0),
326
+ (1, 2),
327
+ (3, 2),
328
+ (5, 2),
329
+ (8, 2),
330
+ (11, 2),
331
+ (4, 5),
332
+ (6, 5),
333
+ (7, 8),
334
+ (10, 8),
335
+ (13, 11),
336
+ (14, 11),
337
+ (15, 11),
338
+ (9, 10),
339
+ (12, 13),
340
+ (16, 15),
341
+ ]
342
+ )
343
+
344
+ assert nx.lowest_common_ancestor(G, 7, 9) is None
345
+
346
+ def test_all_pairs_lca_one_pair_gh4942(self):
347
+ G = nx.DiGraph()
348
+ # Note: order edge addition is critical to the test
349
+ G.add_edge(0, 1)
350
+ G.add_edge(2, 0)
351
+ G.add_edge(2, 3)
352
+ G.add_edge(4, 0)
353
+ G.add_edge(5, 2)
354
+
355
+ assert nx.lowest_common_ancestor(G, 1, 3) == 2
356
+
357
+
358
+ class TestMultiDiGraph_DAGLCA(TestDAGLCA):
359
+ @classmethod
360
+ def setup_class(cls):
361
+ cls.DG = nx.MultiDiGraph()
362
+ nx.add_path(cls.DG, (0, 1, 2, 3))
363
+ # add multiedges
364
+ nx.add_path(cls.DG, (0, 1, 2, 3))
365
+ nx.add_path(cls.DG, (0, 4, 3))
366
+ nx.add_path(cls.DG, (0, 5, 6, 8, 3))
367
+ nx.add_path(cls.DG, (5, 7, 8))
368
+ cls.DG.add_edge(6, 2)
369
+ cls.DG.add_edge(7, 2)
370
+
371
+ cls.root_distance = nx.shortest_path_length(cls.DG, source=0)
372
+
373
+ cls.gold = {
374
+ (1, 1): 1,
375
+ (1, 2): 1,
376
+ (1, 3): 1,
377
+ (1, 4): 0,
378
+ (1, 5): 0,
379
+ (1, 6): 0,
380
+ (1, 7): 0,
381
+ (1, 8): 0,
382
+ (2, 2): 2,
383
+ (2, 3): 2,
384
+ (2, 4): 0,
385
+ (2, 5): 5,
386
+ (2, 6): 6,
387
+ (2, 7): 7,
388
+ (2, 8): 7,
389
+ (3, 3): 3,
390
+ (3, 4): 4,
391
+ (3, 5): 5,
392
+ (3, 6): 6,
393
+ (3, 7): 7,
394
+ (3, 8): 8,
395
+ (4, 4): 4,
396
+ (4, 5): 0,
397
+ (4, 6): 0,
398
+ (4, 7): 0,
399
+ (4, 8): 0,
400
+ (5, 5): 5,
401
+ (5, 6): 5,
402
+ (5, 7): 5,
403
+ (5, 8): 5,
404
+ (6, 6): 6,
405
+ (6, 7): 5,
406
+ (6, 8): 6,
407
+ (7, 7): 7,
408
+ (7, 8): 7,
409
+ (8, 8): 8,
410
+ }
411
+ cls.gold.update(((0, n), 0) for n in cls.DG)
412
+
413
+
414
+ def test_all_pairs_lca_self_ancestors():
415
+ """Self-ancestors should always be the node itself, i.e. lca of (0, 0) is 0.
416
+ See gh-4458."""
417
+ # DAG for test - note order of node/edge addition is relevant
418
+ G = nx.DiGraph()
419
+ G.add_nodes_from(range(5))
420
+ G.add_edges_from([(1, 0), (2, 0), (3, 2), (4, 1), (4, 3)])
421
+
422
+ ap_lca = nx.all_pairs_lowest_common_ancestor
423
+ assert all(u == v == a for (u, v), a in ap_lca(G) if u == v)
424
+ MG = nx.MultiDiGraph(G)
425
+ assert all(u == v == a for (u, v), a in ap_lca(MG) if u == v)
426
+ MG.add_edges_from([(1, 0), (2, 0)])
427
+ assert all(u == v == a for (u, v), a in ap_lca(MG) if u == v)
428
+
429
+
430
+ def test_lca_on_null_graph():
431
+ G = nx.null_graph(create_using=nx.DiGraph)
432
+ with pytest.raises(
433
+ nx.NetworkXPointlessConcept, match="LCA meaningless on null graphs"
434
+ ):
435
+ nx.lowest_common_ancestor(G, 0, 0)
436
+
437
+
438
+ def test_lca_on_cycle_graph():
439
+ G = nx.cycle_graph(6, create_using=nx.DiGraph)
440
+ with pytest.raises(
441
+ nx.NetworkXError, match="LCA only defined on directed acyclic graphs"
442
+ ):
443
+ nx.lowest_common_ancestor(G, 0, 3)
444
+
445
+
446
+ def test_lca_multiple_valid_solutions():
447
+ G = nx.DiGraph()
448
+ G.add_nodes_from(range(4))
449
+ G.add_edges_from([(2, 0), (3, 0), (2, 1), (3, 1)])
450
+ assert nx.lowest_common_ancestor(G, 0, 1) in {2, 3}
451
+
452
+
453
+ def test_lca_dont_rely_on_single_successor():
454
+ # Nodes 0 and 1 have nodes 2 and 3 as immediate ancestors,
455
+ # and node 2 also has node 3 as an immediate ancestor.
456
+ G = nx.DiGraph()
457
+ G.add_nodes_from(range(4))
458
+ G.add_edges_from([(2, 0), (2, 1), (3, 1), (3, 0), (3, 2)])
459
+ assert nx.lowest_common_ancestor(G, 0, 1) == 2
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_matching.py ADDED
@@ -0,0 +1,556 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ from itertools import permutations
3
+
4
+ import pytest
5
+
6
+ import networkx as nx
7
+ from networkx.utils import edges_equal
8
+
9
+
10
+ @pytest.mark.parametrize(
11
+ "fn", (nx.is_matching, nx.is_maximal_matching, nx.is_perfect_matching)
12
+ )
13
+ @pytest.mark.parametrize(
14
+ "edgeset",
15
+ (
16
+ {(0, 5)}, # Single edge, node not in G
17
+ {(5, 0)}, # for both edge orders
18
+ {(0, 5), (2, 3)}, # node not in G, but other edge is valid matching
19
+ {(5, 5), (2, 3)}, # Self-loop hits node not in G validation first
20
+ ),
21
+ )
22
+ def test_is_matching_node_not_in_G(fn, edgeset):
23
+ """All is_*matching functions have consistent exception message for node
24
+ not in G."""
25
+ G = nx.path_graph(4)
26
+ with pytest.raises(nx.NetworkXError, match="matching.*with node not in G"):
27
+ fn(G, edgeset)
28
+
29
+
30
+ @pytest.mark.parametrize(
31
+ "fn", (nx.is_matching, nx.is_maximal_matching, nx.is_perfect_matching)
32
+ )
33
+ @pytest.mark.parametrize(
34
+ "edgeset",
35
+ (
36
+ {(0, 1, 2), (2, 3)}, # 3-tuple
37
+ {(0,), (2, 3)}, # 1-tuple
38
+ ),
39
+ )
40
+ def test_is_matching_invalid_edge(fn, edgeset):
41
+ """All is_*matching functions have consistent exception message for invalid
42
+ edges in matching."""
43
+ G = nx.path_graph(4)
44
+ with pytest.raises(nx.NetworkXError, match=".*non-2-tuple edge.*"):
45
+ fn(G, edgeset)
46
+
47
+
48
+ @pytest.mark.parametrize("graph_type", (nx.MultiGraph, nx.DiGraph, nx.MultiDiGraph))
49
+ @pytest.mark.parametrize(
50
+ "fn", (nx.max_weight_matching, nx.min_weight_matching, nx.maximal_matching)
51
+ )
52
+ def test_wrong_graph_type(fn, graph_type):
53
+ G = graph_type()
54
+ with pytest.raises(nx.NetworkXNotImplemented):
55
+ fn(G)
56
+
57
+
58
+ class TestMaxWeightMatching:
59
+ """Unit tests for the
60
+ :func:`~networkx.algorithms.matching.max_weight_matching` function.
61
+
62
+ """
63
+
64
+ def test_trivial1(self):
65
+ """Empty graph"""
66
+ G = nx.Graph()
67
+ assert nx.max_weight_matching(G) == set()
68
+ assert nx.min_weight_matching(G) == set()
69
+
70
+ def test_selfloop(self):
71
+ G = nx.Graph()
72
+ G.add_edge(0, 0, weight=100)
73
+ assert nx.max_weight_matching(G) == set()
74
+ assert nx.min_weight_matching(G) == set()
75
+
76
+ def test_single_edge(self):
77
+ G = nx.Graph()
78
+ G.add_edge(0, 1)
79
+ assert edges_equal(nx.max_weight_matching(G), {(0, 1)})
80
+ assert edges_equal(nx.min_weight_matching(G), {(0, 1)})
81
+
82
+ def test_two_path(self):
83
+ G = nx.Graph()
84
+ G.add_edge("one", "two", weight=10)
85
+ G.add_edge("two", "three", weight=11)
86
+ assert edges_equal(nx.max_weight_matching(G), {("two", "three")})
87
+ assert edges_equal(nx.min_weight_matching(G), {("one", "two")})
88
+
89
+ def test_path(self):
90
+ G = nx.Graph()
91
+ G.add_edge(1, 2, weight=5)
92
+ G.add_edge(2, 3, weight=11)
93
+ G.add_edge(3, 4, weight=5)
94
+ assert edges_equal(nx.max_weight_matching(G), {(2, 3)})
95
+ assert edges_equal(nx.max_weight_matching(G, weight=None), {(1, 2), (3, 4)})
96
+ assert edges_equal(nx.min_weight_matching(G), {(1, 2), (3, 4)})
97
+ assert edges_equal(nx.min_weight_matching(G, weight=None), {(1, 2), (3, 4)})
98
+
99
+ def test_square(self):
100
+ G = nx.Graph()
101
+ G.add_edge(1, 4, weight=2)
102
+ G.add_edge(2, 3, weight=2)
103
+ G.add_edge(1, 2, weight=1)
104
+ G.add_edge(3, 4, weight=4)
105
+ assert edges_equal(nx.max_weight_matching(G), {(1, 2), (3, 4)})
106
+ assert edges_equal(nx.min_weight_matching(G), {(1, 4), (2, 3)})
107
+
108
+ def test_edge_attribute_name(self):
109
+ G = nx.Graph()
110
+ G.add_edge("one", "two", weight=10, abcd=11)
111
+ G.add_edge("two", "three", weight=11, abcd=10)
112
+ assert edges_equal(nx.max_weight_matching(G, weight="abcd"), {("one", "two")})
113
+ assert edges_equal(nx.min_weight_matching(G, weight="abcd"), {("two", "three")})
114
+
115
+ def test_floating_point_weights(self):
116
+ G = nx.Graph()
117
+ G.add_edge(1, 2, weight=math.pi)
118
+ G.add_edge(2, 3, weight=math.exp(1))
119
+ G.add_edge(1, 3, weight=3.0)
120
+ G.add_edge(1, 4, weight=math.sqrt(2.0))
121
+ assert edges_equal(nx.max_weight_matching(G), {(1, 4), (2, 3)})
122
+ assert edges_equal(nx.min_weight_matching(G), {(1, 4), (2, 3)})
123
+
124
+ def test_negative_weights(self):
125
+ G = nx.Graph()
126
+ G.add_edge(1, 2, weight=2)
127
+ G.add_edge(1, 3, weight=-2)
128
+ G.add_edge(2, 3, weight=1)
129
+ G.add_edge(2, 4, weight=-1)
130
+ G.add_edge(3, 4, weight=-6)
131
+ assert edges_equal(nx.max_weight_matching(G), {(1, 2)})
132
+ assert edges_equal(
133
+ nx.max_weight_matching(G, maxcardinality=True), {(1, 3), (2, 4)}
134
+ )
135
+ assert edges_equal(nx.min_weight_matching(G), {(1, 2), (3, 4)})
136
+
137
+ def test_s_blossom(self):
138
+ """Create S-blossom and use it for augmentation:"""
139
+ G = nx.Graph()
140
+ G.add_weighted_edges_from([(1, 2, 8), (1, 3, 9), (2, 3, 10), (3, 4, 7)])
141
+ answer = {(1, 2), (3, 4)}
142
+ assert edges_equal(nx.max_weight_matching(G), answer)
143
+ assert edges_equal(nx.min_weight_matching(G), answer)
144
+
145
+ G.add_weighted_edges_from([(1, 6, 5), (4, 5, 6)])
146
+ answer = {(1, 6), (2, 3), (4, 5)}
147
+ assert edges_equal(nx.max_weight_matching(G), answer)
148
+ assert edges_equal(nx.min_weight_matching(G), answer)
149
+
150
+ def test_s_t_blossom(self):
151
+ """Create S-blossom, relabel as T-blossom, use for augmentation:"""
152
+ G = nx.Graph()
153
+ G.add_weighted_edges_from(
154
+ [(1, 2, 9), (1, 3, 8), (2, 3, 10), (1, 4, 5), (4, 5, 4), (1, 6, 3)]
155
+ )
156
+ answer = {(1, 6), (2, 3), (4, 5)}
157
+ assert edges_equal(nx.max_weight_matching(G), answer)
158
+ assert edges_equal(nx.min_weight_matching(G), answer)
159
+
160
+ G.add_edge(4, 5, weight=3)
161
+ G.add_edge(1, 6, weight=4)
162
+ assert edges_equal(nx.max_weight_matching(G), answer)
163
+ assert edges_equal(nx.min_weight_matching(G), answer)
164
+
165
+ G.remove_edge(1, 6)
166
+ G.add_edge(3, 6, weight=4)
167
+ answer = {(1, 2), (3, 6), (4, 5)}
168
+ assert edges_equal(nx.max_weight_matching(G), answer)
169
+ assert edges_equal(nx.min_weight_matching(G), answer)
170
+
171
+ def test_nested_s_blossom(self):
172
+ """Create nested S-blossom, use for augmentation:"""
173
+
174
+ G = nx.Graph()
175
+ G.add_weighted_edges_from(
176
+ [
177
+ (1, 2, 9),
178
+ (1, 3, 9),
179
+ (2, 3, 10),
180
+ (2, 4, 8),
181
+ (3, 5, 8),
182
+ (4, 5, 10),
183
+ (5, 6, 6),
184
+ ]
185
+ )
186
+ expected_edgeset = {(1, 3), (2, 4), (5, 6)}
187
+ expected = {frozenset(e) for e in expected_edgeset}
188
+ answer = {frozenset(e) for e in nx.max_weight_matching(G)}
189
+ assert answer == expected
190
+ answer = {frozenset(e) for e in nx.min_weight_matching(G)}
191
+ assert answer == expected
192
+
193
+ def test_nested_s_blossom_relabel(self):
194
+ """Create S-blossom, relabel as S, include in nested S-blossom:"""
195
+ G = nx.Graph()
196
+ G.add_weighted_edges_from(
197
+ [
198
+ (1, 2, 10),
199
+ (1, 7, 10),
200
+ (2, 3, 12),
201
+ (3, 4, 20),
202
+ (3, 5, 20),
203
+ (4, 5, 25),
204
+ (5, 6, 10),
205
+ (6, 7, 10),
206
+ (7, 8, 8),
207
+ ]
208
+ )
209
+ answer = {(1, 2), (3, 4), (5, 6), (7, 8)}
210
+ assert edges_equal(nx.max_weight_matching(G), answer)
211
+ assert edges_equal(nx.min_weight_matching(G), answer)
212
+
213
+ def test_nested_s_blossom_expand(self):
214
+ """Create nested S-blossom, augment, expand recursively:"""
215
+ G = nx.Graph()
216
+ G.add_weighted_edges_from(
217
+ [
218
+ (1, 2, 8),
219
+ (1, 3, 8),
220
+ (2, 3, 10),
221
+ (2, 4, 12),
222
+ (3, 5, 12),
223
+ (4, 5, 14),
224
+ (4, 6, 12),
225
+ (5, 7, 12),
226
+ (6, 7, 14),
227
+ (7, 8, 12),
228
+ ]
229
+ )
230
+ answer = {(1, 2), (3, 5), (4, 6), (7, 8)}
231
+ assert edges_equal(nx.max_weight_matching(G), answer)
232
+ assert edges_equal(nx.min_weight_matching(G), answer)
233
+
234
+ def test_s_blossom_relabel_expand(self):
235
+ """Create S-blossom, relabel as T, expand:"""
236
+ G = nx.Graph()
237
+ G.add_weighted_edges_from(
238
+ [
239
+ (1, 2, 23),
240
+ (1, 5, 22),
241
+ (1, 6, 15),
242
+ (2, 3, 25),
243
+ (3, 4, 22),
244
+ (4, 5, 25),
245
+ (4, 8, 14),
246
+ (5, 7, 13),
247
+ ]
248
+ )
249
+ answer = {(1, 6), (2, 3), (4, 8), (5, 7)}
250
+ assert edges_equal(nx.max_weight_matching(G), answer)
251
+ assert edges_equal(nx.min_weight_matching(G), answer)
252
+
253
+ def test_nested_s_blossom_relabel_expand(self):
254
+ """Create nested S-blossom, relabel as T, expand:"""
255
+ G = nx.Graph()
256
+ G.add_weighted_edges_from(
257
+ [
258
+ (1, 2, 19),
259
+ (1, 3, 20),
260
+ (1, 8, 8),
261
+ (2, 3, 25),
262
+ (2, 4, 18),
263
+ (3, 5, 18),
264
+ (4, 5, 13),
265
+ (4, 7, 7),
266
+ (5, 6, 7),
267
+ ]
268
+ )
269
+ answer = {(1, 8), (2, 3), (4, 7), (5, 6)}
270
+ assert edges_equal(nx.max_weight_matching(G), answer)
271
+ assert edges_equal(nx.min_weight_matching(G), answer)
272
+
273
+ def test_nasty_blossom1(self):
274
+ """Create blossom, relabel as T in more than one way, expand,
275
+ augment:
276
+ """
277
+ G = nx.Graph()
278
+ G.add_weighted_edges_from(
279
+ [
280
+ (1, 2, 45),
281
+ (1, 5, 45),
282
+ (2, 3, 50),
283
+ (3, 4, 45),
284
+ (4, 5, 50),
285
+ (1, 6, 30),
286
+ (3, 9, 35),
287
+ (4, 8, 35),
288
+ (5, 7, 26),
289
+ (9, 10, 5),
290
+ ]
291
+ )
292
+ answer = {(1, 6), (2, 3), (4, 8), (5, 7), (9, 10)}
293
+ assert edges_equal(nx.max_weight_matching(G), answer)
294
+ assert edges_equal(nx.min_weight_matching(G), answer)
295
+
296
+ def test_nasty_blossom2(self):
297
+ """Again but slightly different:"""
298
+ G = nx.Graph()
299
+ G.add_weighted_edges_from(
300
+ [
301
+ (1, 2, 45),
302
+ (1, 5, 45),
303
+ (2, 3, 50),
304
+ (3, 4, 45),
305
+ (4, 5, 50),
306
+ (1, 6, 30),
307
+ (3, 9, 35),
308
+ (4, 8, 26),
309
+ (5, 7, 40),
310
+ (9, 10, 5),
311
+ ]
312
+ )
313
+ answer = {(1, 6), (2, 3), (4, 8), (5, 7), (9, 10)}
314
+ assert edges_equal(nx.max_weight_matching(G), answer)
315
+ assert edges_equal(nx.min_weight_matching(G), answer)
316
+
317
+ def test_nasty_blossom_least_slack(self):
318
+ """Create blossom, relabel as T, expand such that a new
319
+ least-slack S-to-free dge is produced, augment:
320
+ """
321
+ G = nx.Graph()
322
+ G.add_weighted_edges_from(
323
+ [
324
+ (1, 2, 45),
325
+ (1, 5, 45),
326
+ (2, 3, 50),
327
+ (3, 4, 45),
328
+ (4, 5, 50),
329
+ (1, 6, 30),
330
+ (3, 9, 35),
331
+ (4, 8, 28),
332
+ (5, 7, 26),
333
+ (9, 10, 5),
334
+ ]
335
+ )
336
+ answer = {(1, 6), (2, 3), (4, 8), (5, 7), (9, 10)}
337
+ assert edges_equal(nx.max_weight_matching(G), answer)
338
+ assert edges_equal(nx.min_weight_matching(G), answer)
339
+
340
+ def test_nasty_blossom_augmenting(self):
341
+ """Create nested blossom, relabel as T in more than one way"""
342
+ # expand outer blossom such that inner blossom ends up on an
343
+ # augmenting path:
344
+ G = nx.Graph()
345
+ G.add_weighted_edges_from(
346
+ [
347
+ (1, 2, 45),
348
+ (1, 7, 45),
349
+ (2, 3, 50),
350
+ (3, 4, 45),
351
+ (4, 5, 95),
352
+ (4, 6, 94),
353
+ (5, 6, 94),
354
+ (6, 7, 50),
355
+ (1, 8, 30),
356
+ (3, 11, 35),
357
+ (5, 9, 36),
358
+ (7, 10, 26),
359
+ (11, 12, 5),
360
+ ]
361
+ )
362
+ answer = {(1, 8), (2, 3), (4, 6), (5, 9), (7, 10), (11, 12)}
363
+ assert edges_equal(nx.max_weight_matching(G), answer)
364
+ assert edges_equal(nx.min_weight_matching(G), answer)
365
+
366
+ def test_nasty_blossom_expand_recursively(self):
367
+ """Create nested S-blossom, relabel as S, expand recursively:"""
368
+ G = nx.Graph()
369
+ G.add_weighted_edges_from(
370
+ [
371
+ (1, 2, 40),
372
+ (1, 3, 40),
373
+ (2, 3, 60),
374
+ (2, 4, 55),
375
+ (3, 5, 55),
376
+ (4, 5, 50),
377
+ (1, 8, 15),
378
+ (5, 7, 30),
379
+ (7, 6, 10),
380
+ (8, 10, 10),
381
+ (4, 9, 30),
382
+ ]
383
+ )
384
+ answer = {(1, 2), (3, 5), (4, 9), (6, 7), (8, 10)}
385
+ assert edges_equal(nx.max_weight_matching(G), answer)
386
+ assert edges_equal(nx.min_weight_matching(G), answer)
387
+
388
+ def test_min_weight_matching_max_cardinality(self):
389
+ G = nx.Graph()
390
+ G.add_weighted_edges_from([(1, 2, 1000), (2, 3, 2), (3, 4, 3000)])
391
+ # The minimum-weight maximal matching is {(2, 3)}; the minimum-weight
392
+ # maximum-cardinality matching is {(1, 2), (3, 4)}. See gh-8062.
393
+ answer = {(1, 2), (3, 4)}
394
+ assert edges_equal(nx.min_weight_matching(G), answer)
395
+
396
+
397
+ class TestIsMatching:
398
+ """Unit tests for the
399
+ :func:`~networkx.algorithms.matching.is_matching` function.
400
+
401
+ """
402
+
403
+ def test_dict(self):
404
+ G = nx.path_graph(4)
405
+ assert nx.is_matching(G, {0: 1, 1: 0, 2: 3, 3: 2})
406
+
407
+ def test_empty_matching(self):
408
+ G = nx.path_graph(4)
409
+ assert nx.is_matching(G, set())
410
+
411
+ def test_single_edge(self):
412
+ G = nx.path_graph(4)
413
+ assert nx.is_matching(G, {(1, 2)})
414
+
415
+ def test_edge_order(self):
416
+ G = nx.path_graph(4)
417
+ assert nx.is_matching(G, {(0, 1), (2, 3)})
418
+ assert nx.is_matching(G, {(1, 0), (2, 3)})
419
+ assert nx.is_matching(G, {(0, 1), (3, 2)})
420
+ assert nx.is_matching(G, {(1, 0), (3, 2)})
421
+
422
+ def test_valid_matching(self):
423
+ G = nx.path_graph(4)
424
+ assert nx.is_matching(G, {(0, 1), (2, 3)})
425
+
426
+ def test_selfloops(self):
427
+ G = nx.path_graph(4)
428
+ # selfloop edge not in G
429
+ assert not nx.is_matching(G, {(0, 0), (1, 2), (2, 3)})
430
+ # selfloop edge in G
431
+ G.add_edge(0, 0)
432
+ assert not nx.is_matching(G, {(0, 0), (1, 2)})
433
+
434
+ def test_invalid_matching(self):
435
+ G = nx.path_graph(4)
436
+ assert not nx.is_matching(G, {(0, 1), (1, 2), (2, 3)})
437
+
438
+ def test_invalid_edge(self):
439
+ G = nx.path_graph(4)
440
+ assert not nx.is_matching(G, {(0, 3), (1, 2)})
441
+
442
+ G = nx.DiGraph(G.edges)
443
+ assert nx.is_matching(G, {(0, 1)})
444
+ assert not nx.is_matching(G, {(1, 0)})
445
+
446
+
447
+ class TestIsMaximalMatching:
448
+ """Unit tests for the
449
+ :func:`~networkx.algorithms.matching.is_maximal_matching` function.
450
+
451
+ """
452
+
453
+ def test_dict(self):
454
+ G = nx.path_graph(4)
455
+ assert nx.is_maximal_matching(G, {0: 1, 1: 0, 2: 3, 3: 2})
456
+
457
+ def test_valid(self):
458
+ G = nx.path_graph(4)
459
+ assert nx.is_maximal_matching(G, {(0, 1), (2, 3)})
460
+
461
+ def test_not_matching(self):
462
+ G = nx.path_graph(4)
463
+ assert not nx.is_maximal_matching(G, {(0, 1), (1, 2), (2, 3)})
464
+ assert not nx.is_maximal_matching(G, {(0, 3)})
465
+ G.add_edge(0, 0)
466
+ assert not nx.is_maximal_matching(G, {(0, 0)})
467
+
468
+ def test_not_maximal(self):
469
+ G = nx.path_graph(4)
470
+ assert not nx.is_maximal_matching(G, {(0, 1)})
471
+
472
+
473
+ class TestIsPerfectMatching:
474
+ """Unit tests for the
475
+ :func:`~networkx.algorithms.matching.is_perfect_matching` function.
476
+
477
+ """
478
+
479
+ def test_dict(self):
480
+ G = nx.path_graph(4)
481
+ assert nx.is_perfect_matching(G, {0: 1, 1: 0, 2: 3, 3: 2})
482
+
483
+ def test_valid(self):
484
+ G = nx.path_graph(4)
485
+ assert nx.is_perfect_matching(G, {(0, 1), (2, 3)})
486
+
487
+ def test_valid_not_path(self):
488
+ G = nx.cycle_graph(4)
489
+ G.add_edge(0, 4)
490
+ G.add_edge(1, 4)
491
+ G.add_edge(5, 2)
492
+
493
+ assert nx.is_perfect_matching(G, {(1, 4), (0, 3), (5, 2)})
494
+
495
+ def test_selfloops(self):
496
+ G = nx.path_graph(4)
497
+ # selfloop edge not in G
498
+ assert not nx.is_perfect_matching(G, {(0, 0), (1, 2), (2, 3)})
499
+ # selfloop edge in G
500
+ G.add_edge(0, 0)
501
+ assert not nx.is_perfect_matching(G, {(0, 0), (1, 2)})
502
+
503
+ def test_not_matching(self):
504
+ G = nx.path_graph(4)
505
+ assert not nx.is_perfect_matching(G, {(0, 3)})
506
+ assert not nx.is_perfect_matching(G, {(0, 1), (1, 2), (2, 3)})
507
+
508
+ def test_maximal_but_not_perfect(self):
509
+ G = nx.cycle_graph(4)
510
+ G.add_edge(0, 4)
511
+ G.add_edge(1, 4)
512
+
513
+ assert not nx.is_perfect_matching(G, {(1, 4), (0, 3)})
514
+
515
+
516
+ class TestMaximalMatching:
517
+ """Unit tests for the
518
+ :func:`~networkx.algorithms.matching.maximal_matching`.
519
+
520
+ """
521
+
522
+ def test_valid_matching(self):
523
+ edges = [(1, 2), (1, 5), (2, 3), (2, 5), (3, 4), (3, 6), (5, 6)]
524
+ G = nx.Graph(edges)
525
+ matching = nx.maximal_matching(G)
526
+ assert nx.is_maximal_matching(G, matching)
527
+
528
+ def test_single_edge_matching(self):
529
+ # In the star graph, any maximal matching has just one edge.
530
+ G = nx.star_graph(5)
531
+ matching = nx.maximal_matching(G)
532
+ assert 1 == len(matching)
533
+ assert nx.is_maximal_matching(G, matching)
534
+
535
+ def test_self_loops(self):
536
+ # Create the path graph with two self-loops.
537
+ G = nx.path_graph(3)
538
+ G.add_edges_from([(0, 0), (1, 1)])
539
+ matching = nx.maximal_matching(G)
540
+ assert len(matching) == 1
541
+ # The matching should never include self-loops.
542
+ assert not any(u == v for u, v in matching)
543
+ assert nx.is_maximal_matching(G, matching)
544
+
545
+ def test_ordering(self):
546
+ """Tests that a maximal matching is computed correctly
547
+ regardless of the order in which nodes are added to the graph.
548
+
549
+ """
550
+ for nodes in permutations(range(3)):
551
+ G = nx.Graph()
552
+ G.add_nodes_from(nodes)
553
+ G.add_edges_from([(0, 1), (0, 2)])
554
+ matching = nx.maximal_matching(G)
555
+ assert len(matching) == 1
556
+ assert nx.is_maximal_matching(G, matching)
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_max_weight_clique.py ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Maximum weight clique test suite."""
2
+
3
+ import pytest
4
+
5
+ import networkx as nx
6
+
7
+
8
+ class TestMaximumWeightClique:
9
+ def test_basic_cases(self):
10
+ def check_basic_case(graph_func, expected_weight, weight_accessor):
11
+ graph = graph_func()
12
+ clique, weight = nx.algorithms.max_weight_clique(graph, weight_accessor)
13
+ assert verify_clique(
14
+ graph, clique, weight, expected_weight, weight_accessor
15
+ )
16
+
17
+ for graph_func, (expected_weight, expected_size) in TEST_CASES.items():
18
+ check_basic_case(graph_func, expected_weight, "weight")
19
+ check_basic_case(graph_func, expected_size, None)
20
+
21
+ def test_key_error(self):
22
+ graph = two_node_graph()
23
+ with pytest.raises(KeyError):
24
+ nx.algorithms.max_weight_clique(graph, "nonexistent-key")
25
+
26
+ def test_error_on_non_integer_weight(self):
27
+ graph = two_node_graph()
28
+ graph.nodes[2]["weight"] = 1.5
29
+ with pytest.raises(ValueError):
30
+ nx.algorithms.max_weight_clique(graph)
31
+
32
+ def test_unaffected_by_self_loops(self):
33
+ graph = two_node_graph()
34
+ graph.add_edge(1, 1)
35
+ graph.add_edge(2, 2)
36
+ clique, weight = nx.algorithms.max_weight_clique(graph, "weight")
37
+ assert verify_clique(graph, clique, weight, 30, "weight")
38
+ graph = three_node_independent_set()
39
+ graph.add_edge(1, 1)
40
+ clique, weight = nx.algorithms.max_weight_clique(graph, "weight")
41
+ assert verify_clique(graph, clique, weight, 20, "weight")
42
+
43
+ def test_30_node_prob(self):
44
+ G = nx.Graph()
45
+ G.add_nodes_from(range(1, 31))
46
+ for i in range(1, 31):
47
+ G.nodes[i]["weight"] = i + 1
48
+ # fmt: off
49
+ G.add_edges_from(
50
+ [
51
+ (1, 12), (1, 13), (1, 15), (1, 16), (1, 18), (1, 19), (1, 20),
52
+ (1, 23), (1, 26), (1, 28), (1, 29), (1, 30), (2, 3), (2, 4),
53
+ (2, 5), (2, 8), (2, 9), (2, 10), (2, 14), (2, 17), (2, 18),
54
+ (2, 21), (2, 22), (2, 23), (2, 27), (3, 9), (3, 15), (3, 21),
55
+ (3, 22), (3, 23), (3, 24), (3, 27), (3, 28), (3, 29), (4, 5),
56
+ (4, 6), (4, 8), (4, 21), (4, 22), (4, 23), (4, 26), (4, 28),
57
+ (4, 30), (5, 6), (5, 8), (5, 9), (5, 13), (5, 14), (5, 15),
58
+ (5, 16), (5, 20), (5, 21), (5, 22), (5, 25), (5, 28), (5, 29),
59
+ (6, 7), (6, 8), (6, 13), (6, 17), (6, 18), (6, 19), (6, 24),
60
+ (6, 26), (6, 27), (6, 28), (6, 29), (7, 12), (7, 14), (7, 15),
61
+ (7, 16), (7, 17), (7, 20), (7, 25), (7, 27), (7, 29), (7, 30),
62
+ (8, 10), (8, 15), (8, 16), (8, 18), (8, 20), (8, 22), (8, 24),
63
+ (8, 26), (8, 27), (8, 28), (8, 30), (9, 11), (9, 12), (9, 13),
64
+ (9, 14), (9, 15), (9, 16), (9, 19), (9, 20), (9, 21), (9, 24),
65
+ (9, 30), (10, 12), (10, 15), (10, 18), (10, 19), (10, 20),
66
+ (10, 22), (10, 23), (10, 24), (10, 26), (10, 27), (10, 29),
67
+ (10, 30), (11, 13), (11, 15), (11, 16), (11, 17), (11, 18),
68
+ (11, 19), (11, 20), (11, 22), (11, 29), (11, 30), (12, 14),
69
+ (12, 17), (12, 18), (12, 19), (12, 20), (12, 21), (12, 23),
70
+ (12, 25), (12, 26), (12, 30), (13, 20), (13, 22), (13, 23),
71
+ (13, 24), (13, 30), (14, 16), (14, 20), (14, 21), (14, 22),
72
+ (14, 23), (14, 25), (14, 26), (14, 27), (14, 29), (14, 30),
73
+ (15, 17), (15, 18), (15, 20), (15, 21), (15, 26), (15, 27),
74
+ (15, 28), (16, 17), (16, 18), (16, 19), (16, 20), (16, 21),
75
+ (16, 29), (16, 30), (17, 18), (17, 21), (17, 22), (17, 25),
76
+ (17, 27), (17, 28), (17, 30), (18, 19), (18, 20), (18, 21),
77
+ (18, 22), (18, 23), (18, 24), (19, 20), (19, 22), (19, 23),
78
+ (19, 24), (19, 25), (19, 27), (19, 30), (20, 21), (20, 23),
79
+ (20, 24), (20, 26), (20, 28), (20, 29), (21, 23), (21, 26),
80
+ (21, 27), (21, 29), (22, 24), (22, 25), (22, 26), (22, 29),
81
+ (23, 25), (23, 30), (24, 25), (24, 26), (25, 27), (25, 29),
82
+ (26, 27), (26, 28), (26, 30), (28, 29), (29, 30),
83
+ ]
84
+ )
85
+ # fmt: on
86
+ clique, weight = nx.algorithms.max_weight_clique(G)
87
+ assert verify_clique(G, clique, weight, 111, "weight")
88
+
89
+
90
+ # ############################ Utility functions ############################
91
+ def verify_clique(
92
+ graph, clique, reported_clique_weight, expected_clique_weight, weight_accessor
93
+ ):
94
+ for node1 in clique:
95
+ for node2 in clique:
96
+ if node1 == node2:
97
+ continue
98
+ if not graph.has_edge(node1, node2):
99
+ return False
100
+
101
+ if weight_accessor is None:
102
+ clique_weight = len(clique)
103
+ else:
104
+ clique_weight = sum(graph.nodes[v]["weight"] for v in clique)
105
+
106
+ if clique_weight != expected_clique_weight:
107
+ return False
108
+ if clique_weight != reported_clique_weight:
109
+ return False
110
+
111
+ return True
112
+
113
+
114
+ # ############################ Graph Generation ############################
115
+
116
+
117
+ def empty_graph():
118
+ return nx.Graph()
119
+
120
+
121
+ def one_node_graph():
122
+ graph = nx.Graph()
123
+ graph.add_nodes_from([1])
124
+ graph.nodes[1]["weight"] = 10
125
+ return graph
126
+
127
+
128
+ def two_node_graph():
129
+ graph = nx.Graph()
130
+ graph.add_nodes_from([1, 2])
131
+ graph.add_edges_from([(1, 2)])
132
+ graph.nodes[1]["weight"] = 10
133
+ graph.nodes[2]["weight"] = 20
134
+ return graph
135
+
136
+
137
+ def three_node_clique():
138
+ graph = nx.Graph()
139
+ graph.add_nodes_from([1, 2, 3])
140
+ graph.add_edges_from([(1, 2), (1, 3), (2, 3)])
141
+ graph.nodes[1]["weight"] = 10
142
+ graph.nodes[2]["weight"] = 20
143
+ graph.nodes[3]["weight"] = 5
144
+ return graph
145
+
146
+
147
+ def three_node_independent_set():
148
+ graph = nx.Graph()
149
+ graph.add_nodes_from([1, 2, 3])
150
+ graph.nodes[1]["weight"] = 10
151
+ graph.nodes[2]["weight"] = 20
152
+ graph.nodes[3]["weight"] = 5
153
+ return graph
154
+
155
+
156
+ def disconnected():
157
+ graph = nx.Graph()
158
+ graph.add_edges_from([(1, 2), (2, 3), (4, 5), (5, 6)])
159
+ graph.nodes[1]["weight"] = 10
160
+ graph.nodes[2]["weight"] = 20
161
+ graph.nodes[3]["weight"] = 5
162
+ graph.nodes[4]["weight"] = 100
163
+ graph.nodes[5]["weight"] = 200
164
+ graph.nodes[6]["weight"] = 50
165
+ return graph
166
+
167
+
168
+ # --------------------------------------------------------------------------
169
+ # Basic tests for all strategies
170
+ # For each basic graph function, specify expected weight of max weight clique
171
+ # and expected size of maximum clique
172
+ TEST_CASES = {
173
+ empty_graph: (0, 0),
174
+ one_node_graph: (10, 1),
175
+ two_node_graph: (30, 2),
176
+ three_node_clique: (35, 3),
177
+ three_node_independent_set: (20, 1),
178
+ disconnected: (300, 2),
179
+ }
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_mis.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Tests for maximal (not maximum) independent sets.
3
+
4
+ """
5
+
6
+ import random
7
+
8
+ import pytest
9
+
10
+ import networkx as nx
11
+
12
+
13
+ def test_random_seed():
14
+ G = nx.empty_graph(5)
15
+ assert nx.maximal_independent_set(G, seed=1) == [1, 0, 3, 2, 4]
16
+
17
+
18
+ @pytest.mark.parametrize("graph", [nx.complete_graph(5), nx.complete_graph(55)])
19
+ def test_K5(graph):
20
+ """Maximal independent set for complete graphs"""
21
+ assert all(nx.maximal_independent_set(graph, [n]) == [n] for n in graph)
22
+
23
+
24
+ def test_exceptions():
25
+ """Bad input should raise exception."""
26
+ G = nx.florentine_families_graph()
27
+ pytest.raises(nx.NetworkXUnfeasible, nx.maximal_independent_set, G, ["Smith"])
28
+ pytest.raises(
29
+ nx.NetworkXUnfeasible, nx.maximal_independent_set, G, ["Salviati", "Pazzi"]
30
+ )
31
+ # MaximalIndependentSet is not implemented for directed graphs
32
+ pytest.raises(nx.NetworkXNotImplemented, nx.maximal_independent_set, nx.DiGraph(G))
33
+
34
+
35
+ def test_florentine_family():
36
+ G = nx.florentine_families_graph()
37
+ indep = nx.maximal_independent_set(G, ["Medici", "Bischeri"])
38
+ assert set(indep) == {
39
+ "Medici",
40
+ "Bischeri",
41
+ "Castellani",
42
+ "Pazzi",
43
+ "Ginori",
44
+ "Lamberteschi",
45
+ }
46
+
47
+
48
+ def test_bipartite():
49
+ G = nx.complete_bipartite_graph(12, 34)
50
+ indep = nx.maximal_independent_set(G, [4, 5, 9, 10])
51
+ assert sorted(indep) == list(range(12))
52
+
53
+
54
+ def test_random_graphs():
55
+ """Generate 5 random graphs of different types and sizes and
56
+ make sure that all sets are independent and maximal."""
57
+ for i in range(0, 50, 10):
58
+ G = nx.erdos_renyi_graph(i * 10 + 1, random.random())
59
+ IS = nx.maximal_independent_set(G)
60
+ assert G.subgraph(IS).number_of_edges() == 0
61
+ nbrs_of_MIS = set.union(*(set(G.neighbors(v)) for v in IS))
62
+ assert all(v in nbrs_of_MIS for v in set(G.nodes()).difference(IS))
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_moral.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import networkx as nx
2
+ from networkx.algorithms.moral import moral_graph
3
+
4
+
5
+ def test_get_moral_graph():
6
+ graph = nx.DiGraph()
7
+ graph.add_nodes_from([1, 2, 3, 4, 5, 6, 7])
8
+ graph.add_edges_from([(1, 2), (3, 2), (4, 1), (4, 5), (6, 5), (7, 5)])
9
+ H = moral_graph(graph)
10
+ assert not H.is_directed()
11
+ assert H.has_edge(1, 3)
12
+ assert H.has_edge(4, 6)
13
+ assert H.has_edge(6, 7)
14
+ assert H.has_edge(4, 7)
15
+ assert not H.has_edge(1, 5)
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_node_classification.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ pytest.importorskip("numpy")
4
+ pytest.importorskip("scipy")
5
+
6
+ import networkx as nx
7
+ from networkx.algorithms import node_classification
8
+
9
+
10
+ class TestHarmonicFunction:
11
+ def test_path_graph(self):
12
+ G = nx.path_graph(4)
13
+ label_name = "label"
14
+ G.nodes[0][label_name] = "A"
15
+ G.nodes[3][label_name] = "B"
16
+ predicted = node_classification.harmonic_function(G, label_name=label_name)
17
+ assert predicted[0] == "A"
18
+ assert predicted[1] == "A"
19
+ assert predicted[2] == "B"
20
+ assert predicted[3] == "B"
21
+
22
+ def test_no_labels(self):
23
+ with pytest.raises(nx.NetworkXError):
24
+ G = nx.path_graph(4)
25
+ node_classification.harmonic_function(G)
26
+
27
+ def test_no_nodes(self):
28
+ with pytest.raises(nx.NetworkXError):
29
+ G = nx.Graph()
30
+ node_classification.harmonic_function(G)
31
+
32
+ def test_no_edges(self):
33
+ with pytest.raises(nx.NetworkXError):
34
+ G = nx.Graph()
35
+ G.add_node(1)
36
+ G.add_node(2)
37
+ node_classification.harmonic_function(G)
38
+
39
+ def test_digraph(self):
40
+ with pytest.raises(nx.NetworkXNotImplemented):
41
+ G = nx.DiGraph()
42
+ G.add_edge(0, 1)
43
+ G.add_edge(1, 2)
44
+ G.add_edge(2, 3)
45
+ label_name = "label"
46
+ G.nodes[0][label_name] = "A"
47
+ G.nodes[3][label_name] = "B"
48
+ node_classification.harmonic_function(G)
49
+
50
+ def test_one_labeled_node(self):
51
+ G = nx.path_graph(4)
52
+ label_name = "label"
53
+ G.nodes[0][label_name] = "A"
54
+ predicted = node_classification.harmonic_function(G, label_name=label_name)
55
+ assert predicted[0] == "A"
56
+ assert predicted[1] == "A"
57
+ assert predicted[2] == "A"
58
+ assert predicted[3] == "A"
59
+
60
+ def test_nodes_all_labeled(self):
61
+ G = nx.karate_club_graph()
62
+ label_name = "club"
63
+ predicted = node_classification.harmonic_function(G, label_name=label_name)
64
+ for i in range(len(G)):
65
+ assert predicted[i] == G.nodes[i][label_name]
66
+
67
+ def test_labeled_nodes_are_not_changed(self):
68
+ G = nx.karate_club_graph()
69
+ label_name = "club"
70
+ label_removed = {0, 1, 2, 3, 4, 5, 6, 7}
71
+ for i in label_removed:
72
+ del G.nodes[i][label_name]
73
+ predicted = node_classification.harmonic_function(G, label_name=label_name)
74
+ label_not_removed = set(range(len(G))) - label_removed
75
+ for i in label_not_removed:
76
+ assert predicted[i] == G.nodes[i][label_name]
77
+
78
+
79
+ class TestLocalAndGlobalConsistency:
80
+ def test_path_graph(self):
81
+ G = nx.path_graph(4)
82
+ label_name = "label"
83
+ G.nodes[0][label_name] = "A"
84
+ G.nodes[3][label_name] = "B"
85
+ predicted = node_classification.local_and_global_consistency(
86
+ G, label_name=label_name
87
+ )
88
+ assert predicted[0] == "A"
89
+ assert predicted[1] == "A"
90
+ assert predicted[2] == "B"
91
+ assert predicted[3] == "B"
92
+
93
+ def test_no_labels(self):
94
+ with pytest.raises(nx.NetworkXError):
95
+ G = nx.path_graph(4)
96
+ node_classification.local_and_global_consistency(G)
97
+
98
+ def test_no_nodes(self):
99
+ with pytest.raises(nx.NetworkXError):
100
+ G = nx.Graph()
101
+ node_classification.local_and_global_consistency(G)
102
+
103
+ def test_no_edges(self):
104
+ with pytest.raises(nx.NetworkXError):
105
+ G = nx.Graph()
106
+ G.add_node(1)
107
+ G.add_node(2)
108
+ node_classification.local_and_global_consistency(G)
109
+
110
+ def test_digraph(self):
111
+ with pytest.raises(nx.NetworkXNotImplemented):
112
+ G = nx.DiGraph()
113
+ G.add_edge(0, 1)
114
+ G.add_edge(1, 2)
115
+ G.add_edge(2, 3)
116
+ label_name = "label"
117
+ G.nodes[0][label_name] = "A"
118
+ G.nodes[3][label_name] = "B"
119
+ node_classification.harmonic_function(G)
120
+
121
+ def test_one_labeled_node(self):
122
+ G = nx.path_graph(4)
123
+ label_name = "label"
124
+ G.nodes[0][label_name] = "A"
125
+ predicted = node_classification.local_and_global_consistency(
126
+ G, label_name=label_name
127
+ )
128
+ assert predicted[0] == "A"
129
+ assert predicted[1] == "A"
130
+ assert predicted[2] == "A"
131
+ assert predicted[3] == "A"
132
+
133
+ def test_nodes_all_labeled(self):
134
+ G = nx.karate_club_graph()
135
+ label_name = "club"
136
+ predicted = node_classification.local_and_global_consistency(
137
+ G, alpha=0, label_name=label_name
138
+ )
139
+ for i in range(len(G)):
140
+ assert predicted[i] == G.nodes[i][label_name]
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_non_randomness.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import networkx as nx
4
+
5
+ np = pytest.importorskip("numpy")
6
+
7
+
8
+ @pytest.mark.parametrize(
9
+ "k, weight, expected",
10
+ [
11
+ (None, None, 7.21), # infers 3 communities
12
+ (2, None, 11.7),
13
+ (None, "weight", 25.45),
14
+ (2, "weight", 38.8),
15
+ ],
16
+ )
17
+ def test_non_randomness(k, weight, expected):
18
+ G = nx.karate_club_graph()
19
+ np.testing.assert_almost_equal(
20
+ nx.non_randomness(G, k, weight)[0], expected, decimal=2
21
+ )
22
+
23
+
24
+ def test_non_connected():
25
+ G = nx.Graph([(1, 2)])
26
+ G.add_node(3)
27
+ with pytest.raises(nx.NetworkXException, match="Non connected"):
28
+ nx.non_randomness(G)
29
+
30
+
31
+ def test_self_loops():
32
+ G = nx.Graph()
33
+ G.add_edge(1, 2)
34
+ G.add_edge(1, 1)
35
+ with pytest.raises(nx.NetworkXError, match="Graph must not contain self-loops"):
36
+ nx.non_randomness(G)
37
+
38
+
39
+ def test_empty_graph():
40
+ G = nx.empty_graph(1)
41
+ with pytest.raises(nx.NetworkXError, match=".*not applicable to empty graphs"):
42
+ nx.non_randomness(G)
43
+
44
+
45
+ @pytest.mark.parametrize("k", [-1, 0, 2, 5])
46
+ def test_value_error(k):
47
+ """
48
+ Check that invalid values of k raise (must be between 1 and n - 1, inclusive,
49
+ and such that the probability is between 0 and 1, exclusive).
50
+ """
51
+ G = nx.path_graph(5)
52
+ with pytest.raises(ValueError, match=r"invalid number of communities"):
53
+ nx.non_randomness(G, k=k)
54
+
55
+
56
+ @pytest.mark.parametrize("G", [nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph()])
57
+ def test_not_implemented(G):
58
+ """Check that non-randomness is not implemented for directed or multigraphs."""
59
+ with pytest.raises(nx.NetworkXNotImplemented, match=r"not implemented for"):
60
+ nx.non_randomness(G)
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_perfect_graph.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import networkx as nx
2
+
3
+
4
+ def test_chordal_graph():
5
+ G = nx.complete_graph(5)
6
+ assert nx.is_perfect_graph(G)
7
+
8
+
9
+ def test_odd_cycle():
10
+ G = nx.cycle_graph(5) # Induced odd cycle
11
+ assert not nx.is_perfect_graph(G)
12
+
13
+
14
+ def test_even_cycle():
15
+ G = nx.cycle_graph(6) # Even cycle is perfect
16
+ assert nx.is_perfect_graph(G)
17
+
18
+
19
+ def test_complement_of_odd_cycle():
20
+ G = nx.cycle_graph(7)
21
+ GC = nx.complement(G)
22
+ assert not nx.is_perfect_graph(GC)
23
+
24
+
25
+ def test_disconnected_union_of_cliques():
26
+ G = nx.disjoint_union(nx.complete_graph(3), nx.complete_graph(4))
27
+ assert nx.is_perfect_graph(G)
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_planar_drawing.py ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+
3
+ import pytest
4
+
5
+ import networkx as nx
6
+ from networkx.algorithms.planar_drawing import triangulate_embedding
7
+
8
+
9
+ def test_graph1():
10
+ embedding_data = {0: [1, 2, 3], 1: [2, 0], 2: [3, 0, 1], 3: [2, 0]}
11
+ check_embedding_data(embedding_data)
12
+
13
+
14
+ def test_graph2():
15
+ embedding_data = {
16
+ 0: [8, 6],
17
+ 1: [2, 6, 9],
18
+ 2: [8, 1, 7, 9, 6, 4],
19
+ 3: [9],
20
+ 4: [2],
21
+ 5: [6, 8],
22
+ 6: [9, 1, 0, 5, 2],
23
+ 7: [9, 2],
24
+ 8: [0, 2, 5],
25
+ 9: [1, 6, 2, 7, 3],
26
+ }
27
+ check_embedding_data(embedding_data)
28
+
29
+
30
+ def test_circle_graph():
31
+ embedding_data = {
32
+ 0: [1, 9],
33
+ 1: [0, 2],
34
+ 2: [1, 3],
35
+ 3: [2, 4],
36
+ 4: [3, 5],
37
+ 5: [4, 6],
38
+ 6: [5, 7],
39
+ 7: [6, 8],
40
+ 8: [7, 9],
41
+ 9: [8, 0],
42
+ }
43
+ check_embedding_data(embedding_data)
44
+
45
+
46
+ def test_grid_graph():
47
+ embedding_data = {
48
+ (0, 1): [(0, 0), (1, 1), (0, 2)],
49
+ (1, 2): [(1, 1), (2, 2), (0, 2)],
50
+ (0, 0): [(0, 1), (1, 0)],
51
+ (2, 1): [(2, 0), (2, 2), (1, 1)],
52
+ (1, 1): [(2, 1), (1, 2), (0, 1), (1, 0)],
53
+ (2, 0): [(1, 0), (2, 1)],
54
+ (2, 2): [(1, 2), (2, 1)],
55
+ (1, 0): [(0, 0), (2, 0), (1, 1)],
56
+ (0, 2): [(1, 2), (0, 1)],
57
+ }
58
+ check_embedding_data(embedding_data)
59
+
60
+
61
+ def test_one_node_graph():
62
+ embedding_data = {0: []}
63
+ check_embedding_data(embedding_data)
64
+
65
+
66
+ def test_two_node_graph():
67
+ embedding_data = {0: [1], 1: [0]}
68
+ check_embedding_data(embedding_data)
69
+
70
+
71
+ def test_three_node_graph():
72
+ embedding_data = {0: [1, 2], 1: [0, 2], 2: [0, 1]}
73
+ check_embedding_data(embedding_data)
74
+
75
+
76
+ def test_multiple_component_graph1():
77
+ embedding_data = {0: [], 1: []}
78
+ check_embedding_data(embedding_data)
79
+
80
+
81
+ def test_multiple_component_graph2():
82
+ embedding_data = {0: [1, 2], 1: [0, 2], 2: [0, 1], 3: [4, 5], 4: [3, 5], 5: [3, 4]}
83
+ check_embedding_data(embedding_data)
84
+
85
+
86
+ def test_invalid_half_edge():
87
+ with pytest.raises(nx.NetworkXException):
88
+ embedding_data = {1: [2, 3, 4], 2: [1, 3, 4], 3: [1, 2, 4], 4: [1, 2, 3]}
89
+ embedding = nx.PlanarEmbedding()
90
+ embedding.set_data(embedding_data)
91
+ nx.combinatorial_embedding_to_pos(embedding)
92
+
93
+
94
+ def test_triangulate_embedding1():
95
+ embedding = nx.PlanarEmbedding()
96
+ embedding.add_node(1)
97
+ expected_embedding = {1: []}
98
+ check_triangulation(embedding, expected_embedding)
99
+
100
+
101
+ def test_triangulate_embedding2():
102
+ embedding = nx.PlanarEmbedding()
103
+ embedding.connect_components(1, 2)
104
+ expected_embedding = {1: [2], 2: [1]}
105
+ check_triangulation(embedding, expected_embedding)
106
+
107
+
108
+ def check_triangulation(embedding, expected_embedding):
109
+ res_embedding, _ = triangulate_embedding(embedding, True)
110
+ assert res_embedding.get_data() == expected_embedding, (
111
+ "Expected embedding incorrect"
112
+ )
113
+ res_embedding, _ = triangulate_embedding(embedding, False)
114
+ assert res_embedding.get_data() == expected_embedding, (
115
+ "Expected embedding incorrect"
116
+ )
117
+
118
+
119
+ def check_embedding_data(embedding_data):
120
+ """Checks that the planar embedding of the input is correct"""
121
+ embedding = nx.PlanarEmbedding()
122
+ embedding.set_data(embedding_data)
123
+ pos_fully = nx.combinatorial_embedding_to_pos(embedding, False)
124
+ msg = "Planar drawing does not conform to the embedding (fully triangulation)"
125
+ assert planar_drawing_conforms_to_embedding(embedding, pos_fully), msg
126
+ check_edge_intersections(embedding, pos_fully)
127
+ pos_internally = nx.combinatorial_embedding_to_pos(embedding, True)
128
+ msg = "Planar drawing does not conform to the embedding (internal triangulation)"
129
+ assert planar_drawing_conforms_to_embedding(embedding, pos_internally), msg
130
+ check_edge_intersections(embedding, pos_internally)
131
+
132
+
133
+ def is_close(a, b, rel_tol=1e-09, abs_tol=0.0):
134
+ # Check if float numbers are basically equal, for python >=3.5 there is
135
+ # function for that in the standard library
136
+ return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
137
+
138
+
139
+ def point_in_between(a, b, p):
140
+ # checks if p is on the line between a and b
141
+ x1, y1 = a
142
+ x2, y2 = b
143
+ px, py = p
144
+ dist_1_2 = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
145
+ dist_1_p = math.sqrt((x1 - px) ** 2 + (y1 - py) ** 2)
146
+ dist_2_p = math.sqrt((x2 - px) ** 2 + (y2 - py) ** 2)
147
+ return is_close(dist_1_p + dist_2_p, dist_1_2)
148
+
149
+
150
+ def check_edge_intersections(G, pos):
151
+ """Check all edges in G for intersections.
152
+
153
+ Raises an exception if an intersection is found.
154
+
155
+ Parameters
156
+ ----------
157
+ G : NetworkX graph
158
+ pos : dict
159
+ Maps every node to a tuple (x, y) representing its position
160
+
161
+ """
162
+ for a, b in G.edges():
163
+ for c, d in G.edges():
164
+ # Check if end points are different
165
+ if a != c and b != d and b != c and a != d:
166
+ x1, y1 = pos[a]
167
+ x2, y2 = pos[b]
168
+ x3, y3 = pos[c]
169
+ x4, y4 = pos[d]
170
+ determinant = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
171
+ if determinant != 0: # the lines are not parallel
172
+ # calculate intersection point, see:
173
+ # https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
174
+ px = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (
175
+ x3 * y4 - y3 * x4
176
+ ) / determinant
177
+ py = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (
178
+ x3 * y4 - y3 * x4
179
+ ) / determinant
180
+
181
+ # Check if intersection lies between the points
182
+ if point_in_between(pos[a], pos[b], (px, py)) and point_in_between(
183
+ pos[c], pos[d], (px, py)
184
+ ):
185
+ msg = f"There is an intersection at {px},{py}"
186
+ raise nx.NetworkXException(msg)
187
+
188
+ # Check overlap
189
+ msg = "A node lies on a edge connecting two other nodes"
190
+ if (
191
+ point_in_between(pos[a], pos[b], pos[c])
192
+ or point_in_between(pos[a], pos[b], pos[d])
193
+ or point_in_between(pos[c], pos[d], pos[a])
194
+ or point_in_between(pos[c], pos[d], pos[b])
195
+ ):
196
+ raise nx.NetworkXException(msg)
197
+ # No edge intersection found
198
+
199
+
200
+ class Vector:
201
+ """Compare vectors by their angle without loss of precision
202
+
203
+ All vectors in direction [0, 1] are the smallest.
204
+ The vectors grow in clockwise direction.
205
+ """
206
+
207
+ __slots__ = ["x", "y", "node", "quadrant"]
208
+
209
+ def __init__(self, x, y, node):
210
+ self.x = x
211
+ self.y = y
212
+ self.node = node
213
+ if self.x >= 0 and self.y > 0:
214
+ self.quadrant = 1
215
+ elif self.x > 0 and self.y <= 0:
216
+ self.quadrant = 2
217
+ elif self.x <= 0 and self.y < 0:
218
+ self.quadrant = 3
219
+ else:
220
+ self.quadrant = 4
221
+
222
+ def __eq__(self, other):
223
+ return self.quadrant == other.quadrant and self.x * other.y == self.y * other.x
224
+
225
+ def __lt__(self, other):
226
+ if self.quadrant < other.quadrant:
227
+ return True
228
+ elif self.quadrant > other.quadrant:
229
+ return False
230
+ else:
231
+ return self.x * other.y < self.y * other.x
232
+
233
+ def __ne__(self, other):
234
+ return self != other
235
+
236
+ def __le__(self, other):
237
+ return not other < self
238
+
239
+ def __gt__(self, other):
240
+ return other < self
241
+
242
+ def __ge__(self, other):
243
+ return not self < other
244
+
245
+
246
+ def planar_drawing_conforms_to_embedding(embedding, pos):
247
+ """Checks if pos conforms to the planar embedding
248
+
249
+ Returns true iff the neighbors are actually oriented in the orientation
250
+ specified of the embedding
251
+ """
252
+ for v in embedding:
253
+ nbr_vectors = []
254
+ v_pos = pos[v]
255
+ for nbr in embedding[v]:
256
+ new_vector = Vector(pos[nbr][0] - v_pos[0], pos[nbr][1] - v_pos[1], nbr)
257
+ nbr_vectors.append(new_vector)
258
+ # Sort neighbors according to their phi angle
259
+ nbr_vectors.sort()
260
+ for idx, nbr_vector in enumerate(nbr_vectors):
261
+ cw_vector = nbr_vectors[(idx + 1) % len(nbr_vectors)]
262
+ ccw_vector = nbr_vectors[idx - 1]
263
+ if (
264
+ embedding[v][nbr_vector.node]["cw"] != cw_vector.node
265
+ or embedding[v][nbr_vector.node]["ccw"] != ccw_vector.node
266
+ ):
267
+ return False
268
+ if cw_vector.node != nbr_vector.node and cw_vector == nbr_vector:
269
+ # Lines overlap
270
+ return False
271
+ if ccw_vector.node != nbr_vector.node and ccw_vector == nbr_vector:
272
+ # Lines overlap
273
+ return False
274
+ return True
venv/lib/python3.12/site-packages/networkx/algorithms/tests/test_planarity.py ADDED
@@ -0,0 +1,556 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ import networkx as nx
4
+ from networkx.algorithms.planarity import (
5
+ check_planarity_recursive,
6
+ get_counterexample,
7
+ get_counterexample_recursive,
8
+ )
9
+
10
+
11
+ class TestLRPlanarity:
12
+ """Nose Unit tests for the :mod:`networkx.algorithms.planarity` module.
13
+
14
+ Tests three things:
15
+ 1. Check that the result is correct
16
+ (returns planar if and only if the graph is actually planar)
17
+ 2. In case a counter example is returned: Check if it is correct
18
+ 3. In case an embedding is returned: Check if its actually an embedding
19
+ """
20
+
21
+ @staticmethod
22
+ def check_graph(G, is_planar=None):
23
+ """Raises an exception if the lr_planarity check returns a wrong result
24
+
25
+ Parameters
26
+ ----------
27
+ G : NetworkX graph
28
+ is_planar : bool
29
+ The expected result of the planarity check.
30
+ If set to None only counter example or embedding are verified.
31
+
32
+ """
33
+
34
+ # obtain results of planarity check
35
+ is_planar_lr, result = nx.check_planarity(G, True)
36
+ is_planar_lr_rec, result_rec = check_planarity_recursive(G, True)
37
+
38
+ if is_planar is not None:
39
+ # set a message for the assert
40
+ if is_planar:
41
+ msg = "Wrong planarity check result. Should be planar."
42
+ else:
43
+ msg = "Wrong planarity check result. Should be non-planar."
44
+
45
+ # check if the result is as expected
46
+ assert is_planar == is_planar_lr, msg
47
+ assert is_planar == is_planar_lr_rec, msg
48
+
49
+ if is_planar_lr:
50
+ # check embedding
51
+ check_embedding(G, result)
52
+ check_embedding(G, result_rec)
53
+ else:
54
+ # check counter example
55
+ check_counterexample(G, result)
56
+ check_counterexample(G, result_rec)
57
+
58
+ def test_simple_planar_graph(self):
59
+ e = [
60
+ (1, 2),
61
+ (2, 3),
62
+ (3, 4),
63
+ (4, 6),
64
+ (6, 7),
65
+ (7, 1),
66
+ (1, 5),
67
+ (5, 2),
68
+ (2, 4),
69
+ (4, 5),
70
+ (5, 7),
71
+ ]
72
+ self.check_graph(nx.Graph(e), is_planar=True)
73
+
74
+ def test_planar_with_selfloop(self):
75
+ e = [
76
+ (1, 1),
77
+ (2, 2),
78
+ (3, 3),
79
+ (4, 4),
80
+ (5, 5),
81
+ (1, 2),
82
+ (1, 3),
83
+ (1, 5),
84
+ (2, 5),
85
+ (2, 4),
86
+ (3, 4),
87
+ (3, 5),
88
+ (4, 5),
89
+ ]
90
+ self.check_graph(nx.Graph(e), is_planar=True)
91
+
92
+ def test_k3_3(self):
93
+ self.check_graph(nx.complete_bipartite_graph(3, 3), is_planar=False)
94
+
95
+ def test_k5(self):
96
+ self.check_graph(nx.complete_graph(5), is_planar=False)
97
+
98
+ def test_multiple_components_planar(self):
99
+ e = [(1, 2), (2, 3), (3, 1), (4, 5), (5, 6), (6, 4)]
100
+ self.check_graph(nx.Graph(e), is_planar=True)
101
+
102
+ def test_multiple_components_non_planar(self):
103
+ G = nx.complete_graph(5)
104
+ # add another planar component to the non planar component
105
+ # G stays non planar
106
+ G.add_edges_from([(6, 7), (7, 8), (8, 6)])
107
+ self.check_graph(G, is_planar=False)
108
+
109
+ def test_non_planar_with_selfloop(self):
110
+ G = nx.complete_graph(5)
111
+ # add self loops
112
+ for i in range(5):
113
+ G.add_edge(i, i)
114
+ self.check_graph(G, is_planar=False)
115
+
116
+ def test_non_planar1(self):
117
+ # tests a graph that has no subgraph directly isomorph to K5 or K3_3
118
+ e = [
119
+ (1, 5),
120
+ (1, 6),
121
+ (1, 7),
122
+ (2, 6),
123
+ (2, 3),
124
+ (3, 5),
125
+ (3, 7),
126
+ (4, 5),
127
+ (4, 6),
128
+ (4, 7),
129
+ ]
130
+ self.check_graph(nx.Graph(e), is_planar=False)
131
+
132
+ def test_loop(self):
133
+ # test a graph with a selfloop
134
+ e = [(1, 2), (2, 2)]
135
+ G = nx.Graph(e)
136
+ self.check_graph(G, is_planar=True)
137
+
138
+ def test_comp(self):
139
+ # test multiple component graph
140
+ e = [(1, 2), (3, 4)]
141
+ G = nx.Graph(e)
142
+ G.remove_edge(1, 2)
143
+ self.check_graph(G, is_planar=True)
144
+
145
+ def test_goldner_harary(self):
146
+ # test goldner-harary graph (a maximal planar graph)
147
+ e = [
148
+ (1, 2),
149
+ (1, 3),
150
+ (1, 4),
151
+ (1, 5),
152
+ (1, 7),
153
+ (1, 8),
154
+ (1, 10),
155
+ (1, 11),
156
+ (2, 3),
157
+ (2, 4),
158
+ (2, 6),
159
+ (2, 7),
160
+ (2, 9),
161
+ (2, 10),
162
+ (2, 11),
163
+ (3, 4),
164
+ (4, 5),
165
+ (4, 6),
166
+ (4, 7),
167
+ (5, 7),
168
+ (6, 7),
169
+ (7, 8),
170
+ (7, 9),
171
+ (7, 10),
172
+ (8, 10),
173
+ (9, 10),
174
+ (10, 11),
175
+ ]
176
+ G = nx.Graph(e)
177
+ self.check_graph(G, is_planar=True)
178
+
179
+ def test_planar_multigraph(self):
180
+ G = nx.MultiGraph([(1, 2), (1, 2), (1, 2), (1, 2), (2, 3), (3, 1)])
181
+ self.check_graph(G, is_planar=True)
182
+
183
+ def test_non_planar_multigraph(self):
184
+ G = nx.MultiGraph(nx.complete_graph(5))
185
+ G.add_edges_from([(1, 2)] * 5)
186
+ self.check_graph(G, is_planar=False)
187
+
188
+ def test_planar_digraph(self):
189
+ G = nx.DiGraph([(1, 2), (2, 3), (2, 4), (4, 1), (4, 2), (1, 4), (3, 2)])
190
+ self.check_graph(G, is_planar=True)
191
+
192
+ def test_non_planar_digraph(self):
193
+ G = nx.DiGraph(nx.complete_graph(5))
194
+ G.remove_edge(1, 2)
195
+ G.remove_edge(4, 1)
196
+ self.check_graph(G, is_planar=False)
197
+
198
+ def test_single_component(self):
199
+ # Test a graph with only a single node
200
+ G = nx.Graph()
201
+ G.add_node(1)
202
+ self.check_graph(G, is_planar=True)
203
+
204
+ def test_graph1(self):
205
+ G = nx.Graph(
206
+ [
207
+ (3, 10),
208
+ (2, 13),
209
+ (1, 13),
210
+ (7, 11),
211
+ (0, 8),
212
+ (8, 13),
213
+ (0, 2),
214
+ (0, 7),
215
+ (0, 10),
216
+ (1, 7),
217
+ ]
218
+ )
219
+ self.check_graph(G, is_planar=True)
220
+
221
+ def test_graph2(self):
222
+ G = nx.Graph(
223
+ [
224
+ (1, 2),
225
+ (4, 13),
226
+ (0, 13),
227
+ (4, 5),
228
+ (7, 10),
229
+ (1, 7),
230
+ (0, 3),
231
+ (2, 6),
232
+ (5, 6),
233
+ (7, 13),
234
+ (4, 8),
235
+ (0, 8),
236
+ (0, 9),
237
+ (2, 13),
238
+ (6, 7),
239
+ (3, 6),
240
+ (2, 8),
241
+ ]
242
+ )
243
+ self.check_graph(G, is_planar=False)
244
+
245
+ def test_graph3(self):
246
+ G = nx.Graph(
247
+ [
248
+ (0, 7),
249
+ (3, 11),
250
+ (3, 4),
251
+ (8, 9),
252
+ (4, 11),
253
+ (1, 7),
254
+ (1, 13),
255
+ (1, 11),
256
+ (3, 5),
257
+ (5, 7),
258
+ (1, 3),
259
+ (0, 4),
260
+ (5, 11),
261
+ (5, 13),
262
+ ]
263
+ )
264
+ self.check_graph(G, is_planar=False)
265
+
266
+ def test_counterexample_planar(self):
267
+ with pytest.raises(nx.NetworkXException):
268
+ # Try to get a counterexample of a planar graph
269
+ G = nx.Graph()
270
+ G.add_node(1)
271
+ get_counterexample(G)
272
+
273
+ def test_counterexample_planar_recursive(self):
274
+ with pytest.raises(nx.NetworkXException):
275
+ # Try to get a counterexample of a planar graph
276
+ G = nx.Graph()
277
+ G.add_node(1)
278
+ get_counterexample_recursive(G)
279
+
280
+ def test_edge_removal_from_planar_embedding(self):
281
+ # PlanarEmbedding.check_structure() must succeed after edge removal
282
+ edges = ((0, 1), (1, 2), (2, 3), (3, 4), (4, 0), (0, 2), (0, 3))
283
+ G = nx.Graph(edges)
284
+ cert, P = nx.check_planarity(G)
285
+ assert cert is True
286
+ P.remove_edge(0, 2)
287
+ self.check_graph(P, is_planar=True)
288
+ P.add_half_edge_ccw(1, 3, 2)
289
+ P.add_half_edge_cw(3, 1, 2)
290
+ self.check_graph(P, is_planar=True)
291
+ P.remove_edges_from(((0, 3), (1, 3)))
292
+ self.check_graph(P, is_planar=True)
293
+
294
+ @pytest.mark.parametrize("graph_type", (nx.Graph, nx.MultiGraph))
295
+ def test_graph_planar_embedding_to_undirected(self, graph_type):
296
+ G = graph_type([(0, 1), (0, 1), (1, 2), (2, 3), (3, 0), (0, 2)])
297
+ is_planar, P = nx.check_planarity(G)
298
+ assert is_planar
299
+ U = P.to_undirected()
300
+ assert isinstance(U, nx.Graph)
301
+ assert all((d == {} for _, _, d in U.edges(data=True)))
302
+
303
+ @pytest.mark.parametrize(
304
+ "reciprocal, as_view", [(True, True), (True, False), (False, True)]
305
+ )
306
+ def test_planar_embedding_to_undirected_invalid_parameters(
307
+ self, reciprocal, as_view
308
+ ):
309
+ G = nx.Graph([(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)])
310
+ is_planar, P = nx.check_planarity(G)
311
+ assert is_planar
312
+ with pytest.raises(ValueError, match="is not supported for PlanarEmbedding."):
313
+ P.to_undirected(reciprocal=reciprocal, as_view=as_view)
314
+
315
+
316
+ def check_embedding(G, embedding):
317
+ """Raises an exception if the combinatorial embedding is not correct
318
+
319
+ Parameters
320
+ ----------
321
+ G : NetworkX graph
322
+ embedding : a dict mapping nodes to a list of edges
323
+ This specifies the ordering of the outgoing edges from a node for
324
+ a combinatorial embedding
325
+
326
+ Notes
327
+ -----
328
+ Checks the following things:
329
+ - The type of the embedding is correct
330
+ - The nodes and edges match the original graph
331
+ - Every half edge has its matching opposite half edge
332
+ - No intersections of edges (checked by Euler's formula)
333
+ """
334
+
335
+ if not isinstance(embedding, nx.PlanarEmbedding):
336
+ raise nx.NetworkXException("Bad embedding. Not of type nx.PlanarEmbedding")
337
+
338
+ # Check structure
339
+ embedding.check_structure()
340
+
341
+ # Check that graphs are equivalent
342
+
343
+ assert set(G.nodes) == set(embedding.nodes), (
344
+ "Bad embedding. Nodes don't match the original graph."
345
+ )
346
+
347
+ # Check that the edges are equal
348
+ g_edges = set()
349
+ for edge in G.edges:
350
+ if edge[0] != edge[1]:
351
+ g_edges.add((edge[0], edge[1]))
352
+ g_edges.add((edge[1], edge[0]))
353
+ assert g_edges == set(embedding.edges), (
354
+ "Bad embedding. Edges don't match the original graph."
355
+ )
356
+
357
+
358
+ def check_counterexample(G, sub_graph):
359
+ """Raises an exception if the counterexample is wrong.
360
+
361
+ Parameters
362
+ ----------
363
+ G : NetworkX graph
364
+ subdivision_nodes : set
365
+ A set of nodes inducing a subgraph as a counterexample
366
+ """
367
+ # 1. Create the sub graph
368
+ sub_graph = nx.Graph(sub_graph)
369
+
370
+ # 2. Remove self loops
371
+ for u in sub_graph:
372
+ if sub_graph.has_edge(u, u):
373
+ sub_graph.remove_edge(u, u)
374
+
375
+ # keep track of nodes we might need to contract
376
+ contract = list(sub_graph)
377
+
378
+ # 3. Contract Edges
379
+ while len(contract) > 0:
380
+ contract_node = contract.pop()
381
+ if contract_node not in sub_graph:
382
+ # Node was already contracted
383
+ continue
384
+ degree = sub_graph.degree[contract_node]
385
+ # Check if we can remove the node
386
+ if degree == 2:
387
+ # Get the two neighbors
388
+ neighbors = iter(sub_graph[contract_node])
389
+ u = next(neighbors)
390
+ v = next(neighbors)
391
+ # Save nodes for later
392
+ contract.append(u)
393
+ contract.append(v)
394
+ # Contract edge
395
+ sub_graph.remove_node(contract_node)
396
+ sub_graph.add_edge(u, v)
397
+
398
+ # 4. Check for isomorphism with K5 or K3_3 graphs
399
+ if len(sub_graph) == 5:
400
+ if not nx.is_isomorphic(nx.complete_graph(5), sub_graph):
401
+ raise nx.NetworkXException("Bad counter example.")
402
+ elif len(sub_graph) == 6:
403
+ if not nx.is_isomorphic(nx.complete_bipartite_graph(3, 3), sub_graph):
404
+ raise nx.NetworkXException("Bad counter example.")
405
+ else:
406
+ raise nx.NetworkXException("Bad counter example.")
407
+
408
+
409
+ class TestPlanarEmbeddingClass:
410
+ def test_add_half_edge(self):
411
+ embedding = nx.PlanarEmbedding()
412
+ embedding.add_half_edge(0, 1)
413
+ with pytest.raises(
414
+ nx.NetworkXException, match="Invalid clockwise reference node."
415
+ ):
416
+ embedding.add_half_edge(0, 2, cw=3)
417
+ with pytest.raises(
418
+ nx.NetworkXException, match="Invalid counterclockwise reference node."
419
+ ):
420
+ embedding.add_half_edge(0, 2, ccw=3)
421
+ with pytest.raises(
422
+ nx.NetworkXException, match="Only one of cw/ccw can be specified."
423
+ ):
424
+ embedding.add_half_edge(0, 2, cw=1, ccw=1)
425
+ with pytest.raises(
426
+ nx.NetworkXException,
427
+ match=(
428
+ r"Node already has out-half-edge\(s\), either"
429
+ " cw or ccw reference node required."
430
+ ),
431
+ ):
432
+ embedding.add_half_edge(0, 2)
433
+ # these should work
434
+ embedding.add_half_edge(0, 2, cw=1)
435
+ embedding.add_half_edge(0, 3, ccw=1)
436
+ assert sorted(embedding.edges(data=True)) == [
437
+ (0, 1, {"ccw": 2, "cw": 3}),
438
+ (0, 2, {"cw": 1, "ccw": 3}),
439
+ (0, 3, {"cw": 2, "ccw": 1}),
440
+ ]
441
+
442
+ def test_get_data(self):
443
+ embedding = self.get_star_embedding(4)
444
+ data = embedding.get_data()
445
+ data_cmp = {0: [3, 2, 1], 1: [0], 2: [0], 3: [0]}
446
+ assert data == data_cmp
447
+
448
+ def test_edge_removal(self):
449
+ embedding = nx.PlanarEmbedding()
450
+ embedding.set_data(
451
+ {
452
+ 1: [2, 5, 7],
453
+ 2: [1, 3, 4, 5],
454
+ 3: [2, 4],
455
+ 4: [3, 6, 5, 2],
456
+ 5: [7, 1, 2, 4],
457
+ 6: [4, 7],
458
+ 7: [6, 1, 5],
459
+ }
460
+ )
461
+ # remove_edges_from() calls remove_edge(), so both are tested here
462
+ embedding.remove_edges_from(((5, 4), (1, 5)))
463
+ embedding.check_structure()
464
+ embedding_expected = nx.PlanarEmbedding()
465
+ embedding_expected.set_data(
466
+ {
467
+ 1: [2, 7],
468
+ 2: [1, 3, 4, 5],
469
+ 3: [2, 4],
470
+ 4: [3, 6, 2],
471
+ 5: [7, 2],
472
+ 6: [4, 7],
473
+ 7: [6, 1, 5],
474
+ }
475
+ )
476
+ assert nx.utils.graphs_equal(embedding, embedding_expected)
477
+
478
+ def test_missing_edge_orientation(self):
479
+ embedding = nx.PlanarEmbedding({1: {2: {}}, 2: {1: {}}})
480
+ with pytest.raises(nx.NetworkXException):
481
+ # Invalid structure because the orientation of the edge was not set
482
+ embedding.check_structure()
483
+
484
+ def test_invalid_edge_orientation(self):
485
+ embedding = nx.PlanarEmbedding(
486
+ {
487
+ 1: {2: {"cw": 2, "ccw": 2}},
488
+ 2: {1: {"cw": 1, "ccw": 1}},
489
+ 1: {3: {}},
490
+ 3: {1: {}},
491
+ }
492
+ )
493
+ with pytest.raises(nx.NetworkXException):
494
+ embedding.check_structure()
495
+
496
+ def test_missing_half_edge(self):
497
+ embedding = nx.PlanarEmbedding()
498
+ embedding.add_half_edge(1, 2)
499
+ with pytest.raises(nx.NetworkXException):
500
+ # Invalid structure because other half edge is missing
501
+ embedding.check_structure()
502
+
503
+ def test_not_fulfilling_euler_formula(self):
504
+ embedding = nx.PlanarEmbedding()
505
+ for i in range(5):
506
+ ref = None
507
+ for j in range(5):
508
+ if i != j:
509
+ embedding.add_half_edge(i, j, cw=ref)
510
+ ref = j
511
+ with pytest.raises(nx.NetworkXException):
512
+ embedding.check_structure()
513
+
514
+ def test_missing_reference(self):
515
+ embedding = nx.PlanarEmbedding()
516
+ with pytest.raises(nx.NetworkXException, match="Invalid reference node."):
517
+ embedding.add_half_edge(1, 2, ccw=3)
518
+
519
+ def test_connect_components(self):
520
+ embedding = nx.PlanarEmbedding()
521
+ embedding.connect_components(1, 2)
522
+
523
+ def test_successful_face_traversal(self):
524
+ embedding = nx.PlanarEmbedding()
525
+ embedding.add_half_edge(1, 2)
526
+ embedding.add_half_edge(2, 1)
527
+ face = embedding.traverse_face(1, 2)
528
+ assert face == [1, 2]
529
+
530
+ def test_unsuccessful_face_traversal(self):
531
+ embedding = nx.PlanarEmbedding(
532
+ {1: {2: {"cw": 3, "ccw": 2}}, 2: {1: {"cw": 3, "ccw": 1}}}
533
+ )
534
+ with pytest.raises(nx.NetworkXException):
535
+ embedding.traverse_face(1, 2)
536
+
537
+ def test_forbidden_methods(self):
538
+ embedding = nx.PlanarEmbedding()
539
+ embedding.add_node(42) # no exception
540
+ embedding.add_nodes_from([(23, 24)]) # no exception
541
+ with pytest.raises(NotImplementedError):
542
+ embedding.add_edge(1, 3)
543
+ with pytest.raises(NotImplementedError):
544
+ embedding.add_edges_from([(0, 2), (1, 4)])
545
+ with pytest.raises(NotImplementedError):
546
+ embedding.add_weighted_edges_from([(0, 2, 350), (1, 4, 125)])
547
+
548
+ @staticmethod
549
+ def get_star_embedding(n):
550
+ embedding = nx.PlanarEmbedding()
551
+ ref = None
552
+ for i in range(1, n):
553
+ embedding.add_half_edge(0, i, cw=ref)
554
+ ref = i
555
+ embedding.add_half_edge(i, 0)
556
+ return embedding