ZhengyangZhang commited on
Commit
abc9335
·
verified ·
1 Parent(s): 2d65357

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. lib/python3.12/site-packages/blinker-1.9.0.dist-info/INSTALLER +1 -0
  2. lib/python3.12/site-packages/blinker-1.9.0.dist-info/LICENSE.txt +20 -0
  3. lib/python3.12/site-packages/blinker-1.9.0.dist-info/METADATA +60 -0
  4. lib/python3.12/site-packages/blinker-1.9.0.dist-info/RECORD +12 -0
  5. lib/python3.12/site-packages/blinker-1.9.0.dist-info/WHEEL +4 -0
  6. lib/python3.12/site-packages/executing-2.2.1.dist-info/INSTALLER +1 -0
  7. lib/python3.12/site-packages/executing-2.2.1.dist-info/LICENSE.txt +21 -0
  8. lib/python3.12/site-packages/executing-2.2.1.dist-info/METADATA +171 -0
  9. lib/python3.12/site-packages/executing-2.2.1.dist-info/RECORD +21 -0
  10. lib/python3.12/site-packages/executing-2.2.1.dist-info/WHEEL +6 -0
  11. lib/python3.12/site-packages/executing-2.2.1.dist-info/top_level.txt +1 -0
  12. lib/python3.12/site-packages/networkx/__init__.py +62 -0
  13. lib/python3.12/site-packages/networkx/__pycache__/__init__.cpython-312.pyc +0 -0
  14. lib/python3.12/site-packages/networkx/__pycache__/conftest.cpython-312.pyc +0 -0
  15. lib/python3.12/site-packages/networkx/__pycache__/convert.cpython-312.pyc +0 -0
  16. lib/python3.12/site-packages/networkx/__pycache__/convert_matrix.cpython-312.pyc +0 -0
  17. lib/python3.12/site-packages/networkx/__pycache__/exception.cpython-312.pyc +0 -0
  18. lib/python3.12/site-packages/networkx/__pycache__/lazy_imports.cpython-312.pyc +0 -0
  19. lib/python3.12/site-packages/networkx/__pycache__/relabel.cpython-312.pyc +0 -0
  20. lib/python3.12/site-packages/networkx/classes/__init__.py +13 -0
  21. lib/python3.12/site-packages/networkx/classes/__pycache__/__init__.cpython-312.pyc +0 -0
  22. lib/python3.12/site-packages/networkx/classes/__pycache__/coreviews.cpython-312.pyc +0 -0
  23. lib/python3.12/site-packages/networkx/classes/__pycache__/digraph.cpython-312.pyc +0 -0
  24. lib/python3.12/site-packages/networkx/classes/__pycache__/filters.cpython-312.pyc +0 -0
  25. lib/python3.12/site-packages/networkx/classes/__pycache__/function.cpython-312.pyc +0 -0
  26. lib/python3.12/site-packages/networkx/classes/__pycache__/graph.cpython-312.pyc +0 -0
  27. lib/python3.12/site-packages/networkx/classes/__pycache__/graphviews.cpython-312.pyc +0 -0
  28. lib/python3.12/site-packages/networkx/classes/__pycache__/multidigraph.cpython-312.pyc +0 -0
  29. lib/python3.12/site-packages/networkx/classes/__pycache__/multigraph.cpython-312.pyc +0 -0
  30. lib/python3.12/site-packages/networkx/classes/__pycache__/reportviews.cpython-312.pyc +0 -0
  31. lib/python3.12/site-packages/networkx/classes/coreviews.py +435 -0
  32. lib/python3.12/site-packages/networkx/classes/digraph.py +1363 -0
  33. lib/python3.12/site-packages/networkx/classes/filters.py +95 -0
  34. lib/python3.12/site-packages/networkx/classes/function.py +1549 -0
  35. lib/python3.12/site-packages/networkx/classes/graph.py +2082 -0
  36. lib/python3.12/site-packages/networkx/classes/graphviews.py +269 -0
  37. lib/python3.12/site-packages/networkx/classes/multidigraph.py +977 -0
  38. lib/python3.12/site-packages/networkx/classes/multigraph.py +1294 -0
  39. lib/python3.12/site-packages/networkx/classes/reportviews.py +1447 -0
  40. lib/python3.12/site-packages/networkx/classes/tests/__init__.py +0 -0
  41. lib/python3.12/site-packages/networkx/classes/tests/__pycache__/__init__.cpython-312.pyc +0 -0
  42. lib/python3.12/site-packages/networkx/classes/tests/__pycache__/dispatch_interface.cpython-312.pyc +0 -0
  43. lib/python3.12/site-packages/networkx/classes/tests/__pycache__/historical_tests.cpython-312.pyc +0 -0
  44. lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_coreviews.cpython-312.pyc +0 -0
  45. lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_digraph.cpython-312.pyc +0 -0
  46. lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_digraph_historical.cpython-312.pyc +0 -0
  47. lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_filters.cpython-312.pyc +0 -0
  48. lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_function.cpython-312.pyc +0 -0
  49. lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_graph.cpython-312.pyc +0 -0
  50. lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_graph_historical.cpython-312.pyc +0 -0
lib/python3.12/site-packages/blinker-1.9.0.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
lib/python3.12/site-packages/blinker-1.9.0.dist-info/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright 2010 Jason Kirtland
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a
4
+ copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included
12
+ in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
lib/python3.12/site-packages/blinker-1.9.0.dist-info/METADATA ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.3
2
+ Name: blinker
3
+ Version: 1.9.0
4
+ Summary: Fast, simple object-to-object and broadcast signaling
5
+ Author: Jason Kirtland
6
+ Maintainer-email: Pallets Ecosystem <contact@palletsprojects.com>
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Typing :: Typed
13
+ Project-URL: Chat, https://discord.gg/pallets
14
+ Project-URL: Documentation, https://blinker.readthedocs.io
15
+ Project-URL: Source, https://github.com/pallets-eco/blinker/
16
+
17
+ # Blinker
18
+
19
+ Blinker provides a fast dispatching system that allows any number of
20
+ interested parties to subscribe to events, or "signals".
21
+
22
+
23
+ ## Pallets Community Ecosystem
24
+
25
+ > [!IMPORTANT]\
26
+ > This project is part of the Pallets Community Ecosystem. Pallets is the open
27
+ > source organization that maintains Flask; Pallets-Eco enables community
28
+ > maintenance of related projects. If you are interested in helping maintain
29
+ > this project, please reach out on [the Pallets Discord server][discord].
30
+ >
31
+ > [discord]: https://discord.gg/pallets
32
+
33
+
34
+ ## Example
35
+
36
+ Signal receivers can subscribe to specific senders or receive signals
37
+ sent by any sender.
38
+
39
+ ```pycon
40
+ >>> from blinker import signal
41
+ >>> started = signal('round-started')
42
+ >>> def each(round):
43
+ ... print(f"Round {round}")
44
+ ...
45
+ >>> started.connect(each)
46
+
47
+ >>> def round_two(round):
48
+ ... print("This is round two.")
49
+ ...
50
+ >>> started.connect(round_two, sender=2)
51
+
52
+ >>> for round in range(1, 4):
53
+ ... started.send(round)
54
+ ...
55
+ Round 1!
56
+ Round 2!
57
+ This is round two.
58
+ Round 3!
59
+ ```
60
+
lib/python3.12/site-packages/blinker-1.9.0.dist-info/RECORD ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ blinker-1.9.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ blinker-1.9.0.dist-info/LICENSE.txt,sha256=nrc6HzhZekqhcCXSrhvjg5Ykx5XphdTw6Xac4p-spGc,1054
3
+ blinker-1.9.0.dist-info/METADATA,sha256=uIRiM8wjjbHkCtbCyTvctU37IAZk0kEe5kxAld1dvzA,1633
4
+ blinker-1.9.0.dist-info/RECORD,,
5
+ blinker-1.9.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
6
+ blinker/__init__.py,sha256=I2EdZqpy4LyjX17Hn1yzJGWCjeLaVaPzsMgHkLfj_cQ,317
7
+ blinker/__pycache__/__init__.cpython-312.pyc,,
8
+ blinker/__pycache__/_utilities.cpython-312.pyc,,
9
+ blinker/__pycache__/base.cpython-312.pyc,,
10
+ blinker/_utilities.py,sha256=0J7eeXXTUx0Ivf8asfpx0ycVkp0Eqfqnj117x2mYX9E,1675
11
+ blinker/base.py,sha256=QpDuvXXcwJF49lUBcH5BiST46Rz9wSG7VW_p7N_027M,19132
12
+ blinker/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
lib/python3.12/site-packages/blinker-1.9.0.dist-info/WHEEL ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: flit 3.10.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
lib/python3.12/site-packages/executing-2.2.1.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
lib/python3.12/site-packages/executing-2.2.1.dist-info/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Alex Hall
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
lib/python3.12/site-packages/executing-2.2.1.dist-info/METADATA ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.1
2
+ Name: executing
3
+ Version: 2.2.1
4
+ Summary: Get the currently executing AST node of a frame, and other information
5
+ Home-page: https://github.com/alexmojaki/executing
6
+ Author: Alex Hall
7
+ Author-email: alex.mojaki@gmail.com
8
+ License: MIT
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE.txt
22
+ Provides-Extra: tests
23
+ Requires-Dist: asttokens>=2.1.0; extra == "tests"
24
+ Requires-Dist: ipython; extra == "tests"
25
+ Requires-Dist: pytest; extra == "tests"
26
+ Requires-Dist: coverage; extra == "tests"
27
+ Requires-Dist: coverage-enable-subprocess; extra == "tests"
28
+ Requires-Dist: littleutils; extra == "tests"
29
+ Requires-Dist: rich; python_version >= "3.11" and extra == "tests"
30
+
31
+ # executing
32
+
33
+ [![Build Status](https://github.com/alexmojaki/executing/workflows/Tests/badge.svg?branch=master)](https://github.com/alexmojaki/executing/actions) [![Coverage Status](https://coveralls.io/repos/github/alexmojaki/executing/badge.svg?branch=master)](https://coveralls.io/github/alexmojaki/executing?branch=master) [![Supports Python versions 3.5+, including PyPy](https://img.shields.io/pypi/pyversions/executing.svg)](https://pypi.python.org/pypi/executing)
34
+
35
+ This mini-package lets you get information about what a frame is currently doing, particularly the AST node being executed.
36
+
37
+ * [Usage](#usage)
38
+ * [Getting the AST node](#getting-the-ast-node)
39
+ * [Getting the source code of the node](#getting-the-source-code-of-the-node)
40
+ * [Getting the `__qualname__` of the current function](#getting-the-__qualname__-of-the-current-function)
41
+ * [The Source class](#the-source-class)
42
+ * [Installation](#installation)
43
+ * [How does it work?](#how-does-it-work)
44
+ * [Is it reliable?](#is-it-reliable)
45
+ * [Which nodes can it identify?](#which-nodes-can-it-identify)
46
+ * [Projects that use this](#projects-that-use-this)
47
+
48
+ ## Usage
49
+
50
+ ### Getting the AST node
51
+
52
+ ```python
53
+ import executing
54
+
55
+ node = executing.Source.executing(frame).node
56
+ ```
57
+
58
+ Then `node` will be an AST node (from the `ast` standard library module) or None if the node couldn't be identified (which may happen often and should always be checked).
59
+
60
+ `node` will always be the same instance for multiple calls with frames at the same point of execution.
61
+
62
+ If you have a traceback object, pass it directly to `Source.executing()` rather than the `tb_frame` attribute to get the correct node.
63
+
64
+ ### Getting the source code of the node
65
+
66
+ For this you will need to separately install the [`asttokens`](https://github.com/gristlabs/asttokens) library, then obtain an `ASTTokens` object:
67
+
68
+ ```python
69
+ executing.Source.executing(frame).source.asttokens()
70
+ ```
71
+
72
+ or:
73
+
74
+ ```python
75
+ executing.Source.for_frame(frame).asttokens()
76
+ ```
77
+
78
+ or use one of the convenience methods:
79
+
80
+ ```python
81
+ executing.Source.executing(frame).text()
82
+ executing.Source.executing(frame).text_range()
83
+ ```
84
+
85
+ ### Getting the `__qualname__` of the current function
86
+
87
+ ```python
88
+ executing.Source.executing(frame).code_qualname()
89
+ ```
90
+
91
+ or:
92
+
93
+ ```python
94
+ executing.Source.for_frame(frame).code_qualname(frame.f_code)
95
+ ```
96
+
97
+ ### The `Source` class
98
+
99
+ Everything goes through the `Source` class. Only one instance of the class is created for each filename. Subclassing it to add more attributes on creation or methods is recommended. The classmethods such as `executing` will respect this. See the source code and docstrings for more detail.
100
+
101
+ ## Installation
102
+
103
+ pip install executing
104
+
105
+ If you don't like that you can just copy the file `executing.py`, there are no dependencies (but of course you won't get updates).
106
+
107
+ ## How does it work?
108
+
109
+ Suppose the frame is executing this line:
110
+
111
+ ```python
112
+ self.foo(bar.x)
113
+ ```
114
+
115
+ and in particular it's currently obtaining the attribute `self.foo`. Looking at the bytecode, specifically `frame.f_code.co_code[frame.f_lasti]`, we can tell that it's loading an attribute, but it's not obvious which one. We can narrow down the statement being executed using `frame.f_lineno` and find the two `ast.Attribute` nodes representing `self.foo` and `bar.x`. How do we find out which one it is, without recreating the entire compiler in Python?
116
+
117
+ The trick is to modify the AST slightly for each candidate expression and observe the changes in the bytecode instructions. We change the AST to this:
118
+
119
+ ```python
120
+ (self.foo ** 'longuniqueconstant')(bar.x)
121
+ ```
122
+
123
+ and compile it, and the bytecode will be almost the same but there will be two new instructions:
124
+
125
+ LOAD_CONST 'longuniqueconstant'
126
+ BINARY_POWER
127
+
128
+ and just before that will be a `LOAD_ATTR` instruction corresponding to `self.foo`. Seeing that it's in the same position as the original instruction lets us know we've found our match.
129
+
130
+ ## Is it reliable?
131
+
132
+ Yes - if it identifies a node, you can trust that it's identified the correct one. The tests are very thorough - in addition to unit tests which check various situations directly, there are property tests against a large number of files (see the filenames printed in [this build](https://travis-ci.org/alexmojaki/executing/jobs/557970457)) with real code. Specifically, for each file, the tests:
133
+
134
+ 1. Identify as many nodes as possible from all the bytecode instructions in the file, and assert that they are all distinct
135
+ 2. Find all the nodes that should be identifiable, and assert that they were indeed identified somewhere
136
+
137
+ In other words, it shows that there is a one-to-one mapping between the nodes and the instructions that can be handled. This leaves very little room for a bug to creep in.
138
+
139
+ Furthermore, `executing` checks that the instructions compiled from the modified AST exactly match the original code save for a few small known exceptions. This accounts for all the quirks and optimisations in the interpreter.
140
+
141
+ ## Which nodes can it identify?
142
+
143
+ Currently it works in almost all cases for the following `ast` nodes:
144
+
145
+ - `Call`, e.g. `self.foo(bar)`
146
+ - `Attribute`, e.g. `point.x`
147
+ - `Subscript`, e.g. `lst[1]`
148
+ - `BinOp`, e.g. `x + y` (doesn't include `and` and `or`)
149
+ - `UnaryOp`, e.g. `-n` (includes `not` but only works sometimes)
150
+ - `Compare` e.g. `a < b` (not for chains such as `0 < p < 1`)
151
+
152
+ The plan is to extend to more operations in the future.
153
+
154
+ ## Projects that use this
155
+
156
+ ### My Projects
157
+
158
+ - **[`stack_data`](https://github.com/alexmojaki/stack_data)**: Extracts data from stack frames and tracebacks, particularly to display more useful tracebacks than the default. Also uses another related library of mine: **[`pure_eval`](https://github.com/alexmojaki/pure_eval)**.
159
+ - **[`futurecoder`](https://futurecoder.io/)**: Highlights the executing node in tracebacks using `executing` via `stack_data`, and provides debugging with `snoop`.
160
+ - **[`snoop`](https://github.com/alexmojaki/snoop)**: A feature-rich and convenient debugging library. Uses `executing` to show the operation which caused an exception and to allow the `pp` function to display the source of its arguments.
161
+ - **[`heartrate`](https://github.com/alexmojaki/heartrate)**: A simple real time visualisation of the execution of a Python program. Uses `executing` to highlight currently executing operations, particularly in each frame of the stack trace.
162
+ - **[`sorcery`](https://github.com/alexmojaki/sorcery)**: Dark magic delights in Python. Uses `executing` to let special callables called spells know where they're being called from.
163
+
164
+ ### Projects I've contributed to
165
+
166
+ - **[`IPython`](https://github.com/ipython/ipython/pull/12150)**: Highlights the executing node in tracebacks using `executing` via [`stack_data`](https://github.com/alexmojaki/stack_data).
167
+ - **[`icecream`](https://github.com/gruns/icecream)**: 🍦 Sweet and creamy print debugging. Uses `executing` to identify where `ic` is called and print its arguments.
168
+ - **[`friendly_traceback`](https://github.com/friendly-traceback/friendly-traceback)**: Uses `stack_data` and `executing` to pinpoint the cause of errors and provide helpful explanations.
169
+ - **[`python-devtools`](https://github.com/samuelcolvin/python-devtools)**: Uses `executing` for print debugging similar to `icecream`.
170
+ - **[`sentry_sdk`](https://github.com/getsentry/sentry-python)**: Add the integration `sentry_sdk.integrations.executingExecutingIntegration()` to show the function `__qualname__` in each frame in sentry events.
171
+ - **[`varname`](https://github.com/pwwang/python-varname)**: Dark magics about variable names in python. Uses `executing` to find where its various magical functions like `varname` and `nameof` are called from.
lib/python3.12/site-packages/executing-2.2.1.dist-info/RECORD ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ executing-2.2.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ executing-2.2.1.dist-info/LICENSE.txt,sha256=pHaiyw70xBRQNApXeii5GsTH9mkTay7hSAR_q9X8QYE,1066
3
+ executing-2.2.1.dist-info/METADATA,sha256=11htGuIlvuEmxCN-sMTXaZqFgLgetVjMUAwmPSmmM_8,8914
4
+ executing-2.2.1.dist-info/RECORD,,
5
+ executing-2.2.1.dist-info/WHEEL,sha256=Ll72iyqtt6Rbxp-Q7FSafYA1LeRv98X15xcZWRsFEmY,109
6
+ executing-2.2.1.dist-info/top_level.txt,sha256=b9Rtf3NtSqc0_Kak6L_lvnbdKPA0GUim2p-XcFQsf5g,10
7
+ executing/__init__.py,sha256=agdZWnui3FaB1FepFzVWX5ydS0mlUsVeA0zBLMxhvjk,831
8
+ executing/__pycache__/__init__.cpython-312.pyc,,
9
+ executing/__pycache__/_exceptions.cpython-312.pyc,,
10
+ executing/__pycache__/_position_node_finder.cpython-312.pyc,,
11
+ executing/__pycache__/_pytest_utils.cpython-312.pyc,,
12
+ executing/__pycache__/_utils.cpython-312.pyc,,
13
+ executing/__pycache__/executing.cpython-312.pyc,,
14
+ executing/__pycache__/version.cpython-312.pyc,,
15
+ executing/_exceptions.py,sha256=nf5P5jPnSjjo_8YWlh5AOyLZHF_hNyJpDv0OG2XFYgw,568
16
+ executing/_position_node_finder.py,sha256=W1P_MdoZwVILYk7bps13vmhD4K0a9-LBFaTiRe_7s6Q,37681
17
+ executing/_pytest_utils.py,sha256=NRj90nTcExS-8R2P8M1wYm9sodhrTlq74RSd4ZvjQRE,354
18
+ executing/_utils.py,sha256=HYisPx2IaYR-uZUhn7IzQ3jUhqVikPSHjRu06i7VHq4,4119
19
+ executing/executing.py,sha256=lZuc6mmHT1UqiBAFRovBLzJfvLpZSY6F0Pimdk15Lcg,40741
20
+ executing/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ executing/version.py,sha256=86znAaVowM4RwLFp-Ylrkl1sqyqYQo7a8SiNpIIUwPo,21
lib/python3.12/site-packages/executing-2.2.1.dist-info/WHEEL ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.3.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py2-none-any
5
+ Tag: py3-none-any
6
+
lib/python3.12/site-packages/executing-2.2.1.dist-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ executing
lib/python3.12/site-packages/networkx/__init__.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ NetworkX
3
+ ========
4
+
5
+ NetworkX is a Python package for the creation, manipulation, and study of the
6
+ structure, dynamics, and functions of complex networks.
7
+
8
+ See https://networkx.org for complete documentation.
9
+ """
10
+
11
+ __version__ = "3.6.1"
12
+
13
+
14
+ # These are imported in order as listed
15
+ from networkx.lazy_imports import _lazy_import
16
+
17
+ from networkx.exception import *
18
+
19
+ from networkx import utils
20
+ from networkx.utils import _clear_cache, _dispatchable
21
+
22
+ # load_and_call entry_points, set configs
23
+ config = utils.backends._set_configs_from_environment()
24
+ utils.config = utils.configs.config = config # type: ignore[attr-defined]
25
+
26
+ from networkx import classes
27
+ from networkx.classes import filters
28
+ from networkx.classes import *
29
+
30
+ from networkx import convert
31
+ from networkx.convert import *
32
+
33
+ from networkx import convert_matrix
34
+ from networkx.convert_matrix import *
35
+
36
+ from networkx import relabel
37
+ from networkx.relabel import *
38
+
39
+ from networkx import generators
40
+ from networkx.generators import *
41
+
42
+ from networkx import readwrite
43
+ from networkx.readwrite import *
44
+
45
+ # Need to test with SciPy, when available
46
+ from networkx import algorithms
47
+ from networkx.algorithms import *
48
+
49
+ from networkx import linalg
50
+ from networkx.linalg import *
51
+
52
+ from networkx import drawing
53
+ from networkx.drawing import *
54
+
55
+
56
+ def __getattr__(name):
57
+ if name == "random_tree":
58
+ raise AttributeError(
59
+ "nx.random_tree was removed in version 3.4. Use `nx.random_labeled_tree` instead.\n"
60
+ "See: https://networkx.org/documentation/latest/release/release_3.4.html"
61
+ )
62
+ raise AttributeError(f"module 'networkx' has no attribute '{name}'")
lib/python3.12/site-packages/networkx/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (1.92 kB). View file
 
lib/python3.12/site-packages/networkx/__pycache__/conftest.cpython-312.pyc ADDED
Binary file (8.53 kB). View file
 
lib/python3.12/site-packages/networkx/__pycache__/convert.cpython-312.pyc ADDED
Binary file (18.8 kB). View file
 
lib/python3.12/site-packages/networkx/__pycache__/convert_matrix.cpython-312.pyc ADDED
Binary file (50.8 kB). View file
 
lib/python3.12/site-packages/networkx/__pycache__/exception.cpython-312.pyc ADDED
Binary file (5.4 kB). View file
 
lib/python3.12/site-packages/networkx/__pycache__/lazy_imports.cpython-312.pyc ADDED
Binary file (7.33 kB). View file
 
lib/python3.12/site-packages/networkx/__pycache__/relabel.cpython-312.pyc ADDED
Binary file (13.3 kB). View file
 
lib/python3.12/site-packages/networkx/classes/__init__.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .graph import Graph
2
+ from .digraph import DiGraph
3
+ from .multigraph import MultiGraph
4
+ from .multidigraph import MultiDiGraph
5
+
6
+ from .function import *
7
+ from .graphviews import subgraph_view, reverse_view
8
+
9
+ from networkx.classes import filters
10
+
11
+ from networkx.classes import coreviews
12
+ from networkx.classes import graphviews
13
+ from networkx.classes import reportviews
lib/python3.12/site-packages/networkx/classes/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (613 Bytes). View file
 
lib/python3.12/site-packages/networkx/classes/__pycache__/coreviews.cpython-312.pyc ADDED
Binary file (23.3 kB). View file
 
lib/python3.12/site-packages/networkx/classes/__pycache__/digraph.cpython-312.pyc ADDED
Binary file (54.4 kB). View file
 
lib/python3.12/site-packages/networkx/classes/__pycache__/filters.cpython-312.pyc ADDED
Binary file (5.53 kB). View file
 
lib/python3.12/site-packages/networkx/classes/__pycache__/function.cpython-312.pyc ADDED
Binary file (54.8 kB). View file
 
lib/python3.12/site-packages/networkx/classes/__pycache__/graph.cpython-312.pyc ADDED
Binary file (79.7 kB). View file
 
lib/python3.12/site-packages/networkx/classes/__pycache__/graphviews.cpython-312.pyc ADDED
Binary file (9.69 kB). View file
 
lib/python3.12/site-packages/networkx/classes/__pycache__/multidigraph.cpython-312.pyc ADDED
Binary file (40.5 kB). View file
 
lib/python3.12/site-packages/networkx/classes/__pycache__/multigraph.cpython-312.pyc ADDED
Binary file (52.1 kB). View file
 
lib/python3.12/site-packages/networkx/classes/__pycache__/reportviews.cpython-312.pyc ADDED
Binary file (71.6 kB). View file
 
lib/python3.12/site-packages/networkx/classes/coreviews.py ADDED
@@ -0,0 +1,435 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Views of core data structures such as nested Mappings (e.g. dict-of-dicts).
2
+ These ``Views`` often restrict element access, with either the entire view or
3
+ layers of nested mappings being read-only.
4
+ """
5
+
6
+ from collections.abc import Mapping
7
+
8
+ __all__ = [
9
+ "AtlasView",
10
+ "AdjacencyView",
11
+ "MultiAdjacencyView",
12
+ "UnionAtlas",
13
+ "UnionAdjacency",
14
+ "UnionMultiInner",
15
+ "UnionMultiAdjacency",
16
+ "FilterAtlas",
17
+ "FilterAdjacency",
18
+ "FilterMultiInner",
19
+ "FilterMultiAdjacency",
20
+ ]
21
+
22
+
23
+ class AtlasView(Mapping):
24
+ """An AtlasView is a Read-only Mapping of Mappings.
25
+
26
+ It is a View into a dict-of-dict data structure.
27
+ The inner level of dict is read-write. But the
28
+ outer level is read-only.
29
+
30
+ See Also
31
+ ========
32
+ AdjacencyView: View into dict-of-dict-of-dict
33
+ MultiAdjacencyView: View into dict-of-dict-of-dict-of-dict
34
+ """
35
+
36
+ __slots__ = ("_atlas",)
37
+
38
+ def __getstate__(self):
39
+ return {"_atlas": self._atlas}
40
+
41
+ def __setstate__(self, state):
42
+ self._atlas = state["_atlas"]
43
+
44
+ def __init__(self, d):
45
+ self._atlas = d
46
+
47
+ def __len__(self):
48
+ return len(self._atlas)
49
+
50
+ def __iter__(self):
51
+ return iter(self._atlas)
52
+
53
+ def __getitem__(self, key):
54
+ return self._atlas[key]
55
+
56
+ def copy(self):
57
+ return {n: self[n].copy() for n in self._atlas}
58
+
59
+ def __str__(self):
60
+ return str(self._atlas) # {nbr: self[nbr] for nbr in self})
61
+
62
+ def __repr__(self):
63
+ return f"{self.__class__.__name__}({self._atlas!r})"
64
+
65
+
66
+ class AdjacencyView(AtlasView):
67
+ """An AdjacencyView is a Read-only Map of Maps of Maps.
68
+
69
+ It is a View into a dict-of-dict-of-dict data structure.
70
+ The inner level of dict is read-write. But the
71
+ outer levels are read-only.
72
+
73
+ See Also
74
+ ========
75
+ AtlasView: View into dict-of-dict
76
+ MultiAdjacencyView: View into dict-of-dict-of-dict-of-dict
77
+ """
78
+
79
+ __slots__ = () # Still uses AtlasView slots names _atlas
80
+
81
+ def __getitem__(self, name):
82
+ return AtlasView(self._atlas[name])
83
+
84
+ def copy(self):
85
+ return {n: self[n].copy() for n in self._atlas}
86
+
87
+
88
+ class MultiAdjacencyView(AdjacencyView):
89
+ """An MultiAdjacencyView is a Read-only Map of Maps of Maps of Maps.
90
+
91
+ It is a View into a dict-of-dict-of-dict-of-dict data structure.
92
+ The inner level of dict is read-write. But the
93
+ outer levels are read-only.
94
+
95
+ See Also
96
+ ========
97
+ AtlasView: View into dict-of-dict
98
+ AdjacencyView: View into dict-of-dict-of-dict
99
+ """
100
+
101
+ __slots__ = () # Still uses AtlasView slots names _atlas
102
+
103
+ def __getitem__(self, name):
104
+ return AdjacencyView(self._atlas[name])
105
+
106
+ def copy(self):
107
+ return {n: self[n].copy() for n in self._atlas}
108
+
109
+
110
+ class UnionAtlas(Mapping):
111
+ """A read-only union of two atlases (dict-of-dict).
112
+
113
+ The two dict-of-dicts represent the inner dict of
114
+ an Adjacency: `G.succ[node]` and `G.pred[node]`.
115
+ The inner level of dict of both hold attribute key:value
116
+ pairs and is read-write. But the outer level is read-only.
117
+
118
+ See Also
119
+ ========
120
+ UnionAdjacency: View into dict-of-dict-of-dict
121
+ UnionMultiAdjacency: View into dict-of-dict-of-dict-of-dict
122
+ """
123
+
124
+ __slots__ = ("_succ", "_pred")
125
+
126
+ def __getstate__(self):
127
+ return {"_succ": self._succ, "_pred": self._pred}
128
+
129
+ def __setstate__(self, state):
130
+ self._succ = state["_succ"]
131
+ self._pred = state["_pred"]
132
+
133
+ def __init__(self, succ, pred):
134
+ self._succ = succ
135
+ self._pred = pred
136
+
137
+ def __len__(self):
138
+ return len(self._succ.keys() | self._pred.keys())
139
+
140
+ def __iter__(self):
141
+ return iter(set(self._succ.keys()) | set(self._pred.keys()))
142
+
143
+ def __getitem__(self, key):
144
+ try:
145
+ return self._succ[key]
146
+ except KeyError:
147
+ return self._pred[key]
148
+
149
+ def copy(self):
150
+ result = {nbr: dd.copy() for nbr, dd in self._succ.items()}
151
+ for nbr, dd in self._pred.items():
152
+ if nbr in result:
153
+ result[nbr].update(dd)
154
+ else:
155
+ result[nbr] = dd.copy()
156
+ return result
157
+
158
+ def __str__(self):
159
+ return str({nbr: self[nbr] for nbr in self})
160
+
161
+ def __repr__(self):
162
+ return f"{self.__class__.__name__}({self._succ!r}, {self._pred!r})"
163
+
164
+
165
+ class UnionAdjacency(Mapping):
166
+ """A read-only union of dict Adjacencies as a Map of Maps of Maps.
167
+
168
+ The two input dict-of-dict-of-dicts represent the union of
169
+ `G.succ` and `G.pred`. Return values are UnionAtlas
170
+ The inner level of dict is read-write. But the
171
+ middle and outer levels are read-only.
172
+
173
+ succ : a dict-of-dict-of-dict {node: nbrdict}
174
+ pred : a dict-of-dict-of-dict {node: nbrdict}
175
+ The keys for the two dicts should be the same
176
+
177
+ See Also
178
+ ========
179
+ UnionAtlas: View into dict-of-dict
180
+ UnionMultiAdjacency: View into dict-of-dict-of-dict-of-dict
181
+ """
182
+
183
+ __slots__ = ("_succ", "_pred")
184
+
185
+ def __getstate__(self):
186
+ return {"_succ": self._succ, "_pred": self._pred}
187
+
188
+ def __setstate__(self, state):
189
+ self._succ = state["_succ"]
190
+ self._pred = state["_pred"]
191
+
192
+ def __init__(self, succ, pred):
193
+ # keys must be the same for two input dicts
194
+ assert len(set(succ.keys()) ^ set(pred.keys())) == 0
195
+ self._succ = succ
196
+ self._pred = pred
197
+
198
+ def __len__(self):
199
+ return len(self._succ) # length of each dict should be the same
200
+
201
+ def __iter__(self):
202
+ return iter(self._succ)
203
+
204
+ def __getitem__(self, nbr):
205
+ return UnionAtlas(self._succ[nbr], self._pred[nbr])
206
+
207
+ def copy(self):
208
+ return {n: self[n].copy() for n in self._succ}
209
+
210
+ def __str__(self):
211
+ return str({nbr: self[nbr] for nbr in self})
212
+
213
+ def __repr__(self):
214
+ return f"{self.__class__.__name__}({self._succ!r}, {self._pred!r})"
215
+
216
+
217
+ class UnionMultiInner(UnionAtlas):
218
+ """A read-only union of two inner dicts of MultiAdjacencies.
219
+
220
+ The two input dict-of-dict-of-dicts represent the union of
221
+ `G.succ[node]` and `G.pred[node]` for MultiDiGraphs.
222
+ Return values are UnionAtlas.
223
+ The inner level of dict is read-write. But the outer levels are read-only.
224
+
225
+ See Also
226
+ ========
227
+ UnionAtlas: View into dict-of-dict
228
+ UnionAdjacency: View into dict-of-dict-of-dict
229
+ UnionMultiAdjacency: View into dict-of-dict-of-dict-of-dict
230
+ """
231
+
232
+ __slots__ = () # Still uses UnionAtlas slots names _succ, _pred
233
+
234
+ def __getitem__(self, node):
235
+ in_succ = node in self._succ
236
+ in_pred = node in self._pred
237
+ if in_succ:
238
+ if in_pred:
239
+ return UnionAtlas(self._succ[node], self._pred[node])
240
+ return UnionAtlas(self._succ[node], {})
241
+ return UnionAtlas({}, self._pred[node])
242
+
243
+ def copy(self):
244
+ nodes = set(self._succ.keys()) | set(self._pred.keys())
245
+ return {n: self[n].copy() for n in nodes}
246
+
247
+
248
+ class UnionMultiAdjacency(UnionAdjacency):
249
+ """A read-only union of two dict MultiAdjacencies.
250
+
251
+ The two input dict-of-dict-of-dict-of-dicts represent the union of
252
+ `G.succ` and `G.pred` for MultiDiGraphs. Return values are UnionAdjacency.
253
+ The inner level of dict is read-write. But the outer levels are read-only.
254
+
255
+ See Also
256
+ ========
257
+ UnionAtlas: View into dict-of-dict
258
+ UnionMultiInner: View into dict-of-dict-of-dict
259
+ """
260
+
261
+ __slots__ = () # Still uses UnionAdjacency slots names _succ, _pred
262
+
263
+ def __getitem__(self, node):
264
+ return UnionMultiInner(self._succ[node], self._pred[node])
265
+
266
+
267
+ class FilterAtlas(Mapping): # nodedict, nbrdict, keydict
268
+ """A read-only Mapping of Mappings with filtering criteria for nodes.
269
+
270
+ It is a view into a dict-of-dict data structure, and it selects only
271
+ nodes that meet the criteria defined by ``NODE_OK``.
272
+
273
+ See Also
274
+ ========
275
+ FilterAdjacency
276
+ FilterMultiInner
277
+ FilterMultiAdjacency
278
+ """
279
+
280
+ def __init__(self, d, NODE_OK):
281
+ self._atlas = d
282
+ self.NODE_OK = NODE_OK
283
+
284
+ def __len__(self):
285
+ # check whether NODE_OK stores the number of nodes as `length`
286
+ # or the nodes themselves as a set `nodes`. If not, count the nodes.
287
+ if hasattr(self.NODE_OK, "length"):
288
+ return self.NODE_OK.length
289
+ if hasattr(self.NODE_OK, "nodes"):
290
+ return len(self.NODE_OK.nodes & self._atlas.keys())
291
+ return sum(1 for n in self._atlas if self.NODE_OK(n))
292
+
293
+ def __iter__(self):
294
+ try: # check that NODE_OK has attr 'nodes'
295
+ node_ok_shorter = 2 * len(self.NODE_OK.nodes) < len(self._atlas)
296
+ except AttributeError:
297
+ node_ok_shorter = False
298
+ if node_ok_shorter:
299
+ return (n for n in self.NODE_OK.nodes if n in self._atlas)
300
+ return (n for n in self._atlas if self.NODE_OK(n))
301
+
302
+ def __getitem__(self, key):
303
+ if key in self._atlas and self.NODE_OK(key):
304
+ return self._atlas[key]
305
+ raise KeyError(f"Key {key} not found")
306
+
307
+ def __str__(self):
308
+ return str({nbr: self[nbr] for nbr in self})
309
+
310
+ def __repr__(self):
311
+ return f"{self.__class__.__name__}({self._atlas!r}, {self.NODE_OK!r})"
312
+
313
+
314
+ class FilterAdjacency(Mapping): # edgedict
315
+ """A read-only Mapping of Mappings with filtering criteria for nodes and edges.
316
+
317
+ It is a view into a dict-of-dict-of-dict data structure, and it selects nodes
318
+ and edges that satisfy specific criteria defined by ``NODE_OK`` and ``EDGE_OK``,
319
+ respectively.
320
+
321
+ See Also
322
+ ========
323
+ FilterAtlas
324
+ FilterMultiInner
325
+ FilterMultiAdjacency
326
+ """
327
+
328
+ def __init__(self, d, NODE_OK, EDGE_OK):
329
+ self._atlas = d
330
+ self.NODE_OK = NODE_OK
331
+ self.EDGE_OK = EDGE_OK
332
+
333
+ def __len__(self):
334
+ # check whether NODE_OK stores the number of nodes as `length`
335
+ # or the nodes themselves as a set `nodes`. If not, count the nodes.
336
+ if hasattr(self.NODE_OK, "length"):
337
+ return self.NODE_OK.length
338
+ if hasattr(self.NODE_OK, "nodes"):
339
+ return len(self.NODE_OK.nodes & self._atlas.keys())
340
+ return sum(1 for n in self._atlas if self.NODE_OK(n))
341
+
342
+ def __iter__(self):
343
+ try: # check that NODE_OK has attr 'nodes'
344
+ node_ok_shorter = 2 * len(self.NODE_OK.nodes) < len(self._atlas)
345
+ except AttributeError:
346
+ node_ok_shorter = False
347
+ if node_ok_shorter:
348
+ return (n for n in self.NODE_OK.nodes if n in self._atlas)
349
+ return (n for n in self._atlas if self.NODE_OK(n))
350
+
351
+ def __getitem__(self, node):
352
+ if node in self._atlas and self.NODE_OK(node):
353
+
354
+ def new_node_ok(nbr):
355
+ return self.NODE_OK(nbr) and self.EDGE_OK(node, nbr)
356
+
357
+ return FilterAtlas(self._atlas[node], new_node_ok)
358
+ raise KeyError(f"Key {node} not found")
359
+
360
+ def __str__(self):
361
+ return str({nbr: self[nbr] for nbr in self})
362
+
363
+ def __repr__(self):
364
+ name = self.__class__.__name__
365
+ return f"{name}({self._atlas!r}, {self.NODE_OK!r}, {self.EDGE_OK!r})"
366
+
367
+
368
+ class FilterMultiInner(FilterAdjacency): # muliedge_seconddict
369
+ """A read-only Mapping of Mappings with filtering criteria for nodes and edges.
370
+
371
+ It is a view into a dict-of-dict-of-dict-of-dict data structure, and it selects nodes
372
+ and edges that meet specific criteria defined by ``NODE_OK`` and ``EDGE_OK``.
373
+
374
+ See Also
375
+ ========
376
+ FilterAtlas
377
+ FilterAdjacency
378
+ FilterMultiAdjacency
379
+ """
380
+
381
+ def __iter__(self):
382
+ try: # check that NODE_OK has attr 'nodes'
383
+ node_ok_shorter = 2 * len(self.NODE_OK.nodes) < len(self._atlas)
384
+ except AttributeError:
385
+ node_ok_shorter = False
386
+ if node_ok_shorter:
387
+ my_nodes = (n for n in self.NODE_OK.nodes if n in self._atlas)
388
+ else:
389
+ my_nodes = (n for n in self._atlas if self.NODE_OK(n))
390
+ for n in my_nodes:
391
+ some_keys_ok = False
392
+ for key in self._atlas[n]:
393
+ if self.EDGE_OK(n, key):
394
+ some_keys_ok = True
395
+ break
396
+ if some_keys_ok is True:
397
+ yield n
398
+
399
+ def __getitem__(self, nbr):
400
+ if (
401
+ nbr in self._atlas
402
+ and self.NODE_OK(nbr)
403
+ and any(self.EDGE_OK(nbr, key) for key in self._atlas[nbr])
404
+ ):
405
+
406
+ def new_node_ok(key):
407
+ return self.EDGE_OK(nbr, key)
408
+
409
+ return FilterAtlas(self._atlas[nbr], new_node_ok)
410
+ raise KeyError(f"Key {nbr} not found")
411
+
412
+
413
+ class FilterMultiAdjacency(FilterAdjacency): # multiedgedict
414
+ """A read-only Mapping of Mappings with filtering criteria
415
+ for nodes and edges.
416
+
417
+ It is a view into a dict-of-dict-of-dict-of-dict data structure,
418
+ and it selects nodes and edges that satisfy specific criteria
419
+ defined by ``NODE_OK`` and ``EDGE_OK``, respectively.
420
+
421
+ See Also
422
+ ========
423
+ FilterAtlas
424
+ FilterAdjacency
425
+ FilterMultiInner
426
+ """
427
+
428
+ def __getitem__(self, node):
429
+ if node in self._atlas and self.NODE_OK(node):
430
+
431
+ def edge_ok(nbr, key):
432
+ return self.NODE_OK(nbr) and self.EDGE_OK(node, nbr, key)
433
+
434
+ return FilterMultiInner(self._atlas[node], self.NODE_OK, edge_ok)
435
+ raise KeyError(f"Key {node} not found")
lib/python3.12/site-packages/networkx/classes/digraph.py ADDED
@@ -0,0 +1,1363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Base class for directed graphs."""
2
+
3
+ from copy import deepcopy
4
+ from functools import cached_property
5
+
6
+ import networkx as nx
7
+ from networkx import convert
8
+ from networkx.classes.coreviews import AdjacencyView
9
+ from networkx.classes.graph import Graph
10
+ from networkx.classes.reportviews import (
11
+ DiDegreeView,
12
+ InDegreeView,
13
+ InEdgeView,
14
+ OutDegreeView,
15
+ OutEdgeView,
16
+ )
17
+ from networkx.exception import NetworkXError
18
+
19
+ __all__ = ["DiGraph"]
20
+
21
+
22
+ class _CachedPropertyResetterAdjAndSucc:
23
+ """Data Descriptor class that syncs and resets cached properties adj and succ
24
+
25
+ The cached properties `adj` and `succ` are reset whenever `_adj` or `_succ`
26
+ are set to new objects. In addition, the attributes `_succ` and `_adj`
27
+ are synced so these two names point to the same object.
28
+
29
+ Warning: most of the time, when ``G._adj`` is set, ``G._pred`` should also
30
+ be set to maintain a valid data structure. They share datadicts.
31
+
32
+ This object sits on a class and ensures that any instance of that
33
+ class clears its cached properties "succ" and "adj" whenever the
34
+ underlying instance attributes "_succ" or "_adj" are set to a new object.
35
+ It only affects the set process of the obj._adj and obj._succ attribute.
36
+ All get/del operations act as they normally would.
37
+
38
+ For info on Data Descriptors see: https://docs.python.org/3/howto/descriptor.html
39
+ """
40
+
41
+ def __set__(self, obj, value):
42
+ od = obj.__dict__
43
+ od["_adj"] = value
44
+ od["_succ"] = value
45
+ # reset cached properties
46
+ props = [
47
+ "adj",
48
+ "succ",
49
+ "edges",
50
+ "out_edges",
51
+ "degree",
52
+ "out_degree",
53
+ "in_degree",
54
+ ]
55
+ for prop in props:
56
+ if prop in od:
57
+ del od[prop]
58
+
59
+
60
+ class _CachedPropertyResetterPred:
61
+ """Data Descriptor class for _pred that resets ``pred`` cached_property when needed
62
+
63
+ This assumes that the ``cached_property`` ``G.pred`` should be reset whenever
64
+ ``G._pred`` is set to a new value.
65
+
66
+ Warning: most of the time, when ``G._pred`` is set, ``G._adj`` should also
67
+ be set to maintain a valid data structure. They share datadicts.
68
+
69
+ This object sits on a class and ensures that any instance of that
70
+ class clears its cached property "pred" whenever the underlying
71
+ instance attribute "_pred" is set to a new object. It only affects
72
+ the set process of the obj._pred attribute. All get/del operations
73
+ act as they normally would.
74
+
75
+ For info on Data Descriptors see: https://docs.python.org/3/howto/descriptor.html
76
+ """
77
+
78
+ def __set__(self, obj, value):
79
+ od = obj.__dict__
80
+ od["_pred"] = value
81
+ # reset cached properties
82
+ props = ["pred", "in_edges", "degree", "out_degree", "in_degree"]
83
+ for prop in props:
84
+ if prop in od:
85
+ del od[prop]
86
+
87
+
88
+ class DiGraph(Graph):
89
+ """
90
+ Base class for directed graphs.
91
+
92
+ A DiGraph stores nodes and edges with optional data, or attributes.
93
+
94
+ DiGraphs hold directed edges. Self loops are allowed but multiple
95
+ (parallel) edges are not.
96
+
97
+ Nodes can be arbitrary (hashable) Python objects with optional
98
+ key/value attributes. By convention `None` is not used as a node.
99
+
100
+ Edges are represented as links between nodes with optional
101
+ key/value attributes.
102
+
103
+ Parameters
104
+ ----------
105
+ incoming_graph_data : input graph (optional, default: None)
106
+ Data to initialize graph. If None (default) an empty
107
+ graph is created. The data can be any format that is supported
108
+ by the to_networkx_graph() function, currently including edge list,
109
+ dict of dicts, dict of lists, NetworkX graph, 2D NumPy array, SciPy
110
+ sparse matrix, or PyGraphviz graph.
111
+
112
+ attr : keyword arguments, optional (default= no attributes)
113
+ Attributes to add to graph as key=value pairs.
114
+
115
+ See Also
116
+ --------
117
+ Graph
118
+ MultiGraph
119
+ MultiDiGraph
120
+
121
+ Examples
122
+ --------
123
+ Create an empty graph structure (a "null graph") with no nodes and
124
+ no edges.
125
+
126
+ >>> G = nx.DiGraph()
127
+
128
+ G can be grown in several ways.
129
+
130
+ **Nodes:**
131
+
132
+ Add one node at a time:
133
+
134
+ >>> G.add_node(1)
135
+
136
+ Add the nodes from any container (a list, dict, set or
137
+ even the lines from a file or the nodes from another graph).
138
+
139
+ >>> G.add_nodes_from([2, 3])
140
+ >>> G.add_nodes_from(range(100, 110))
141
+ >>> H = nx.path_graph(10)
142
+ >>> G.add_nodes_from(H)
143
+
144
+ In addition to strings and integers any hashable Python object
145
+ (except None) can represent a node, e.g. a customized node object,
146
+ or even another Graph.
147
+
148
+ >>> G.add_node(H)
149
+
150
+ **Edges:**
151
+
152
+ G can also be grown by adding edges.
153
+
154
+ Add one edge,
155
+
156
+ >>> G.add_edge(1, 2)
157
+
158
+ a list of edges,
159
+
160
+ >>> G.add_edges_from([(1, 2), (1, 3)])
161
+
162
+ or a collection of edges,
163
+
164
+ >>> G.add_edges_from(H.edges)
165
+
166
+ If some edges connect nodes not yet in the graph, the nodes
167
+ are added automatically. There are no errors when adding
168
+ nodes or edges that already exist.
169
+
170
+ **Attributes:**
171
+
172
+ Each graph, node, and edge can hold key/value attribute pairs
173
+ in an associated attribute dictionary (the keys must be hashable).
174
+ By default these are empty, but can be added or changed using
175
+ add_edge, add_node or direct manipulation of the attribute
176
+ dictionaries named graph, node and edge respectively.
177
+
178
+ >>> G = nx.DiGraph(day="Friday")
179
+ >>> G.graph
180
+ {'day': 'Friday'}
181
+
182
+ Add node attributes using add_node(), add_nodes_from() or G.nodes
183
+
184
+ >>> G.add_node(1, time="5pm")
185
+ >>> G.add_nodes_from([3], time="2pm")
186
+ >>> G.nodes[1]
187
+ {'time': '5pm'}
188
+ >>> G.nodes[1]["room"] = 714
189
+ >>> del G.nodes[1]["room"] # remove attribute
190
+ >>> list(G.nodes(data=True))
191
+ [(1, {'time': '5pm'}), (3, {'time': '2pm'})]
192
+
193
+ Add edge attributes using add_edge(), add_edges_from(), subscript
194
+ notation, or G.edges.
195
+
196
+ >>> G.add_edge(1, 2, weight=4.7)
197
+ >>> G.add_edges_from([(3, 4), (4, 5)], color="red")
198
+ >>> G.add_edges_from([(1, 2, {"color": "blue"}), (2, 3, {"weight": 8})])
199
+ >>> G[1][2]["weight"] = 4.7
200
+ >>> G.edges[1, 2]["weight"] = 4
201
+
202
+ Warning: we protect the graph data structure by making `G.edges[1, 2]` a
203
+ read-only dict-like structure. However, you can assign to attributes
204
+ in e.g. `G.edges[1, 2]`. Thus, use 2 sets of brackets to add/change
205
+ data attributes: `G.edges[1, 2]['weight'] = 4`
206
+ (For multigraphs: `MG.edges[u, v, key][name] = value`).
207
+
208
+ **Shortcuts:**
209
+
210
+ Many common graph features allow python syntax to speed reporting.
211
+
212
+ >>> 1 in G # check if node in graph
213
+ True
214
+ >>> [n for n in G if n < 3] # iterate through nodes
215
+ [1, 2]
216
+ >>> len(G) # number of nodes in graph
217
+ 5
218
+
219
+ Often the best way to traverse all edges of a graph is via the neighbors.
220
+ The neighbors are reported as an adjacency-dict `G.adj` or `G.adjacency()`
221
+
222
+ >>> for n, nbrsdict in G.adjacency():
223
+ ... for nbr, eattr in nbrsdict.items():
224
+ ... if "weight" in eattr:
225
+ ... # Do something useful with the edges
226
+ ... pass
227
+
228
+ But the edges reporting object is often more convenient:
229
+
230
+ >>> for u, v, weight in G.edges(data="weight"):
231
+ ... if weight is not None:
232
+ ... # Do something useful with the edges
233
+ ... pass
234
+
235
+ **Reporting:**
236
+
237
+ Simple graph information is obtained using object-attributes and methods.
238
+ Reporting usually provides views instead of containers to reduce memory
239
+ usage. The views update as the graph is updated similarly to dict-views.
240
+ The objects `nodes`, `edges` and `adj` provide access to data attributes
241
+ via lookup (e.g. `nodes[n]`, `edges[u, v]`, `adj[u][v]`) and iteration
242
+ (e.g. `nodes.items()`, `nodes.data('color')`,
243
+ `nodes.data('color', default='blue')` and similarly for `edges`)
244
+ Views exist for `nodes`, `edges`, `neighbors()`/`adj` and `degree`.
245
+
246
+ For details on these and other miscellaneous methods, see below.
247
+
248
+ **Subclasses (Advanced):**
249
+
250
+ The Graph class uses a dict-of-dict-of-dict data structure.
251
+ The outer dict (node_dict) holds adjacency information keyed by node.
252
+ The next dict (adjlist_dict) represents the adjacency information and holds
253
+ edge data keyed by neighbor. The inner dict (edge_attr_dict) represents
254
+ the edge data and holds edge attribute values keyed by attribute names.
255
+
256
+ Each of these three dicts can be replaced in a subclass by a user defined
257
+ dict-like object. In general, the dict-like features should be
258
+ maintained but extra features can be added. To replace one of the
259
+ dicts create a new graph class by changing the class(!) variable
260
+ holding the factory for that dict-like structure. The variable names are
261
+ node_dict_factory, node_attr_dict_factory, adjlist_inner_dict_factory,
262
+ adjlist_outer_dict_factory, edge_attr_dict_factory and graph_attr_dict_factory.
263
+
264
+ node_dict_factory : function, (default: dict)
265
+ Factory function to be used to create the dict containing node
266
+ attributes, keyed by node id.
267
+ It should require no arguments and return a dict-like object
268
+
269
+ node_attr_dict_factory: function, (default: dict)
270
+ Factory function to be used to create the node attribute
271
+ dict which holds attribute values keyed by attribute name.
272
+ It should require no arguments and return a dict-like object
273
+
274
+ adjlist_outer_dict_factory : function, (default: dict)
275
+ Factory function to be used to create the outer-most dict
276
+ in the data structure that holds adjacency info keyed by node.
277
+ It should require no arguments and return a dict-like object.
278
+
279
+ adjlist_inner_dict_factory : function, optional (default: dict)
280
+ Factory function to be used to create the adjacency list
281
+ dict which holds edge data keyed by neighbor.
282
+ It should require no arguments and return a dict-like object
283
+
284
+ edge_attr_dict_factory : function, optional (default: dict)
285
+ Factory function to be used to create the edge attribute
286
+ dict which holds attribute values keyed by attribute name.
287
+ It should require no arguments and return a dict-like object.
288
+
289
+ graph_attr_dict_factory : function, (default: dict)
290
+ Factory function to be used to create the graph attribute
291
+ dict which holds attribute values keyed by attribute name.
292
+ It should require no arguments and return a dict-like object.
293
+
294
+ Typically, if your extension doesn't impact the data structure all
295
+ methods will inherited without issue except: `to_directed/to_undirected`.
296
+ By default these methods create a DiGraph/Graph class and you probably
297
+ want them to create your extension of a DiGraph/Graph. To facilitate
298
+ this we define two class variables that you can set in your subclass.
299
+
300
+ to_directed_class : callable, (default: DiGraph or MultiDiGraph)
301
+ Class to create a new graph structure in the `to_directed` method.
302
+ If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used.
303
+
304
+ to_undirected_class : callable, (default: Graph or MultiGraph)
305
+ Class to create a new graph structure in the `to_undirected` method.
306
+ If `None`, a NetworkX class (Graph or MultiGraph) is used.
307
+
308
+ **Subclassing Example**
309
+
310
+ Create a low memory graph class that effectively disallows edge
311
+ attributes by using a single attribute dict for all edges.
312
+ This reduces the memory used, but you lose edge attributes.
313
+
314
+ >>> class ThinGraph(nx.Graph):
315
+ ... all_edge_dict = {"weight": 1}
316
+ ...
317
+ ... def single_edge_dict(self):
318
+ ... return self.all_edge_dict
319
+ ...
320
+ ... edge_attr_dict_factory = single_edge_dict
321
+ >>> G = ThinGraph()
322
+ >>> G.add_edge(2, 1)
323
+ >>> G[2][1]
324
+ {'weight': 1}
325
+ >>> G.add_edge(2, 2)
326
+ >>> G[2][1] is G[2][2]
327
+ True
328
+ """
329
+
330
+ _adj = _CachedPropertyResetterAdjAndSucc() # type: ignore[assignment]
331
+ _succ = _adj # type: ignore[has-type]
332
+ _pred = _CachedPropertyResetterPred()
333
+
334
+ # This __new__ method just does what Python itself does automatically.
335
+ # We include it here as part of the dispatchable/backend interface.
336
+ # If your goal is to understand how the graph classes work, you can ignore
337
+ # this method, even when subclassing the base classes. If you are subclassing
338
+ # in order to provide a backend that allows class instantiation, this method
339
+ # can be overridden to return your own backend graph class.
340
+ @nx._dispatchable(name="digraph__new__", graphs=None, returns_graph=True)
341
+ def __new__(cls, *args, **kwargs):
342
+ return object.__new__(cls)
343
+
344
+ def __init__(self, incoming_graph_data=None, **attr):
345
+ """Initialize a graph with edges, name, or graph attributes.
346
+
347
+ Parameters
348
+ ----------
349
+ incoming_graph_data : input graph (optional, default: None)
350
+ Data to initialize graph. If None (default) an empty
351
+ graph is created. The data can be an edge list, or any
352
+ NetworkX graph object. If the corresponding optional Python
353
+ packages are installed the data can also be a 2D NumPy array, a
354
+ SciPy sparse array, or a PyGraphviz graph.
355
+
356
+ attr : keyword arguments, optional (default= no attributes)
357
+ Attributes to add to graph as key=value pairs.
358
+
359
+ See Also
360
+ --------
361
+ convert
362
+
363
+ Examples
364
+ --------
365
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
366
+ >>> G = nx.Graph(name="my graph")
367
+ >>> e = [(1, 2), (2, 3), (3, 4)] # list of edges
368
+ >>> G = nx.Graph(e)
369
+
370
+ Arbitrary graph attribute pairs (key=value) may be assigned
371
+
372
+ >>> G = nx.Graph(e, day="Friday")
373
+ >>> G.graph
374
+ {'day': 'Friday'}
375
+
376
+ """
377
+ self.graph = self.graph_attr_dict_factory() # dictionary for graph attributes
378
+ self._node = self.node_dict_factory() # dictionary for node attr
379
+ # We store two adjacency lists:
380
+ # the predecessors of node n are stored in the dict self._pred
381
+ # the successors of node n are stored in the dict self._succ=self._adj
382
+ self._adj = self.adjlist_outer_dict_factory() # empty adjacency dict successor
383
+ self._pred = self.adjlist_outer_dict_factory() # predecessor
384
+ # Note: self._succ = self._adj # successor
385
+
386
+ self.__networkx_cache__ = {}
387
+ # attempt to load graph with data
388
+ if incoming_graph_data is not None:
389
+ convert.to_networkx_graph(incoming_graph_data, create_using=self)
390
+ # load graph attributes (must be after convert)
391
+ attr.pop("backend", None) # Ignore explicit `backend="networkx"`
392
+ self.graph.update(attr)
393
+
394
+ @cached_property
395
+ def adj(self):
396
+ """Graph adjacency object holding the neighbors of each node.
397
+
398
+ This object is a read-only dict-like structure with node keys
399
+ and neighbor-dict values. The neighbor-dict is keyed by neighbor
400
+ to the edge-data-dict. So `G.adj[3][2]['color'] = 'blue'` sets
401
+ the color of the edge `(3, 2)` to `"blue"`.
402
+
403
+ Iterating over G.adj behaves like a dict. Useful idioms include
404
+ `for nbr, datadict in G.adj[n].items():`.
405
+
406
+ The neighbor information is also provided by subscripting the graph.
407
+ So `for nbr, foovalue in G[node].data('foo', default=1):` works.
408
+
409
+ For directed graphs, `G.adj` holds outgoing (successor) info.
410
+ """
411
+ return AdjacencyView(self._succ)
412
+
413
+ @cached_property
414
+ def succ(self):
415
+ """Graph adjacency object holding the successors of each node.
416
+
417
+ This object is a read-only dict-like structure with node keys
418
+ and neighbor-dict values. The neighbor-dict is keyed by neighbor
419
+ to the edge-data-dict. So `G.succ[3][2]['color'] = 'blue'` sets
420
+ the color of the edge `(3, 2)` to `"blue"`.
421
+
422
+ Iterating over G.succ behaves like a dict. Useful idioms include
423
+ `for nbr, datadict in G.succ[n].items():`. A data-view not provided
424
+ by dicts also exists: `for nbr, foovalue in G.succ[node].data('foo'):`
425
+ and a default can be set via a `default` argument to the `data` method.
426
+
427
+ The neighbor information is also provided by subscripting the graph.
428
+ So `for nbr, foovalue in G[node].data('foo', default=1):` works.
429
+
430
+ For directed graphs, `G.adj` is identical to `G.succ`.
431
+ """
432
+ return AdjacencyView(self._succ)
433
+
434
+ @cached_property
435
+ def pred(self):
436
+ """Graph adjacency object holding the predecessors of each node.
437
+
438
+ This object is a read-only dict-like structure with node keys
439
+ and neighbor-dict values. The neighbor-dict is keyed by neighbor
440
+ to the edge-data-dict. So `G.pred[2][3]['color'] = 'blue'` sets
441
+ the color of the edge `(3, 2)` to `"blue"`.
442
+
443
+ Iterating over G.pred behaves like a dict. Useful idioms include
444
+ `for nbr, datadict in G.pred[n].items():`. A data-view not provided
445
+ by dicts also exists: `for nbr, foovalue in G.pred[node].data('foo'):`
446
+ A default can be set via a `default` argument to the `data` method.
447
+ """
448
+ return AdjacencyView(self._pred)
449
+
450
+ def add_node(self, node_for_adding, **attr):
451
+ """Add a single node `node_for_adding` and update node attributes.
452
+
453
+ Parameters
454
+ ----------
455
+ node_for_adding : node
456
+ A node can be any hashable Python object except None.
457
+ attr : keyword arguments, optional
458
+ Set or change node attributes using key=value.
459
+
460
+ See Also
461
+ --------
462
+ add_nodes_from
463
+
464
+ Examples
465
+ --------
466
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
467
+ >>> G.add_node(1)
468
+ >>> G.add_node("Hello")
469
+ >>> K3 = nx.Graph([(0, 1), (1, 2), (2, 0)])
470
+ >>> G.add_node(K3)
471
+ >>> G.number_of_nodes()
472
+ 3
473
+
474
+ Use keywords set/change node attributes:
475
+
476
+ >>> G.add_node(1, size=10)
477
+ >>> G.add_node(3, weight=0.4, UTM=("13S", 382871, 3972649))
478
+
479
+ Notes
480
+ -----
481
+ A hashable object is one that can be used as a key in a Python
482
+ dictionary. This includes strings, numbers, tuples of strings
483
+ and numbers, etc.
484
+
485
+ On many platforms hashable items also include mutables such as
486
+ NetworkX Graphs, though one should be careful that the hash
487
+ doesn't change on mutables.
488
+ """
489
+ if node_for_adding not in self._succ:
490
+ if node_for_adding is None:
491
+ raise ValueError("None cannot be a node")
492
+ self._succ[node_for_adding] = self.adjlist_inner_dict_factory()
493
+ self._pred[node_for_adding] = self.adjlist_inner_dict_factory()
494
+ attr_dict = self._node[node_for_adding] = self.node_attr_dict_factory()
495
+ attr_dict.update(attr)
496
+ else: # update attr even if node already exists
497
+ self._node[node_for_adding].update(attr)
498
+ nx._clear_cache(self)
499
+
500
+ def add_nodes_from(self, nodes_for_adding, **attr):
501
+ """Add multiple nodes.
502
+
503
+ Parameters
504
+ ----------
505
+ nodes_for_adding : iterable container
506
+ A container of nodes (list, dict, set, etc.).
507
+ OR
508
+ A container of (node, attribute dict) tuples.
509
+ Node attributes are updated using the attribute dict.
510
+ attr : keyword arguments, optional (default= no attributes)
511
+ Update attributes for all nodes in nodes.
512
+ Node attributes specified in nodes as a tuple take
513
+ precedence over attributes specified via keyword arguments.
514
+
515
+ See Also
516
+ --------
517
+ add_node
518
+
519
+ Notes
520
+ -----
521
+ When adding nodes from an iterator over the graph you are changing,
522
+ a `RuntimeError` can be raised with message:
523
+ `RuntimeError: dictionary changed size during iteration`. This
524
+ happens when the graph's underlying dictionary is modified during
525
+ iteration. To avoid this error, evaluate the iterator into a separate
526
+ object, e.g. by using `list(iterator_of_nodes)`, and pass this
527
+ object to `G.add_nodes_from`.
528
+
529
+ Examples
530
+ --------
531
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
532
+ >>> G.add_nodes_from("Hello")
533
+ >>> K3 = nx.Graph([(0, 1), (1, 2), (2, 0)])
534
+ >>> G.add_nodes_from(K3)
535
+ >>> sorted(G.nodes(), key=str)
536
+ [0, 1, 2, 'H', 'e', 'l', 'o']
537
+
538
+ Use keywords to update specific node attributes for every node.
539
+
540
+ >>> G.add_nodes_from([1, 2], size=10)
541
+ >>> G.add_nodes_from([3, 4], weight=0.4)
542
+
543
+ Use (node, attrdict) tuples to update attributes for specific nodes.
544
+
545
+ >>> G.add_nodes_from([(1, dict(size=11)), (2, {"color": "blue"})])
546
+ >>> G.nodes[1]["size"]
547
+ 11
548
+ >>> H = nx.Graph()
549
+ >>> H.add_nodes_from(G.nodes(data=True))
550
+ >>> H.nodes[1]["size"]
551
+ 11
552
+
553
+ Evaluate an iterator over a graph if using it to modify the same graph
554
+
555
+ >>> G = nx.DiGraph([(0, 1), (1, 2), (3, 4)])
556
+ >>> # wrong way - will raise RuntimeError
557
+ >>> # G.add_nodes_from(n + 1 for n in G.nodes)
558
+ >>> # correct way
559
+ >>> G.add_nodes_from(list(n + 1 for n in G.nodes))
560
+ """
561
+ for n in nodes_for_adding:
562
+ try:
563
+ newnode = n not in self._node
564
+ newdict = attr
565
+ except TypeError:
566
+ n, ndict = n
567
+ newnode = n not in self._node
568
+ newdict = attr.copy()
569
+ newdict.update(ndict)
570
+ if newnode:
571
+ if n is None:
572
+ raise ValueError("None cannot be a node")
573
+ self._succ[n] = self.adjlist_inner_dict_factory()
574
+ self._pred[n] = self.adjlist_inner_dict_factory()
575
+ self._node[n] = self.node_attr_dict_factory()
576
+ self._node[n].update(newdict)
577
+ nx._clear_cache(self)
578
+
579
+ def remove_node(self, n):
580
+ """Remove node n.
581
+
582
+ Removes the node n and all adjacent edges.
583
+ Attempting to remove a nonexistent node will raise an exception.
584
+
585
+ Parameters
586
+ ----------
587
+ n : node
588
+ A node in the graph
589
+
590
+ Raises
591
+ ------
592
+ NetworkXError
593
+ If n is not in the graph.
594
+
595
+ See Also
596
+ --------
597
+ remove_nodes_from
598
+
599
+ Examples
600
+ --------
601
+ >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
602
+ >>> list(G.edges)
603
+ [(0, 1), (1, 2)]
604
+ >>> G.remove_node(1)
605
+ >>> list(G.edges)
606
+ []
607
+
608
+ """
609
+ try:
610
+ nbrs = self._succ[n]
611
+ del self._node[n]
612
+ except KeyError as err: # NetworkXError if n not in self
613
+ raise NetworkXError(f"The node {n} is not in the digraph.") from err
614
+ for u in nbrs:
615
+ del self._pred[u][n] # remove all edges n-u in digraph
616
+ del self._succ[n] # remove node from succ
617
+ for u in self._pred[n]:
618
+ del self._succ[u][n] # remove all edges n-u in digraph
619
+ del self._pred[n] # remove node from pred
620
+ nx._clear_cache(self)
621
+
622
+ def remove_nodes_from(self, nodes):
623
+ """Remove multiple nodes.
624
+
625
+ Parameters
626
+ ----------
627
+ nodes : iterable container
628
+ A container of nodes (list, dict, set, etc.). If a node
629
+ in the container is not in the graph it is silently ignored.
630
+
631
+ See Also
632
+ --------
633
+ remove_node
634
+
635
+ Notes
636
+ -----
637
+ When removing nodes from an iterator over the graph you are changing,
638
+ a `RuntimeError` will be raised with message:
639
+ `RuntimeError: dictionary changed size during iteration`. This
640
+ happens when the graph's underlying dictionary is modified during
641
+ iteration. To avoid this error, evaluate the iterator into a separate
642
+ object, e.g. by using `list(iterator_of_nodes)`, and pass this
643
+ object to `G.remove_nodes_from`.
644
+
645
+ Examples
646
+ --------
647
+ >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
648
+ >>> e = list(G.nodes)
649
+ >>> e
650
+ [0, 1, 2]
651
+ >>> G.remove_nodes_from(e)
652
+ >>> list(G.nodes)
653
+ []
654
+
655
+ Evaluate an iterator over a graph if using it to modify the same graph
656
+
657
+ >>> G = nx.DiGraph([(0, 1), (1, 2), (3, 4)])
658
+ >>> # this command will fail, as the graph's dict is modified during iteration
659
+ >>> # G.remove_nodes_from(n for n in G.nodes if n < 2)
660
+ >>> # this command will work, since the dictionary underlying graph is not modified
661
+ >>> G.remove_nodes_from(list(n for n in G.nodes if n < 2))
662
+ """
663
+ for n in nodes:
664
+ try:
665
+ succs = self._succ[n]
666
+ del self._node[n]
667
+ for u in succs:
668
+ del self._pred[u][n] # remove all edges n-u in digraph
669
+ del self._succ[n] # now remove node
670
+ for u in self._pred[n]:
671
+ del self._succ[u][n] # remove all edges n-u in digraph
672
+ del self._pred[n] # now remove node
673
+ except KeyError:
674
+ pass # silent failure on remove
675
+ nx._clear_cache(self)
676
+
677
+ def add_edge(self, u_of_edge, v_of_edge, **attr):
678
+ """Add an edge between u and v.
679
+
680
+ The nodes u and v will be automatically added if they are
681
+ not already in the graph.
682
+
683
+ Edge attributes can be specified with keywords or by directly
684
+ accessing the edge's attribute dictionary. See examples below.
685
+
686
+ Parameters
687
+ ----------
688
+ u_of_edge, v_of_edge : nodes
689
+ Nodes can be, for example, strings or numbers.
690
+ Nodes must be hashable (and not None) Python objects.
691
+ attr : keyword arguments, optional
692
+ Edge data (or labels or objects) can be assigned using
693
+ keyword arguments.
694
+
695
+ See Also
696
+ --------
697
+ add_edges_from : add a collection of edges
698
+
699
+ Notes
700
+ -----
701
+ Adding an edge that already exists updates the edge data.
702
+
703
+ Many NetworkX algorithms designed for weighted graphs use
704
+ an edge attribute (by default `weight`) to hold a numerical value.
705
+
706
+ Examples
707
+ --------
708
+ The following all add the edge e=(1, 2) to graph G:
709
+
710
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
711
+ >>> e = (1, 2)
712
+ >>> G.add_edge(1, 2) # explicit two-node form
713
+ >>> G.add_edge(*e) # single edge as tuple of two nodes
714
+ >>> G.add_edges_from([(1, 2)]) # add edges from iterable container
715
+
716
+ Associate data to edges using keywords:
717
+
718
+ >>> G.add_edge(1, 2, weight=3)
719
+ >>> G.add_edge(1, 3, weight=7, capacity=15, length=342.7)
720
+
721
+ For non-string attribute keys, use subscript notation.
722
+
723
+ >>> G.add_edge(1, 2)
724
+ >>> G[1][2].update({0: 5})
725
+ >>> G.edges[1, 2].update({0: 5})
726
+ """
727
+ u, v = u_of_edge, v_of_edge
728
+ # add nodes
729
+ if u not in self._succ:
730
+ if u is None:
731
+ raise ValueError("None cannot be a node")
732
+ self._succ[u] = self.adjlist_inner_dict_factory()
733
+ self._pred[u] = self.adjlist_inner_dict_factory()
734
+ self._node[u] = self.node_attr_dict_factory()
735
+ if v not in self._succ:
736
+ if v is None:
737
+ raise ValueError("None cannot be a node")
738
+ self._succ[v] = self.adjlist_inner_dict_factory()
739
+ self._pred[v] = self.adjlist_inner_dict_factory()
740
+ self._node[v] = self.node_attr_dict_factory()
741
+ # add the edge
742
+ datadict = self._adj[u].get(v, self.edge_attr_dict_factory())
743
+ datadict.update(attr)
744
+ self._succ[u][v] = datadict
745
+ self._pred[v][u] = datadict
746
+ nx._clear_cache(self)
747
+
748
+ def add_edges_from(self, ebunch_to_add, **attr):
749
+ """Add all the edges in ebunch_to_add.
750
+
751
+ Parameters
752
+ ----------
753
+ ebunch_to_add : container of edges
754
+ Each edge given in the container will be added to the
755
+ graph. The edges must be given as 2-tuples (u, v) or
756
+ 3-tuples (u, v, d) where d is a dictionary containing edge data.
757
+ attr : keyword arguments, optional
758
+ Edge data (or labels or objects) can be assigned using
759
+ keyword arguments.
760
+
761
+ See Also
762
+ --------
763
+ add_edge : add a single edge
764
+ add_weighted_edges_from : convenient way to add weighted edges
765
+
766
+ Notes
767
+ -----
768
+ Adding the same edge twice has no effect but any edge data
769
+ will be updated when each duplicate edge is added.
770
+
771
+ Edge attributes specified in an ebunch take precedence over
772
+ attributes specified via keyword arguments.
773
+
774
+ When adding edges from an iterator over the graph you are changing,
775
+ a `RuntimeError` can be raised with message:
776
+ `RuntimeError: dictionary changed size during iteration`. This
777
+ happens when the graph's underlying dictionary is modified during
778
+ iteration. To avoid this error, evaluate the iterator into a separate
779
+ object, e.g. by using `list(iterator_of_edges)`, and pass this
780
+ object to `G.add_edges_from`.
781
+
782
+ Examples
783
+ --------
784
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
785
+ >>> G.add_edges_from([(0, 1), (1, 2)]) # using a list of edge tuples
786
+ >>> e = zip(range(0, 3), range(1, 4))
787
+ >>> G.add_edges_from(e) # Add the path graph 0-1-2-3
788
+
789
+ Associate data to edges
790
+
791
+ >>> G.add_edges_from([(1, 2), (2, 3)], weight=3)
792
+ >>> G.add_edges_from([(3, 4), (1, 4)], label="WN2898")
793
+
794
+ Evaluate an iterator over a graph if using it to modify the same graph
795
+
796
+ >>> G = nx.DiGraph([(1, 2), (2, 3), (3, 4)])
797
+ >>> # Grow graph by one new node, adding edges to all existing nodes.
798
+ >>> # wrong way - will raise RuntimeError
799
+ >>> # G.add_edges_from(((5, n) for n in G.nodes))
800
+ >>> # right way - note that there will be no self-edge for node 5
801
+ >>> G.add_edges_from(list((5, n) for n in G.nodes))
802
+ """
803
+ for e in ebunch_to_add:
804
+ ne = len(e)
805
+ if ne == 3:
806
+ u, v, dd = e
807
+ elif ne == 2:
808
+ u, v = e
809
+ dd = {}
810
+ else:
811
+ raise NetworkXError(f"Edge tuple {e} must be a 2-tuple or 3-tuple.")
812
+ if u not in self._succ:
813
+ if u is None:
814
+ raise ValueError("None cannot be a node")
815
+ self._succ[u] = self.adjlist_inner_dict_factory()
816
+ self._pred[u] = self.adjlist_inner_dict_factory()
817
+ self._node[u] = self.node_attr_dict_factory()
818
+ if v not in self._succ:
819
+ if v is None:
820
+ raise ValueError("None cannot be a node")
821
+ self._succ[v] = self.adjlist_inner_dict_factory()
822
+ self._pred[v] = self.adjlist_inner_dict_factory()
823
+ self._node[v] = self.node_attr_dict_factory()
824
+ datadict = self._adj[u].get(v, self.edge_attr_dict_factory())
825
+ datadict.update(attr)
826
+ datadict.update(dd)
827
+ self._succ[u][v] = datadict
828
+ self._pred[v][u] = datadict
829
+ nx._clear_cache(self)
830
+
831
+ def remove_edge(self, u, v):
832
+ """Remove the edge between u and v.
833
+
834
+ Parameters
835
+ ----------
836
+ u, v : nodes
837
+ Remove the edge between nodes u and v.
838
+
839
+ Raises
840
+ ------
841
+ NetworkXError
842
+ If there is not an edge between u and v.
843
+
844
+ See Also
845
+ --------
846
+ remove_edges_from : remove a collection of edges
847
+
848
+ Examples
849
+ --------
850
+ >>> G = nx.Graph() # or DiGraph, etc
851
+ >>> nx.add_path(G, [0, 1, 2, 3])
852
+ >>> G.remove_edge(0, 1)
853
+ >>> e = (1, 2)
854
+ >>> G.remove_edge(*e) # unpacks e from an edge tuple
855
+ >>> e = (2, 3, {"weight": 7}) # an edge with attribute data
856
+ >>> G.remove_edge(*e[:2]) # select first part of edge tuple
857
+ """
858
+ try:
859
+ del self._succ[u][v]
860
+ del self._pred[v][u]
861
+ except KeyError as err:
862
+ raise NetworkXError(f"The edge {u}-{v} not in graph.") from err
863
+ nx._clear_cache(self)
864
+
865
+ def remove_edges_from(self, ebunch):
866
+ """Remove all edges specified in ebunch.
867
+
868
+ Parameters
869
+ ----------
870
+ ebunch: list or container of edge tuples
871
+ Each edge given in the list or container will be removed
872
+ from the graph. The edges can be:
873
+
874
+ - 2-tuples (u, v) edge between u and v.
875
+ - 3-tuples (u, v, k) where k is ignored.
876
+
877
+ See Also
878
+ --------
879
+ remove_edge : remove a single edge
880
+
881
+ Notes
882
+ -----
883
+ Will fail silently if an edge in ebunch is not in the graph.
884
+
885
+ Examples
886
+ --------
887
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
888
+ >>> ebunch = [(1, 2), (2, 3)]
889
+ >>> G.remove_edges_from(ebunch)
890
+ """
891
+ for e in ebunch:
892
+ u, v = e[:2] # ignore edge data
893
+ if u in self._succ and v in self._succ[u]:
894
+ del self._succ[u][v]
895
+ del self._pred[v][u]
896
+ nx._clear_cache(self)
897
+
898
+ def has_successor(self, u, v):
899
+ """Returns True if node u has successor v.
900
+
901
+ This is true if graph has the edge u->v.
902
+ """
903
+ return u in self._succ and v in self._succ[u]
904
+
905
+ def has_predecessor(self, u, v):
906
+ """Returns True if node u has predecessor v.
907
+
908
+ This is true if graph has the edge u<-v.
909
+ """
910
+ return u in self._pred and v in self._pred[u]
911
+
912
+ def successors(self, n):
913
+ """Returns an iterator over successor nodes of n.
914
+
915
+ A successor of n is a node m such that there exists a directed
916
+ edge from n to m.
917
+
918
+ Parameters
919
+ ----------
920
+ n : node
921
+ A node in the graph
922
+
923
+ Raises
924
+ ------
925
+ NetworkXError
926
+ If n is not in the graph.
927
+
928
+ See Also
929
+ --------
930
+ predecessors
931
+
932
+ Notes
933
+ -----
934
+ neighbors() and successors() are the same.
935
+ """
936
+ try:
937
+ return iter(self._succ[n])
938
+ except KeyError as err:
939
+ raise NetworkXError(f"The node {n} is not in the digraph.") from err
940
+
941
+ # digraph definitions
942
+ neighbors = successors
943
+
944
+ def predecessors(self, n):
945
+ """Returns an iterator over predecessor nodes of n.
946
+
947
+ A predecessor of n is a node m such that there exists a directed
948
+ edge from m to n.
949
+
950
+ Parameters
951
+ ----------
952
+ n : node
953
+ A node in the graph
954
+
955
+ Raises
956
+ ------
957
+ NetworkXError
958
+ If n is not in the graph.
959
+
960
+ See Also
961
+ --------
962
+ successors
963
+ """
964
+ try:
965
+ return iter(self._pred[n])
966
+ except KeyError as err:
967
+ raise NetworkXError(f"The node {n} is not in the digraph.") from err
968
+
969
+ @cached_property
970
+ def edges(self):
971
+ """An OutEdgeView of the DiGraph as G.edges or G.edges().
972
+
973
+ edges(self, nbunch=None, data=False, default=None)
974
+
975
+ The OutEdgeView provides set-like operations on the edge-tuples
976
+ as well as edge attribute lookup. When called, it also provides
977
+ an EdgeDataView object which allows control of access to edge
978
+ attributes (but does not provide set-like operations).
979
+ Hence, `G.edges[u, v]['color']` provides the value of the color
980
+ attribute for edge `(u, v)` while
981
+ `for (u, v, c) in G.edges.data('color', default='red'):`
982
+ iterates through all the edges yielding the color attribute
983
+ with default `'red'` if no color attribute exists.
984
+
985
+ Parameters
986
+ ----------
987
+ nbunch : single node, container, or all nodes (default= all nodes)
988
+ The view will only report edges from these nodes.
989
+ data : string or bool, optional (default=False)
990
+ The edge attribute returned in 3-tuple (u, v, ddict[data]).
991
+ If True, return edge attribute dict in 3-tuple (u, v, ddict).
992
+ If False, return 2-tuple (u, v).
993
+ default : value, optional (default=None)
994
+ Value used for edges that don't have the requested attribute.
995
+ Only relevant if data is not True or False.
996
+
997
+ Returns
998
+ -------
999
+ edges : OutEdgeView
1000
+ A view of edge attributes, usually it iterates over (u, v)
1001
+ or (u, v, d) tuples of edges, but can also be used for
1002
+ attribute lookup as `edges[u, v]['foo']`.
1003
+
1004
+ See Also
1005
+ --------
1006
+ in_edges, out_edges
1007
+
1008
+ Notes
1009
+ -----
1010
+ Nodes in nbunch that are not in the graph will be (quietly) ignored.
1011
+ For directed graphs this returns the out-edges.
1012
+
1013
+ Examples
1014
+ --------
1015
+ >>> G = nx.DiGraph() # or MultiDiGraph, etc
1016
+ >>> nx.add_path(G, [0, 1, 2])
1017
+ >>> G.add_edge(2, 3, weight=5)
1018
+ >>> [e for e in G.edges]
1019
+ [(0, 1), (1, 2), (2, 3)]
1020
+ >>> G.edges.data() # default data is {} (empty dict)
1021
+ OutEdgeDataView([(0, 1, {}), (1, 2, {}), (2, 3, {'weight': 5})])
1022
+ >>> G.edges.data("weight", default=1)
1023
+ OutEdgeDataView([(0, 1, 1), (1, 2, 1), (2, 3, 5)])
1024
+ >>> G.edges([0, 2]) # only edges originating from these nodes
1025
+ OutEdgeDataView([(0, 1), (2, 3)])
1026
+ >>> G.edges(0) # only edges from node 0
1027
+ OutEdgeDataView([(0, 1)])
1028
+
1029
+ """
1030
+ return OutEdgeView(self)
1031
+
1032
+ # alias out_edges to edges
1033
+ @cached_property
1034
+ def out_edges(self):
1035
+ return OutEdgeView(self)
1036
+
1037
+ out_edges.__doc__ = edges.__doc__
1038
+
1039
+ @cached_property
1040
+ def in_edges(self):
1041
+ """A view of the in edges of the graph as G.in_edges or G.in_edges().
1042
+
1043
+ in_edges(self, nbunch=None, data=False, default=None):
1044
+
1045
+ Parameters
1046
+ ----------
1047
+ nbunch : single node, container, or all nodes (default= all nodes)
1048
+ The view will only report edges incident to these nodes.
1049
+ data : string or bool, optional (default=False)
1050
+ The edge attribute returned in 3-tuple (u, v, ddict[data]).
1051
+ If True, return edge attribute dict in 3-tuple (u, v, ddict).
1052
+ If False, return 2-tuple (u, v).
1053
+ default : value, optional (default=None)
1054
+ Value used for edges that don't have the requested attribute.
1055
+ Only relevant if data is not True or False.
1056
+
1057
+ Returns
1058
+ -------
1059
+ in_edges : InEdgeView or InEdgeDataView
1060
+ A view of edge attributes, usually it iterates over (u, v)
1061
+ or (u, v, d) tuples of edges, but can also be used for
1062
+ attribute lookup as `edges[u, v]['foo']`.
1063
+
1064
+ Examples
1065
+ --------
1066
+ >>> G = nx.DiGraph()
1067
+ >>> G.add_edge(1, 2, color="blue")
1068
+ >>> G.in_edges()
1069
+ InEdgeView([(1, 2)])
1070
+ >>> G.in_edges(nbunch=2)
1071
+ InEdgeDataView([(1, 2)])
1072
+
1073
+ See Also
1074
+ --------
1075
+ edges
1076
+ """
1077
+ return InEdgeView(self)
1078
+
1079
+ @cached_property
1080
+ def degree(self):
1081
+ """A DegreeView for the Graph as G.degree or G.degree().
1082
+
1083
+ The node degree is the number of edges adjacent to the node.
1084
+ The weighted node degree is the sum of the edge weights for
1085
+ edges incident to that node.
1086
+
1087
+ This object provides an iterator for (node, degree) as well as
1088
+ lookup for the degree for a single node.
1089
+
1090
+ Parameters
1091
+ ----------
1092
+ nbunch : single node, container, or all nodes (default= all nodes)
1093
+ The view will only report edges incident to these nodes.
1094
+
1095
+ weight : string or None, optional (default=None)
1096
+ The name of an edge attribute that holds the numerical value used
1097
+ as a weight. If None, then each edge has weight 1.
1098
+ The degree is the sum of the edge weights adjacent to the node.
1099
+
1100
+ Returns
1101
+ -------
1102
+ DiDegreeView or int
1103
+ If multiple nodes are requested (the default), returns a `DiDegreeView`
1104
+ mapping nodes to their degree.
1105
+ If a single node is requested, returns the degree of the node as an integer.
1106
+
1107
+ See Also
1108
+ --------
1109
+ in_degree, out_degree
1110
+
1111
+ Examples
1112
+ --------
1113
+ >>> G = nx.DiGraph() # or MultiDiGraph
1114
+ >>> nx.add_path(G, [0, 1, 2, 3])
1115
+ >>> G.degree(0) # node 0 with degree 1
1116
+ 1
1117
+ >>> list(G.degree([0, 1, 2]))
1118
+ [(0, 1), (1, 2), (2, 2)]
1119
+
1120
+ """
1121
+ return DiDegreeView(self)
1122
+
1123
+ @cached_property
1124
+ def in_degree(self):
1125
+ """An InDegreeView for (node, in_degree) or in_degree for single node.
1126
+
1127
+ The node in_degree is the number of edges pointing to the node.
1128
+ The weighted node degree is the sum of the edge weights for
1129
+ edges incident to that node.
1130
+
1131
+ This object provides an iteration over (node, in_degree) as well as
1132
+ lookup for the degree for a single node.
1133
+
1134
+ Parameters
1135
+ ----------
1136
+ nbunch : single node, container, or all nodes (default= all nodes)
1137
+ The view will only report edges incident to these nodes.
1138
+
1139
+ weight : string or None, optional (default=None)
1140
+ The name of an edge attribute that holds the numerical value used
1141
+ as a weight. If None, then each edge has weight 1.
1142
+ The degree is the sum of the edge weights adjacent to the node.
1143
+
1144
+ Returns
1145
+ -------
1146
+ If a single node is requested
1147
+ deg : int
1148
+ In-degree of the node
1149
+
1150
+ OR if multiple nodes are requested
1151
+ nd_iter : iterator
1152
+ The iterator returns two-tuples of (node, in-degree).
1153
+
1154
+ See Also
1155
+ --------
1156
+ degree, out_degree
1157
+
1158
+ Examples
1159
+ --------
1160
+ >>> G = nx.DiGraph()
1161
+ >>> nx.add_path(G, [0, 1, 2, 3])
1162
+ >>> G.in_degree(0) # node 0 with degree 0
1163
+ 0
1164
+ >>> list(G.in_degree([0, 1, 2]))
1165
+ [(0, 0), (1, 1), (2, 1)]
1166
+
1167
+ """
1168
+ return InDegreeView(self)
1169
+
1170
+ @cached_property
1171
+ def out_degree(self):
1172
+ """An OutDegreeView for (node, out_degree)
1173
+
1174
+ The node out_degree is the number of edges pointing out of the node.
1175
+ The weighted node degree is the sum of the edge weights for
1176
+ edges incident to that node.
1177
+
1178
+ This object provides an iterator over (node, out_degree) as well as
1179
+ lookup for the degree for a single node.
1180
+
1181
+ Parameters
1182
+ ----------
1183
+ nbunch : single node, container, or all nodes (default= all nodes)
1184
+ The view will only report edges incident to these nodes.
1185
+
1186
+ weight : string or None, optional (default=None)
1187
+ The name of an edge attribute that holds the numerical value used
1188
+ as a weight. If None, then each edge has weight 1.
1189
+ The degree is the sum of the edge weights adjacent to the node.
1190
+
1191
+ Returns
1192
+ -------
1193
+ If a single node is requested
1194
+ deg : int
1195
+ Out-degree of the node
1196
+
1197
+ OR if multiple nodes are requested
1198
+ nd_iter : iterator
1199
+ The iterator returns two-tuples of (node, out-degree).
1200
+
1201
+ See Also
1202
+ --------
1203
+ degree, in_degree
1204
+
1205
+ Examples
1206
+ --------
1207
+ >>> G = nx.DiGraph()
1208
+ >>> nx.add_path(G, [0, 1, 2, 3])
1209
+ >>> G.out_degree(0) # node 0 with degree 1
1210
+ 1
1211
+ >>> list(G.out_degree([0, 1, 2]))
1212
+ [(0, 1), (1, 1), (2, 1)]
1213
+
1214
+ """
1215
+ return OutDegreeView(self)
1216
+
1217
+ def clear(self):
1218
+ """Remove all nodes and edges from the graph.
1219
+
1220
+ This also removes the name, and all graph, node, and edge attributes.
1221
+
1222
+ Examples
1223
+ --------
1224
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1225
+ >>> G.clear()
1226
+ >>> list(G.nodes)
1227
+ []
1228
+ >>> list(G.edges)
1229
+ []
1230
+
1231
+ """
1232
+ self._succ.clear()
1233
+ self._pred.clear()
1234
+ self._node.clear()
1235
+ self.graph.clear()
1236
+ nx._clear_cache(self)
1237
+
1238
+ def clear_edges(self):
1239
+ """Remove all edges from the graph without altering nodes.
1240
+
1241
+ Examples
1242
+ --------
1243
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1244
+ >>> G.clear_edges()
1245
+ >>> list(G.nodes)
1246
+ [0, 1, 2, 3]
1247
+ >>> list(G.edges)
1248
+ []
1249
+
1250
+ """
1251
+ for predecessor_dict in self._pred.values():
1252
+ predecessor_dict.clear()
1253
+ for successor_dict in self._succ.values():
1254
+ successor_dict.clear()
1255
+ nx._clear_cache(self)
1256
+
1257
+ def is_multigraph(self):
1258
+ """Returns True if graph is a multigraph, False otherwise."""
1259
+ return False
1260
+
1261
+ def is_directed(self):
1262
+ """Returns True if graph is directed, False otherwise."""
1263
+ return True
1264
+
1265
+ def to_undirected(self, reciprocal=False, as_view=False):
1266
+ """Returns an undirected representation of the digraph.
1267
+
1268
+ Parameters
1269
+ ----------
1270
+ reciprocal : bool (optional)
1271
+ If True only keep edges that appear in both directions
1272
+ in the original digraph.
1273
+ as_view : bool (optional, default=False)
1274
+ If True return an undirected view of the original directed graph.
1275
+
1276
+ Returns
1277
+ -------
1278
+ G : Graph
1279
+ An undirected graph with the same name and nodes and
1280
+ with edge (u, v, data) if either (u, v, data) or (v, u, data)
1281
+ is in the digraph. If both edges exist in digraph and
1282
+ their edge data is different, only one edge is created
1283
+ with an arbitrary choice of which edge data to use.
1284
+ You must check and correct for this manually if desired.
1285
+
1286
+ See Also
1287
+ --------
1288
+ Graph, copy, add_edge, add_edges_from
1289
+
1290
+ Notes
1291
+ -----
1292
+ If edges in both directions (u, v) and (v, u) exist in the
1293
+ graph, attributes for the new undirected edge will be a combination of
1294
+ the attributes of the directed edges. The edge data is updated
1295
+ in the (arbitrary) order that the edges are encountered. For
1296
+ more customized control of the edge attributes use add_edge().
1297
+
1298
+ This returns a "deepcopy" of the edge, node, and
1299
+ graph attributes which attempts to completely copy
1300
+ all of the data and references.
1301
+
1302
+ This is in contrast to the similar G=DiGraph(D) which returns a
1303
+ shallow copy of the data.
1304
+
1305
+ See the Python copy module for more information on shallow
1306
+ and deep copies, https://docs.python.org/3/library/copy.html.
1307
+
1308
+ Warning: If you have subclassed DiGraph to use dict-like objects
1309
+ in the data structure, those changes do not transfer to the
1310
+ Graph created by this method.
1311
+
1312
+ Examples
1313
+ --------
1314
+ >>> G = nx.path_graph(2) # or MultiGraph, etc
1315
+ >>> H = G.to_directed()
1316
+ >>> list(H.edges)
1317
+ [(0, 1), (1, 0)]
1318
+ >>> G2 = H.to_undirected()
1319
+ >>> list(G2.edges)
1320
+ [(0, 1)]
1321
+ """
1322
+ graph_class = self.to_undirected_class()
1323
+ if as_view is True:
1324
+ return nx.graphviews.generic_graph_view(self, graph_class)
1325
+ # deepcopy when not a view
1326
+ G = graph_class()
1327
+ G.graph.update(deepcopy(self.graph))
1328
+ G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
1329
+ if reciprocal is True:
1330
+ G.add_edges_from(
1331
+ (u, v, deepcopy(d))
1332
+ for u, nbrs in self._adj.items()
1333
+ for v, d in nbrs.items()
1334
+ if v in self._pred[u]
1335
+ )
1336
+ else:
1337
+ G.add_edges_from(
1338
+ (u, v, deepcopy(d))
1339
+ for u, nbrs in self._adj.items()
1340
+ for v, d in nbrs.items()
1341
+ )
1342
+ return G
1343
+
1344
+ def reverse(self, copy=True):
1345
+ """Returns the reverse of the graph.
1346
+
1347
+ The reverse is a graph with the same nodes and edges
1348
+ but with the directions of the edges reversed.
1349
+
1350
+ Parameters
1351
+ ----------
1352
+ copy : bool optional (default=True)
1353
+ If True, return a new DiGraph holding the reversed edges.
1354
+ If False, the reverse graph is created using a view of
1355
+ the original graph.
1356
+ """
1357
+ if copy:
1358
+ H = self.__class__()
1359
+ H.graph.update(deepcopy(self.graph))
1360
+ H.add_nodes_from((n, deepcopy(d)) for n, d in self.nodes.items())
1361
+ H.add_edges_from((v, u, deepcopy(d)) for u, v, d in self.edges(data=True))
1362
+ return H
1363
+ return nx.reverse_view(self)
lib/python3.12/site-packages/networkx/classes/filters.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Filter factories to hide or show sets of nodes and edges.
2
+
3
+ These filters return the function used when creating `SubGraph`.
4
+ """
5
+
6
+ __all__ = [
7
+ "no_filter",
8
+ "hide_nodes",
9
+ "hide_edges",
10
+ "hide_multiedges",
11
+ "hide_diedges",
12
+ "hide_multidiedges",
13
+ "show_nodes",
14
+ "show_edges",
15
+ "show_multiedges",
16
+ "show_diedges",
17
+ "show_multidiedges",
18
+ ]
19
+
20
+
21
+ def no_filter(*items):
22
+ """Returns a filter function that always evaluates to True."""
23
+ return True
24
+
25
+
26
+ def hide_nodes(nodes):
27
+ """Returns a filter function that hides specific nodes."""
28
+ nodes = set(nodes)
29
+ return lambda node: node not in nodes
30
+
31
+
32
+ def hide_diedges(edges):
33
+ """Returns a filter function that hides specific directed edges."""
34
+ edges = {(u, v) for u, v in edges}
35
+ return lambda u, v: (u, v) not in edges
36
+
37
+
38
+ def hide_edges(edges):
39
+ """Returns a filter function that hides specific undirected edges."""
40
+ alledges = set(edges) | {(v, u) for (u, v) in edges}
41
+ return lambda u, v: (u, v) not in alledges
42
+
43
+
44
+ def hide_multidiedges(edges):
45
+ """Returns a filter function that hides specific multi-directed edges."""
46
+ edges = {(u, v, k) for u, v, k in edges}
47
+ return lambda u, v, k: (u, v, k) not in edges
48
+
49
+
50
+ def hide_multiedges(edges):
51
+ """Returns a filter function that hides specific multi-undirected edges."""
52
+ alledges = set(edges) | {(v, u, k) for (u, v, k) in edges}
53
+ return lambda u, v, k: (u, v, k) not in alledges
54
+
55
+
56
+ # write show_nodes as a class to make SubGraph pickleable
57
+ class show_nodes:
58
+ """Filter class to show specific nodes.
59
+
60
+ Attach the set of nodes as an attribute to speed up this commonly used filter
61
+
62
+ Note that another allowed attribute for filters is to store the number of nodes
63
+ on the filter as attribute `length` (used in `__len__`). It is a user
64
+ responsibility to ensure this attribute is accurate if present.
65
+ """
66
+
67
+ def __init__(self, nodes):
68
+ self.nodes = set(nodes)
69
+
70
+ def __call__(self, node):
71
+ return node in self.nodes
72
+
73
+
74
+ def show_diedges(edges):
75
+ """Returns a filter function that shows specific directed edges."""
76
+ edges = {(u, v) for u, v in edges}
77
+ return lambda u, v: (u, v) in edges
78
+
79
+
80
+ def show_edges(edges):
81
+ """Returns a filter function that shows specific undirected edges."""
82
+ alledges = set(edges) | {(v, u) for (u, v) in edges}
83
+ return lambda u, v: (u, v) in alledges
84
+
85
+
86
+ def show_multidiedges(edges):
87
+ """Returns a filter function that shows specific multi-directed edges."""
88
+ edges = {(u, v, k) for u, v, k in edges}
89
+ return lambda u, v, k: (u, v, k) in edges
90
+
91
+
92
+ def show_multiedges(edges):
93
+ """Returns a filter function that shows specific multi-undirected edges."""
94
+ alledges = set(edges) | {(v, u, k) for (u, v, k) in edges}
95
+ return lambda u, v, k: (u, v, k) in alledges
lib/python3.12/site-packages/networkx/classes/function.py ADDED
@@ -0,0 +1,1549 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Functional interface to graph methods and assorted utilities."""
2
+
3
+ from collections import Counter
4
+ from itertools import chain
5
+
6
+ import networkx as nx
7
+ from networkx.utils import not_implemented_for, pairwise
8
+
9
+ __all__ = [
10
+ "nodes",
11
+ "edges",
12
+ "degree",
13
+ "degree_histogram",
14
+ "neighbors",
15
+ "number_of_nodes",
16
+ "number_of_edges",
17
+ "density",
18
+ "is_directed",
19
+ "freeze",
20
+ "is_frozen",
21
+ "subgraph",
22
+ "induced_subgraph",
23
+ "edge_subgraph",
24
+ "restricted_view",
25
+ "to_directed",
26
+ "to_undirected",
27
+ "add_star",
28
+ "add_path",
29
+ "add_cycle",
30
+ "create_empty_copy",
31
+ "set_node_attributes",
32
+ "get_node_attributes",
33
+ "remove_node_attributes",
34
+ "set_edge_attributes",
35
+ "get_edge_attributes",
36
+ "remove_edge_attributes",
37
+ "all_neighbors",
38
+ "non_neighbors",
39
+ "non_edges",
40
+ "common_neighbors",
41
+ "is_weighted",
42
+ "is_negatively_weighted",
43
+ "is_empty",
44
+ "selfloop_edges",
45
+ "nodes_with_selfloops",
46
+ "number_of_selfloops",
47
+ "path_weight",
48
+ "is_path",
49
+ "describe",
50
+ ]
51
+
52
+
53
+ def nodes(G):
54
+ """Returns a NodeView over the graph nodes.
55
+
56
+ This function wraps the :func:`G.nodes <networkx.Graph.nodes>` property.
57
+ """
58
+ return G.nodes()
59
+
60
+
61
+ def edges(G, nbunch=None):
62
+ """Returns an edge view of edges incident to nodes in nbunch.
63
+
64
+ Return all edges if nbunch is unspecified or nbunch=None.
65
+
66
+ For digraphs, edges=out_edges
67
+
68
+ This function wraps the :func:`G.edges <networkx.Graph.edges>` property.
69
+ """
70
+ return G.edges(nbunch)
71
+
72
+
73
+ def degree(G, nbunch=None, weight=None):
74
+ """Returns a degree view of single node or of nbunch of nodes.
75
+ If nbunch is omitted, then return degrees of *all* nodes.
76
+
77
+ This function wraps the :func:`G.degree <networkx.Graph.degree>` property.
78
+ """
79
+ return G.degree(nbunch, weight)
80
+
81
+
82
+ def neighbors(G, n):
83
+ """Returns an iterator over all neighbors of node n.
84
+
85
+ This function wraps the :func:`G.neighbors <networkx.Graph.neighbors>` function.
86
+ """
87
+ return G.neighbors(n)
88
+
89
+
90
+ def number_of_nodes(G):
91
+ """Returns the number of nodes in the graph.
92
+
93
+ This function wraps the :func:`G.number_of_nodes <networkx.Graph.number_of_nodes>` function.
94
+ """
95
+ return G.number_of_nodes()
96
+
97
+
98
+ def number_of_edges(G):
99
+ """Returns the number of edges in the graph.
100
+
101
+ This function wraps the :func:`G.number_of_edges <networkx.Graph.number_of_edges>` function.
102
+ """
103
+ return G.number_of_edges()
104
+
105
+
106
+ def density(G):
107
+ r"""Returns the density of a graph.
108
+
109
+ The density for undirected graphs is
110
+
111
+ .. math::
112
+
113
+ d = \frac{2m}{n(n-1)},
114
+
115
+ and for directed graphs is
116
+
117
+ .. math::
118
+
119
+ d = \frac{m}{n(n-1)},
120
+
121
+ where `n` is the number of nodes and `m` is the number of edges in `G`.
122
+
123
+ Notes
124
+ -----
125
+ The density is 0 for a graph without edges and 1 for a complete graph.
126
+ The density of multigraphs can be higher than 1.
127
+
128
+ Self loops are counted in the total number of edges so graphs with self
129
+ loops can have density higher than 1.
130
+ """
131
+ n = number_of_nodes(G)
132
+ m = number_of_edges(G)
133
+ if m == 0 or n <= 1:
134
+ return 0
135
+ d = m / (n * (n - 1))
136
+ if not G.is_directed():
137
+ d *= 2
138
+ return d
139
+
140
+
141
+ def degree_histogram(G):
142
+ """Returns a list of the frequency of each degree value.
143
+
144
+ Parameters
145
+ ----------
146
+ G : Networkx graph
147
+ A graph
148
+
149
+ Returns
150
+ -------
151
+ hist : list
152
+ A list of frequencies of degrees.
153
+ The degree values are the index in the list.
154
+
155
+ Notes
156
+ -----
157
+ Note: the bins are width one, hence len(list) can be large
158
+ (Order(number_of_edges))
159
+ """
160
+ counts = Counter(d for n, d in G.degree())
161
+ return [counts.get(i, 0) for i in range(max(counts) + 1 if counts else 0)]
162
+
163
+
164
+ def is_directed(G):
165
+ """Return True if graph is directed."""
166
+ return G.is_directed()
167
+
168
+
169
+ def frozen(*args, **kwargs):
170
+ """Dummy method for raising errors when trying to modify frozen graphs"""
171
+ raise nx.NetworkXError("Frozen graph can't be modified")
172
+
173
+
174
+ def freeze(G):
175
+ """Modify graph to prevent further change by adding or removing
176
+ nodes or edges.
177
+
178
+ Node and edge data can still be modified.
179
+
180
+ Parameters
181
+ ----------
182
+ G : graph
183
+ A NetworkX graph
184
+
185
+ Examples
186
+ --------
187
+ >>> G = nx.path_graph(4)
188
+ >>> G = nx.freeze(G)
189
+ >>> try:
190
+ ... G.add_edge(4, 5)
191
+ ... except nx.NetworkXError as err:
192
+ ... print(str(err))
193
+ Frozen graph can't be modified
194
+
195
+ Notes
196
+ -----
197
+ To "unfreeze" a graph you must make a copy by creating a new graph object:
198
+
199
+ >>> graph = nx.path_graph(4)
200
+ >>> frozen_graph = nx.freeze(graph)
201
+ >>> unfrozen_graph = nx.Graph(frozen_graph)
202
+ >>> nx.is_frozen(unfrozen_graph)
203
+ False
204
+
205
+ See Also
206
+ --------
207
+ is_frozen
208
+ """
209
+ G.add_node = frozen
210
+ G.add_nodes_from = frozen
211
+ G.remove_node = frozen
212
+ G.remove_nodes_from = frozen
213
+ G.add_edge = frozen
214
+ G.add_edges_from = frozen
215
+ G.add_weighted_edges_from = frozen
216
+ G.remove_edge = frozen
217
+ G.remove_edges_from = frozen
218
+ G.clear = frozen
219
+ G.clear_edges = frozen
220
+ G.frozen = True
221
+ return G
222
+
223
+
224
+ def is_frozen(G):
225
+ """Returns True if graph is frozen.
226
+
227
+ Parameters
228
+ ----------
229
+ G : graph
230
+ A NetworkX graph
231
+
232
+ See Also
233
+ --------
234
+ freeze
235
+ """
236
+ try:
237
+ return G.frozen
238
+ except AttributeError:
239
+ return False
240
+
241
+
242
+ def add_star(G_to_add_to, nodes_for_star, **attr):
243
+ """Add a star to Graph G_to_add_to.
244
+
245
+ The first node in `nodes_for_star` is the middle of the star.
246
+ It is connected to all other nodes.
247
+
248
+ Parameters
249
+ ----------
250
+ G_to_add_to : graph
251
+ A NetworkX graph
252
+ nodes_for_star : iterable container
253
+ A container of nodes.
254
+ attr : keyword arguments, optional (default= no attributes)
255
+ Attributes to add to every edge in star.
256
+
257
+ See Also
258
+ --------
259
+ add_path, add_cycle
260
+
261
+ Examples
262
+ --------
263
+ >>> G = nx.Graph()
264
+ >>> nx.add_star(G, [0, 1, 2, 3])
265
+ >>> nx.add_star(G, [10, 11, 12], weight=2)
266
+ """
267
+ nlist = iter(nodes_for_star)
268
+ try:
269
+ v = next(nlist)
270
+ except StopIteration:
271
+ return
272
+ G_to_add_to.add_node(v)
273
+ edges = ((v, n) for n in nlist)
274
+ G_to_add_to.add_edges_from(edges, **attr)
275
+
276
+
277
+ def add_path(G_to_add_to, nodes_for_path, **attr):
278
+ """Add a path to the Graph G_to_add_to.
279
+
280
+ Parameters
281
+ ----------
282
+ G_to_add_to : graph
283
+ A NetworkX graph
284
+ nodes_for_path : iterable container
285
+ A container of nodes. A path will be constructed from
286
+ the nodes (in order) and added to the graph.
287
+ attr : keyword arguments, optional (default= no attributes)
288
+ Attributes to add to every edge in path.
289
+
290
+ See Also
291
+ --------
292
+ add_star, add_cycle
293
+
294
+ Examples
295
+ --------
296
+ >>> G = nx.Graph()
297
+ >>> nx.add_path(G, [0, 1, 2, 3])
298
+ >>> nx.add_path(G, [10, 11, 12], weight=7)
299
+ """
300
+ nlist = iter(nodes_for_path)
301
+ try:
302
+ first_node = next(nlist)
303
+ except StopIteration:
304
+ return
305
+ G_to_add_to.add_node(first_node)
306
+ G_to_add_to.add_edges_from(pairwise(chain((first_node,), nlist)), **attr)
307
+
308
+
309
+ def add_cycle(G_to_add_to, nodes_for_cycle, **attr):
310
+ """Add a cycle to the Graph G_to_add_to.
311
+
312
+ Parameters
313
+ ----------
314
+ G_to_add_to : graph
315
+ A NetworkX graph
316
+ nodes_for_cycle: iterable container
317
+ A container of nodes. A cycle will be constructed from
318
+ the nodes (in order) and added to the graph.
319
+ attr : keyword arguments, optional (default= no attributes)
320
+ Attributes to add to every edge in cycle.
321
+
322
+ See Also
323
+ --------
324
+ add_path, add_star
325
+
326
+ Examples
327
+ --------
328
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
329
+ >>> nx.add_cycle(G, [0, 1, 2, 3])
330
+ >>> nx.add_cycle(G, [10, 11, 12], weight=7)
331
+ """
332
+ nlist = iter(nodes_for_cycle)
333
+ try:
334
+ first_node = next(nlist)
335
+ except StopIteration:
336
+ return
337
+ G_to_add_to.add_node(first_node)
338
+ G_to_add_to.add_edges_from(
339
+ pairwise(chain((first_node,), nlist), cyclic=True), **attr
340
+ )
341
+
342
+
343
+ def subgraph(G, nbunch):
344
+ """Returns the subgraph induced on nodes in nbunch.
345
+
346
+ Parameters
347
+ ----------
348
+ G : graph
349
+ A NetworkX graph
350
+
351
+ nbunch : list, iterable
352
+ A container of nodes that will be iterated through once (thus
353
+ it should be an iterator or be iterable). Each element of the
354
+ container should be a valid node type: any hashable type except
355
+ None. If nbunch is None, return all edges data in the graph.
356
+ Nodes in nbunch that are not in the graph will be (quietly)
357
+ ignored.
358
+
359
+ Notes
360
+ -----
361
+ subgraph(G) calls G.subgraph()
362
+ """
363
+ return G.subgraph(nbunch)
364
+
365
+
366
+ def induced_subgraph(G, nbunch):
367
+ """Returns a SubGraph view of `G` showing only nodes in nbunch.
368
+
369
+ The induced subgraph of a graph on a set of nodes N is the
370
+ graph with nodes N and edges from G which have both ends in N.
371
+
372
+ Parameters
373
+ ----------
374
+ G : NetworkX Graph
375
+ nbunch : node, container of nodes or None (for all nodes)
376
+
377
+ Returns
378
+ -------
379
+ subgraph : SubGraph View
380
+ A read-only view of the subgraph in `G` induced by the nodes.
381
+ Changes to the graph `G` will be reflected in the view.
382
+
383
+ Notes
384
+ -----
385
+ To create a mutable subgraph with its own copies of nodes
386
+ edges and attributes use `subgraph.copy()` or `Graph(subgraph)`
387
+
388
+ For an inplace reduction of a graph to a subgraph you can remove nodes:
389
+ `G.remove_nodes_from(n in G if n not in set(nbunch))`
390
+
391
+ If you are going to compute subgraphs of your subgraphs you could
392
+ end up with a chain of views that can be very slow once the chain
393
+ has about 15 views in it. If they are all induced subgraphs, you
394
+ can short-cut the chain by making them all subgraphs of the original
395
+ graph. The graph class method `G.subgraph` does this when `G` is
396
+ a subgraph. In contrast, this function allows you to choose to build
397
+ chains or not, as you wish. The returned subgraph is a view on `G`.
398
+
399
+ Examples
400
+ --------
401
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
402
+ >>> H = nx.induced_subgraph(G, [0, 1, 3])
403
+ >>> list(H.edges)
404
+ [(0, 1)]
405
+ >>> list(H.nodes)
406
+ [0, 1, 3]
407
+ """
408
+ induced_nodes = nx.filters.show_nodes(G.nbunch_iter(nbunch))
409
+ return nx.subgraph_view(G, filter_node=induced_nodes)
410
+
411
+
412
+ def edge_subgraph(G, edges):
413
+ """Returns a view of the subgraph induced by the specified edges.
414
+
415
+ The induced subgraph contains each edge in `edges` and each
416
+ node incident to any of those edges.
417
+
418
+ Parameters
419
+ ----------
420
+ G : NetworkX Graph
421
+ edges : iterable
422
+ An iterable of edges. Edges not present in `G` are ignored.
423
+
424
+ Returns
425
+ -------
426
+ subgraph : SubGraph View
427
+ A read-only edge-induced subgraph of `G`.
428
+ Changes to `G` are reflected in the view.
429
+
430
+ Notes
431
+ -----
432
+ To create a mutable subgraph with its own copies of nodes
433
+ edges and attributes use `subgraph.copy()` or `Graph(subgraph)`
434
+
435
+ If you create a subgraph of a subgraph recursively you can end up
436
+ with a chain of subgraphs that becomes very slow with about 15
437
+ nested subgraph views. Luckily the edge_subgraph filter nests
438
+ nicely so you can use the original graph as G in this function
439
+ to avoid chains. We do not rule out chains programmatically so
440
+ that odd cases like an `edge_subgraph` of a `restricted_view`
441
+ can be created.
442
+
443
+ Examples
444
+ --------
445
+ >>> G = nx.path_graph(5)
446
+ >>> H = G.edge_subgraph([(0, 1), (3, 4)])
447
+ >>> list(H.nodes)
448
+ [0, 1, 3, 4]
449
+ >>> list(H.edges)
450
+ [(0, 1), (3, 4)]
451
+ """
452
+ nxf = nx.filters
453
+ edges = set(edges)
454
+ nodes = set()
455
+ for e in edges:
456
+ nodes.update(e[:2])
457
+ induced_nodes = nxf.show_nodes(nodes)
458
+ if G.is_multigraph():
459
+ if G.is_directed():
460
+ induced_edges = nxf.show_multidiedges(edges)
461
+ else:
462
+ induced_edges = nxf.show_multiedges(edges)
463
+ else:
464
+ if G.is_directed():
465
+ induced_edges = nxf.show_diedges(edges)
466
+ else:
467
+ induced_edges = nxf.show_edges(edges)
468
+ return nx.subgraph_view(G, filter_node=induced_nodes, filter_edge=induced_edges)
469
+
470
+
471
+ def restricted_view(G, nodes, edges):
472
+ """Returns a view of `G` with hidden nodes and edges.
473
+
474
+ The resulting subgraph filters out node `nodes` and edges `edges`.
475
+ Filtered out nodes also filter out any of their edges.
476
+
477
+ Parameters
478
+ ----------
479
+ G : NetworkX Graph
480
+ nodes : iterable
481
+ An iterable of nodes. Nodes not present in `G` are ignored.
482
+ edges : iterable
483
+ An iterable of edges. Edges not present in `G` are ignored.
484
+
485
+ Returns
486
+ -------
487
+ subgraph : SubGraph View
488
+ A read-only restricted view of `G` filtering out nodes and edges.
489
+ Changes to `G` are reflected in the view.
490
+
491
+ Notes
492
+ -----
493
+ To create a mutable subgraph with its own copies of nodes
494
+ edges and attributes use `subgraph.copy()` or `Graph(subgraph)`
495
+
496
+ If you create a subgraph of a subgraph recursively you may end up
497
+ with a chain of subgraph views. Such chains can get quite slow
498
+ for lengths near 15. To avoid long chains, try to make your subgraph
499
+ based on the original graph. We do not rule out chains programmatically
500
+ so that odd cases like an `edge_subgraph` of a `restricted_view`
501
+ can be created.
502
+
503
+ Examples
504
+ --------
505
+ >>> G = nx.path_graph(5)
506
+ >>> H = nx.restricted_view(G, [0], [(1, 2), (3, 4)])
507
+ >>> list(H.nodes)
508
+ [1, 2, 3, 4]
509
+ >>> list(H.edges)
510
+ [(2, 3)]
511
+ """
512
+ nxf = nx.filters
513
+ hide_nodes = nxf.hide_nodes(nodes)
514
+ if G.is_multigraph():
515
+ if G.is_directed():
516
+ hide_edges = nxf.hide_multidiedges(edges)
517
+ else:
518
+ hide_edges = nxf.hide_multiedges(edges)
519
+ else:
520
+ if G.is_directed():
521
+ hide_edges = nxf.hide_diedges(edges)
522
+ else:
523
+ hide_edges = nxf.hide_edges(edges)
524
+ return nx.subgraph_view(G, filter_node=hide_nodes, filter_edge=hide_edges)
525
+
526
+
527
+ def to_directed(graph):
528
+ """Returns a directed view of the graph `graph`.
529
+
530
+ Identical to graph.to_directed(as_view=True)
531
+ Note that graph.to_directed defaults to `as_view=False`
532
+ while this function always provides a view.
533
+ """
534
+ return graph.to_directed(as_view=True)
535
+
536
+
537
+ def to_undirected(graph):
538
+ """Returns an undirected view of the graph `graph`.
539
+
540
+ Identical to graph.to_undirected(as_view=True)
541
+ Note that graph.to_undirected defaults to `as_view=False`
542
+ while this function always provides a view.
543
+ """
544
+ return graph.to_undirected(as_view=True)
545
+
546
+
547
+ def create_empty_copy(G, with_data=True):
548
+ """Returns a copy of the graph G with all of the edges removed.
549
+
550
+ Parameters
551
+ ----------
552
+ G : graph
553
+ A NetworkX graph
554
+
555
+ with_data : bool (default=True)
556
+ Propagate Graph and Nodes data to the new graph.
557
+
558
+ See Also
559
+ --------
560
+ empty_graph
561
+
562
+ """
563
+ H = G.__class__()
564
+ H.add_nodes_from(G.nodes(data=with_data))
565
+ if with_data:
566
+ H.graph.update(G.graph)
567
+ return H
568
+
569
+
570
+ @nx._dispatchable(preserve_node_attrs=True, mutates_input=True)
571
+ def set_node_attributes(G, values, name=None):
572
+ """Sets node attributes from a given value or dictionary of values.
573
+
574
+ .. Warning:: The call order of arguments `values` and `name`
575
+ switched between v1.x & v2.x.
576
+
577
+ Parameters
578
+ ----------
579
+ G : NetworkX Graph
580
+
581
+ values : scalar value, dict-like
582
+ What the node attribute should be set to. If `values` is
583
+ not a dictionary, then it is treated as a single attribute value
584
+ that is then applied to every node in `G`. This means that if
585
+ you provide a mutable object, like a list, updates to that object
586
+ will be reflected in the node attribute for every node.
587
+ The attribute name will be `name`.
588
+
589
+ If `values` is a dict or a dict of dict, it should be keyed
590
+ by node to either an attribute value or a dict of attribute key/value
591
+ pairs used to update the node's attributes.
592
+
593
+ name : string (optional, default=None)
594
+ Name of the node attribute to set if values is a scalar.
595
+
596
+ Examples
597
+ --------
598
+ After computing some property of the nodes of a graph, you may want
599
+ to assign a node attribute to store the value of that property for
600
+ each node::
601
+
602
+ >>> G = nx.path_graph(3)
603
+ >>> bb = nx.betweenness_centrality(G)
604
+ >>> isinstance(bb, dict)
605
+ True
606
+ >>> nx.set_node_attributes(G, bb, "betweenness")
607
+ >>> G.nodes[1]["betweenness"]
608
+ 1.0
609
+
610
+ If you provide a list as the second argument, updates to the list
611
+ will be reflected in the node attribute for each node::
612
+
613
+ >>> G = nx.path_graph(3)
614
+ >>> labels = []
615
+ >>> nx.set_node_attributes(G, labels, "labels")
616
+ >>> labels.append("foo")
617
+ >>> G.nodes[0]["labels"]
618
+ ['foo']
619
+ >>> G.nodes[1]["labels"]
620
+ ['foo']
621
+ >>> G.nodes[2]["labels"]
622
+ ['foo']
623
+
624
+ If you provide a dictionary of dictionaries as the second argument,
625
+ the outer dictionary is assumed to be keyed by node to an inner
626
+ dictionary of node attributes for that node::
627
+
628
+ >>> G = nx.path_graph(3)
629
+ >>> attrs = {0: {"attr1": 20, "attr2": "nothing"}, 1: {"attr2": 3}}
630
+ >>> nx.set_node_attributes(G, attrs)
631
+ >>> G.nodes[0]["attr1"]
632
+ 20
633
+ >>> G.nodes[0]["attr2"]
634
+ 'nothing'
635
+ >>> G.nodes[1]["attr2"]
636
+ 3
637
+ >>> G.nodes[2]
638
+ {}
639
+
640
+ Note that if the dictionary contains nodes that are not in `G`, the
641
+ values are silently ignored::
642
+
643
+ >>> G = nx.Graph()
644
+ >>> G.add_node(0)
645
+ >>> nx.set_node_attributes(G, {0: "red", 1: "blue"}, name="color")
646
+ >>> G.nodes[0]["color"]
647
+ 'red'
648
+ >>> 1 in G.nodes
649
+ False
650
+
651
+ """
652
+ # Set node attributes based on type of `values`
653
+ if name is not None: # `values` must not be a dict of dict
654
+ try: # `values` is a dict
655
+ for n, v in values.items():
656
+ try:
657
+ G.nodes[n][name] = values[n]
658
+ except KeyError:
659
+ pass
660
+ except AttributeError: # `values` is a constant
661
+ for n in G:
662
+ G.nodes[n][name] = values
663
+ else: # `values` must be dict of dict
664
+ for n, d in values.items():
665
+ try:
666
+ G.nodes[n].update(d)
667
+ except KeyError:
668
+ pass
669
+ nx._clear_cache(G)
670
+
671
+
672
+ @nx._dispatchable(node_attrs={"name": "default"})
673
+ def get_node_attributes(G, name, default=None):
674
+ """Get node attributes from graph
675
+
676
+ Parameters
677
+ ----------
678
+ G : NetworkX Graph
679
+
680
+ name : string
681
+ Attribute name
682
+
683
+ default: object (default=None)
684
+ Default value of the node attribute if there is no value set for that
685
+ node in graph. If `None` then nodes without this attribute are not
686
+ included in the returned dict.
687
+
688
+ Returns
689
+ -------
690
+ Dictionary of attributes keyed by node.
691
+
692
+ Examples
693
+ --------
694
+ >>> G = nx.Graph()
695
+ >>> G.add_nodes_from([1, 2, 3], color="red")
696
+ >>> color = nx.get_node_attributes(G, "color")
697
+ >>> color[1]
698
+ 'red'
699
+ >>> G.add_node(4)
700
+ >>> color = nx.get_node_attributes(G, "color", default="yellow")
701
+ >>> color[4]
702
+ 'yellow'
703
+ """
704
+ if default is not None:
705
+ return {n: d.get(name, default) for n, d in G.nodes.items()}
706
+ return {n: d[name] for n, d in G.nodes.items() if name in d}
707
+
708
+
709
+ @nx._dispatchable(preserve_node_attrs=True, mutates_input=True)
710
+ def remove_node_attributes(G, *attr_names, nbunch=None):
711
+ """Remove node attributes from all nodes in the graph.
712
+
713
+ Parameters
714
+ ----------
715
+ G : NetworkX Graph
716
+
717
+ *attr_names : List of Strings
718
+ The attribute names to remove from the graph.
719
+
720
+ nbunch : List of Nodes
721
+ Remove the node attributes only from the nodes in this list.
722
+
723
+ Examples
724
+ --------
725
+ >>> G = nx.Graph()
726
+ >>> G.add_nodes_from([1, 2, 3], color="blue")
727
+ >>> nx.get_node_attributes(G, "color")
728
+ {1: 'blue', 2: 'blue', 3: 'blue'}
729
+ >>> nx.remove_node_attributes(G, "color")
730
+ >>> nx.get_node_attributes(G, "color")
731
+ {}
732
+ """
733
+
734
+ if nbunch is None:
735
+ nbunch = G.nodes()
736
+
737
+ for attr in attr_names:
738
+ for n, d in G.nodes(data=True):
739
+ if n in nbunch:
740
+ try:
741
+ del d[attr]
742
+ except KeyError:
743
+ pass
744
+
745
+
746
+ @nx._dispatchable(preserve_edge_attrs=True, mutates_input=True)
747
+ def set_edge_attributes(G, values, name=None):
748
+ """Sets edge attributes from a given value or dictionary of values.
749
+
750
+ .. Warning:: The call order of arguments `values` and `name`
751
+ switched between v1.x & v2.x.
752
+
753
+ Parameters
754
+ ----------
755
+ G : NetworkX Graph
756
+
757
+ values : scalar value, dict-like
758
+ What the edge attribute should be set to. If `values` is
759
+ not a dictionary, then it is treated as a single attribute value
760
+ that is then applied to every edge in `G`. This means that if
761
+ you provide a mutable object, like a list, updates to that object
762
+ will be reflected in the edge attribute for each edge. The attribute
763
+ name will be `name`.
764
+
765
+ If `values` is a dict or a dict of dict, it should be keyed
766
+ by edge tuple to either an attribute value or a dict of attribute
767
+ key/value pairs used to update the edge's attributes.
768
+ For multigraphs, the edge tuples must be of the form ``(u, v, key)``,
769
+ where `u` and `v` are nodes and `key` is the edge key.
770
+ For non-multigraphs, the keys must be tuples of the form ``(u, v)``.
771
+
772
+ name : string (optional, default=None)
773
+ Name of the edge attribute to set if values is a scalar.
774
+
775
+ Examples
776
+ --------
777
+ After computing some property of the edges of a graph, you may want
778
+ to assign a edge attribute to store the value of that property for
779
+ each edge::
780
+
781
+ >>> G = nx.path_graph(3)
782
+ >>> bb = nx.edge_betweenness_centrality(G, normalized=False)
783
+ >>> nx.set_edge_attributes(G, bb, "betweenness")
784
+ >>> G.edges[1, 2]["betweenness"]
785
+ 2.0
786
+
787
+ If you provide a list as the second argument, updates to the list
788
+ will be reflected in the edge attribute for each edge::
789
+
790
+ >>> labels = []
791
+ >>> nx.set_edge_attributes(G, labels, "labels")
792
+ >>> labels.append("foo")
793
+ >>> G.edges[0, 1]["labels"]
794
+ ['foo']
795
+ >>> G.edges[1, 2]["labels"]
796
+ ['foo']
797
+
798
+ If you provide a dictionary of dictionaries as the second argument,
799
+ the entire dictionary will be used to update edge attributes::
800
+
801
+ >>> G = nx.path_graph(3)
802
+ >>> attrs = {(0, 1): {"attr1": 20, "attr2": "nothing"}, (1, 2): {"attr2": 3}}
803
+ >>> nx.set_edge_attributes(G, attrs)
804
+ >>> G[0][1]["attr1"]
805
+ 20
806
+ >>> G[0][1]["attr2"]
807
+ 'nothing'
808
+ >>> G[1][2]["attr2"]
809
+ 3
810
+
811
+ The attributes of one Graph can be used to set those of another.
812
+
813
+ >>> H = nx.path_graph(3)
814
+ >>> nx.set_edge_attributes(H, G.edges)
815
+
816
+ Note that if the dict contains edges that are not in `G`, they are
817
+ silently ignored::
818
+
819
+ >>> G = nx.Graph([(0, 1)])
820
+ >>> nx.set_edge_attributes(G, {(1, 2): {"weight": 2.0}})
821
+ >>> (1, 2) in G.edges()
822
+ False
823
+
824
+ For multigraphs, the `values` dict is expected to be keyed by 3-tuples
825
+ including the edge key::
826
+
827
+ >>> MG = nx.MultiGraph()
828
+ >>> edges = [(0, 1), (0, 1)]
829
+ >>> MG.add_edges_from(edges) # Returns list of edge keys
830
+ [0, 1]
831
+ >>> attributes = {(0, 1, 0): {"cost": 21}, (0, 1, 1): {"cost": 7}}
832
+ >>> nx.set_edge_attributes(MG, attributes)
833
+ >>> MG[0][1][0]["cost"]
834
+ 21
835
+ >>> MG[0][1][1]["cost"]
836
+ 7
837
+
838
+ If MultiGraph attributes are desired for a Graph, you must convert the 3-tuple
839
+ multiedge to a 2-tuple edge and the last multiedge's attribute value will
840
+ overwrite the previous values. Continuing from the previous case we get::
841
+
842
+ >>> H = nx.path_graph([0, 1, 2])
843
+ >>> nx.set_edge_attributes(H, {(u, v): ed for u, v, ed in MG.edges.data()})
844
+ >>> nx.get_edge_attributes(H, "cost")
845
+ {(0, 1): 7}
846
+
847
+ """
848
+ if name is not None:
849
+ # `values` does not contain attribute names
850
+ try:
851
+ # if `values` is a dict using `.items()` => {edge: value}
852
+ if G.is_multigraph():
853
+ for (u, v, key), value in values.items():
854
+ try:
855
+ G._adj[u][v][key][name] = value
856
+ except KeyError:
857
+ pass
858
+ else:
859
+ for (u, v), value in values.items():
860
+ try:
861
+ G._adj[u][v][name] = value
862
+ except KeyError:
863
+ pass
864
+ except AttributeError:
865
+ # treat `values` as a constant
866
+ for u, v, data in G.edges(data=True):
867
+ data[name] = values
868
+ else:
869
+ # `values` consists of doct-of-dict {edge: {attr: value}} shape
870
+ if G.is_multigraph():
871
+ for (u, v, key), d in values.items():
872
+ try:
873
+ G._adj[u][v][key].update(d)
874
+ except KeyError:
875
+ pass
876
+ else:
877
+ for (u, v), d in values.items():
878
+ try:
879
+ G._adj[u][v].update(d)
880
+ except KeyError:
881
+ pass
882
+ nx._clear_cache(G)
883
+
884
+
885
+ @nx._dispatchable(edge_attrs={"name": "default"})
886
+ def get_edge_attributes(G, name, default=None):
887
+ """Get edge attributes from graph
888
+
889
+ Parameters
890
+ ----------
891
+ G : NetworkX Graph
892
+
893
+ name : string
894
+ Attribute name
895
+
896
+ default: object (default=None)
897
+ Default value of the edge attribute if there is no value set for that
898
+ edge in graph. If `None` then edges without this attribute are not
899
+ included in the returned dict.
900
+
901
+ Returns
902
+ -------
903
+ Dictionary of attributes keyed by edge. For (di)graphs, the keys are
904
+ 2-tuples of the form: (u, v). For multi(di)graphs, the keys are 3-tuples of
905
+ the form: (u, v, key).
906
+
907
+ Examples
908
+ --------
909
+ >>> G = nx.Graph()
910
+ >>> nx.add_path(G, [1, 2, 3], color="red")
911
+ >>> color = nx.get_edge_attributes(G, "color")
912
+ >>> color[(1, 2)]
913
+ 'red'
914
+ >>> G.add_edge(3, 4)
915
+ >>> color = nx.get_edge_attributes(G, "color", default="yellow")
916
+ >>> color[(3, 4)]
917
+ 'yellow'
918
+ """
919
+ if G.is_multigraph():
920
+ edges = G.edges(keys=True, data=True)
921
+ else:
922
+ edges = G.edges(data=True)
923
+ if default is not None:
924
+ return {x[:-1]: x[-1].get(name, default) for x in edges}
925
+ return {x[:-1]: x[-1][name] for x in edges if name in x[-1]}
926
+
927
+
928
+ @nx._dispatchable(preserve_edge_attrs=True, mutates_input=True)
929
+ def remove_edge_attributes(G, *attr_names, ebunch=None):
930
+ """Remove edge attributes from all edges in the graph.
931
+
932
+ Parameters
933
+ ----------
934
+ G : NetworkX Graph
935
+
936
+ *attr_names : List of Strings
937
+ The attribute names to remove from the graph.
938
+
939
+ Examples
940
+ --------
941
+ >>> G = nx.path_graph(3)
942
+ >>> nx.set_edge_attributes(G, {(u, v): u + v for u, v in G.edges()}, name="weight")
943
+ >>> nx.get_edge_attributes(G, "weight")
944
+ {(0, 1): 1, (1, 2): 3}
945
+ >>> remove_edge_attributes(G, "weight")
946
+ >>> nx.get_edge_attributes(G, "weight")
947
+ {}
948
+ """
949
+ if ebunch is None:
950
+ ebunch = G.edges(keys=True) if G.is_multigraph() else G.edges()
951
+
952
+ for attr in attr_names:
953
+ edges = (
954
+ G.edges(keys=True, data=True) if G.is_multigraph() else G.edges(data=True)
955
+ )
956
+ for *e, d in edges:
957
+ if tuple(e) in ebunch:
958
+ try:
959
+ del d[attr]
960
+ except KeyError:
961
+ pass
962
+
963
+
964
+ def all_neighbors(graph, node):
965
+ """Returns all of the neighbors of a node in the graph.
966
+
967
+ If the graph is directed returns predecessors as well as successors.
968
+
969
+ Parameters
970
+ ----------
971
+ graph : NetworkX graph
972
+ Graph to find neighbors.
973
+ node : node
974
+ The node whose neighbors will be returned.
975
+
976
+ Returns
977
+ -------
978
+ neighbors : iterator
979
+ Iterator of neighbors
980
+
981
+ Raises
982
+ ------
983
+ NetworkXError
984
+ If `node` is not in the graph.
985
+
986
+ Examples
987
+ --------
988
+ For undirected graphs, this function is equivalent to ``G.neighbors(node)``.
989
+
990
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
991
+ >>> list(nx.all_neighbors(G, 1))
992
+ [0, 2]
993
+
994
+ For directed graphs, this function returns both predecessors and successors,
995
+ which may include duplicates if a node is both a predecessor and successor
996
+ (e.g., in bidirectional edges or self-loops).
997
+
998
+ >>> DG = nx.DiGraph([(0, 1), (1, 2), (2, 1)])
999
+ >>> list(nx.all_neighbors(DG, 1))
1000
+ [0, 2, 2]
1001
+
1002
+ Notes
1003
+ -----
1004
+ This function iterates over all neighbors (both predecessors and successors).
1005
+
1006
+ See Also
1007
+ --------
1008
+ Graph.neighbors : Returns successors for both Graph and DiGraph
1009
+ DiGraph.predecessors : Returns predecessors for directed graphs only
1010
+ DiGraph.successors : Returns successors for directed graphs only
1011
+ """
1012
+ if graph.is_directed():
1013
+ values = chain(graph.predecessors(node), graph.successors(node))
1014
+ else:
1015
+ values = graph.neighbors(node)
1016
+ return values
1017
+
1018
+
1019
+ def non_neighbors(graph, node):
1020
+ """Returns the non-neighbors of the node in the graph.
1021
+
1022
+ Parameters
1023
+ ----------
1024
+ graph : NetworkX graph
1025
+ Graph to find neighbors.
1026
+
1027
+ node : node
1028
+ The node whose neighbors will be returned.
1029
+
1030
+ Returns
1031
+ -------
1032
+ non_neighbors : set
1033
+ Set of nodes in the graph that are not neighbors of the node.
1034
+ """
1035
+ return graph._adj.keys() - graph._adj[node].keys() - {node}
1036
+
1037
+
1038
+ def non_edges(graph):
1039
+ """Returns the nonexistent edges in the graph.
1040
+
1041
+ Parameters
1042
+ ----------
1043
+ graph : NetworkX graph.
1044
+ Graph to find nonexistent edges.
1045
+
1046
+ Returns
1047
+ -------
1048
+ non_edges : iterator
1049
+ Iterator of edges that are not in the graph.
1050
+ """
1051
+ if graph.is_directed():
1052
+ for u in graph:
1053
+ for v in non_neighbors(graph, u):
1054
+ yield (u, v)
1055
+ else:
1056
+ nodes = set(graph)
1057
+ while nodes:
1058
+ u = nodes.pop()
1059
+ for v in nodes - set(graph[u]):
1060
+ yield (u, v)
1061
+
1062
+
1063
+ @not_implemented_for("directed")
1064
+ def common_neighbors(G, u, v):
1065
+ """Returns the common neighbors of two nodes in a graph.
1066
+
1067
+ Parameters
1068
+ ----------
1069
+ G : graph
1070
+ A NetworkX undirected graph.
1071
+
1072
+ u, v : nodes
1073
+ Nodes in the graph.
1074
+
1075
+ Returns
1076
+ -------
1077
+ cnbors : set
1078
+ Set of common neighbors of u and v in the graph.
1079
+
1080
+ Raises
1081
+ ------
1082
+ NetworkXError
1083
+ If u or v is not a node in the graph.
1084
+
1085
+ Examples
1086
+ --------
1087
+ >>> G = nx.complete_graph(5)
1088
+ >>> sorted(nx.common_neighbors(G, 0, 1))
1089
+ [2, 3, 4]
1090
+ """
1091
+ if u not in G:
1092
+ raise nx.NetworkXError("u is not in the graph.")
1093
+ if v not in G:
1094
+ raise nx.NetworkXError("v is not in the graph.")
1095
+
1096
+ return G._adj[u].keys() & G._adj[v].keys() - {u, v}
1097
+
1098
+
1099
+ @nx._dispatchable(preserve_edge_attrs=True)
1100
+ def is_weighted(G, edge=None, weight="weight"):
1101
+ """Returns True if `G` has weighted edges.
1102
+
1103
+ Parameters
1104
+ ----------
1105
+ G : graph
1106
+ A NetworkX graph.
1107
+
1108
+ edge : tuple, optional
1109
+ A 2-tuple specifying the only edge in `G` that will be tested. If
1110
+ None, then every edge in `G` is tested.
1111
+
1112
+ weight: string, optional
1113
+ The attribute name used to query for edge weights.
1114
+
1115
+ Returns
1116
+ -------
1117
+ bool
1118
+ A boolean signifying if `G`, or the specified edge, is weighted.
1119
+
1120
+ Raises
1121
+ ------
1122
+ NetworkXError
1123
+ If the specified edge does not exist.
1124
+
1125
+ Examples
1126
+ --------
1127
+ >>> G = nx.path_graph(4)
1128
+ >>> nx.is_weighted(G)
1129
+ False
1130
+ >>> nx.is_weighted(G, (2, 3))
1131
+ False
1132
+
1133
+ >>> G = nx.DiGraph()
1134
+ >>> G.add_edge(1, 2, weight=1)
1135
+ >>> nx.is_weighted(G)
1136
+ True
1137
+
1138
+ """
1139
+ if edge is not None:
1140
+ data = G.get_edge_data(*edge)
1141
+ if data is None:
1142
+ msg = f"Edge {edge!r} does not exist."
1143
+ raise nx.NetworkXError(msg)
1144
+ return weight in data
1145
+
1146
+ if is_empty(G):
1147
+ # Special handling required since: all([]) == True
1148
+ return False
1149
+
1150
+ return all(weight in data for u, v, data in G.edges(data=True))
1151
+
1152
+
1153
+ @nx._dispatchable(edge_attrs="weight")
1154
+ def is_negatively_weighted(G, edge=None, weight="weight"):
1155
+ """Returns True if `G` has negatively weighted edges.
1156
+
1157
+ Parameters
1158
+ ----------
1159
+ G : graph
1160
+ A NetworkX graph.
1161
+
1162
+ edge : tuple, optional
1163
+ A 2-tuple specifying the only edge in `G` that will be tested. If
1164
+ None, then every edge in `G` is tested.
1165
+
1166
+ weight: string, optional
1167
+ The attribute name used to query for edge weights.
1168
+
1169
+ Returns
1170
+ -------
1171
+ bool
1172
+ A boolean signifying if `G`, or the specified edge, is negatively
1173
+ weighted.
1174
+
1175
+ Raises
1176
+ ------
1177
+ NetworkXError
1178
+ If the specified edge does not exist.
1179
+
1180
+ Examples
1181
+ --------
1182
+ >>> G = nx.Graph()
1183
+ >>> G.add_edges_from([(1, 3), (2, 4), (2, 6)])
1184
+ >>> G.add_edge(1, 2, weight=4)
1185
+ >>> nx.is_negatively_weighted(G, (1, 2))
1186
+ False
1187
+ >>> G[2][4]["weight"] = -2
1188
+ >>> nx.is_negatively_weighted(G)
1189
+ True
1190
+ >>> G = nx.DiGraph()
1191
+ >>> edges = [("0", "3", 3), ("0", "1", -5), ("1", "0", -2)]
1192
+ >>> G.add_weighted_edges_from(edges)
1193
+ >>> nx.is_negatively_weighted(G)
1194
+ True
1195
+
1196
+ """
1197
+ if edge is not None:
1198
+ data = G.get_edge_data(*edge)
1199
+ if data is None:
1200
+ msg = f"Edge {edge!r} does not exist."
1201
+ raise nx.NetworkXError(msg)
1202
+ return weight in data and data[weight] < 0
1203
+
1204
+ return any(weight in data and data[weight] < 0 for u, v, data in G.edges(data=True))
1205
+
1206
+
1207
+ @nx._dispatchable
1208
+ def is_empty(G):
1209
+ """Returns True if `G` has no edges.
1210
+
1211
+ Parameters
1212
+ ----------
1213
+ G : graph
1214
+ A NetworkX graph.
1215
+
1216
+ Returns
1217
+ -------
1218
+ bool
1219
+ True if `G` has no edges, and False otherwise.
1220
+
1221
+ Notes
1222
+ -----
1223
+ An empty graph can have nodes but not edges. The empty graph with zero
1224
+ nodes is known as the null graph. This is an $O(n)$ operation where n
1225
+ is the number of nodes in the graph.
1226
+
1227
+ """
1228
+ return not any(G._adj.values())
1229
+
1230
+
1231
+ def nodes_with_selfloops(G):
1232
+ """Returns an iterator over nodes with self loops.
1233
+
1234
+ A node with a self loop has an edge with both ends adjacent
1235
+ to that node.
1236
+
1237
+ Returns
1238
+ -------
1239
+ nodelist : iterator
1240
+ A iterator over nodes with self loops.
1241
+
1242
+ See Also
1243
+ --------
1244
+ selfloop_edges, number_of_selfloops
1245
+
1246
+ Examples
1247
+ --------
1248
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
1249
+ >>> G.add_edge(1, 1)
1250
+ >>> G.add_edge(1, 2)
1251
+ >>> list(nx.nodes_with_selfloops(G))
1252
+ [1]
1253
+
1254
+ """
1255
+ return (n for n, nbrs in G._adj.items() if n in nbrs)
1256
+
1257
+
1258
+ def selfloop_edges(G, data=False, keys=False, default=None):
1259
+ """Returns an iterator over selfloop edges.
1260
+
1261
+ A selfloop edge has the same node at both ends.
1262
+
1263
+ Parameters
1264
+ ----------
1265
+ G : graph
1266
+ A NetworkX graph.
1267
+ data : string or bool, optional (default=False)
1268
+ Return selfloop edges as two tuples (u, v) (data=False)
1269
+ or three-tuples (u, v, datadict) (data=True)
1270
+ or three-tuples (u, v, datavalue) (data='attrname')
1271
+ keys : bool, optional (default=False)
1272
+ If True, return edge keys with each edge.
1273
+ default : value, optional (default=None)
1274
+ Value used for edges that don't have the requested attribute.
1275
+ Only relevant if data is not True or False.
1276
+
1277
+ Returns
1278
+ -------
1279
+ edgeiter : iterator over edge tuples
1280
+ An iterator over all selfloop edges.
1281
+
1282
+ See Also
1283
+ --------
1284
+ nodes_with_selfloops, number_of_selfloops
1285
+
1286
+ Examples
1287
+ --------
1288
+ >>> G = nx.MultiGraph() # or Graph, DiGraph, MultiDiGraph, etc
1289
+ >>> ekey = G.add_edge(1, 1)
1290
+ >>> ekey = G.add_edge(1, 2)
1291
+ >>> list(nx.selfloop_edges(G))
1292
+ [(1, 1)]
1293
+ >>> list(nx.selfloop_edges(G, data=True))
1294
+ [(1, 1, {})]
1295
+ >>> list(nx.selfloop_edges(G, keys=True))
1296
+ [(1, 1, 0)]
1297
+ >>> list(nx.selfloop_edges(G, keys=True, data=True))
1298
+ [(1, 1, 0, {})]
1299
+ """
1300
+ if data is True:
1301
+ if G.is_multigraph():
1302
+ if keys is True:
1303
+ return (
1304
+ (n, n, k, d)
1305
+ for n, nbrs in G._adj.items()
1306
+ if n in nbrs
1307
+ for k, d in nbrs[n].items()
1308
+ )
1309
+ else:
1310
+ return (
1311
+ (n, n, d)
1312
+ for n, nbrs in G._adj.items()
1313
+ if n in nbrs
1314
+ for d in nbrs[n].values()
1315
+ )
1316
+ else:
1317
+ return ((n, n, nbrs[n]) for n, nbrs in G._adj.items() if n in nbrs)
1318
+ elif data is not False:
1319
+ if G.is_multigraph():
1320
+ if keys is True:
1321
+ return (
1322
+ (n, n, k, d.get(data, default))
1323
+ for n, nbrs in G._adj.items()
1324
+ if n in nbrs
1325
+ for k, d in nbrs[n].items()
1326
+ )
1327
+ else:
1328
+ return (
1329
+ (n, n, d.get(data, default))
1330
+ for n, nbrs in G._adj.items()
1331
+ if n in nbrs
1332
+ for d in nbrs[n].values()
1333
+ )
1334
+ else:
1335
+ return (
1336
+ (n, n, nbrs[n].get(data, default))
1337
+ for n, nbrs in G._adj.items()
1338
+ if n in nbrs
1339
+ )
1340
+ else:
1341
+ if G.is_multigraph():
1342
+ if keys is True:
1343
+ return (
1344
+ (n, n, k)
1345
+ for n, nbrs in G._adj.items()
1346
+ if n in nbrs
1347
+ for k in nbrs[n]
1348
+ )
1349
+ else:
1350
+ return (
1351
+ (n, n)
1352
+ for n, nbrs in G._adj.items()
1353
+ if n in nbrs
1354
+ for i in range(len(nbrs[n])) # for easy edge removal (#4068)
1355
+ )
1356
+ else:
1357
+ return ((n, n) for n, nbrs in G._adj.items() if n in nbrs)
1358
+
1359
+
1360
+ @nx._dispatchable
1361
+ def number_of_selfloops(G):
1362
+ """Returns the number of selfloop edges.
1363
+
1364
+ A selfloop edge has the same node at both ends.
1365
+
1366
+ Returns
1367
+ -------
1368
+ nloops : int
1369
+ The number of selfloops.
1370
+
1371
+ See Also
1372
+ --------
1373
+ nodes_with_selfloops, selfloop_edges
1374
+
1375
+ Examples
1376
+ --------
1377
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
1378
+ >>> G.add_edge(1, 1)
1379
+ >>> G.add_edge(1, 2)
1380
+ >>> nx.number_of_selfloops(G)
1381
+ 1
1382
+ """
1383
+ return sum(1 for _ in nx.selfloop_edges(G))
1384
+
1385
+
1386
+ def is_path(G, path):
1387
+ """Returns whether or not the specified path exists.
1388
+
1389
+ For it to return True, every node on the path must exist and
1390
+ each consecutive pair must be connected via one or more edges.
1391
+
1392
+ Parameters
1393
+ ----------
1394
+ G : graph
1395
+ A NetworkX graph.
1396
+
1397
+ path : list
1398
+ A list of nodes which defines the path to traverse
1399
+
1400
+ Returns
1401
+ -------
1402
+ bool
1403
+ True if `path` is a valid path in `G`
1404
+
1405
+ """
1406
+ try:
1407
+ return all(nbr in G._adj[node] for node, nbr in nx.utils.pairwise(path))
1408
+ except (KeyError, TypeError):
1409
+ return False
1410
+
1411
+
1412
+ def path_weight(G, path, weight):
1413
+ """Returns total cost associated with specified path and weight
1414
+
1415
+ Parameters
1416
+ ----------
1417
+ G : graph
1418
+ A NetworkX graph.
1419
+
1420
+ path: list
1421
+ A list of node labels which defines the path to traverse
1422
+
1423
+ weight: string
1424
+ A string indicating which edge attribute to use for path cost
1425
+
1426
+ Returns
1427
+ -------
1428
+ cost: int or float
1429
+ An integer or a float representing the total cost with respect to the
1430
+ specified weight of the specified path
1431
+
1432
+ Raises
1433
+ ------
1434
+ NetworkXNoPath
1435
+ If the specified edge does not exist.
1436
+ """
1437
+ multigraph = G.is_multigraph()
1438
+ cost = 0
1439
+
1440
+ if not nx.is_path(G, path):
1441
+ raise nx.NetworkXNoPath("path does not exist")
1442
+ for node, nbr in nx.utils.pairwise(path):
1443
+ if multigraph:
1444
+ cost += min(v[weight] for v in G._adj[node][nbr].values())
1445
+ else:
1446
+ cost += G._adj[node][nbr][weight]
1447
+ return cost
1448
+
1449
+
1450
+ def describe(G, describe_hook=None):
1451
+ """Prints a description of the graph G.
1452
+
1453
+ By default, the description includes some basic properties of the graph.
1454
+ You can also provide additional functions to compute and include
1455
+ more properties in the description.
1456
+
1457
+ Parameters
1458
+ ----------
1459
+ G : graph
1460
+ A NetworkX graph.
1461
+
1462
+ describe_hook: callable, optional (default=None)
1463
+ A function that takes a graph as input and returns a
1464
+ dictionary of additional properties to include in the description.
1465
+ The keys of the dictionary are the property names, and the values
1466
+ are the corresponding property values.
1467
+
1468
+ Examples
1469
+ --------
1470
+ >>> G = nx.path_graph(5)
1471
+ >>> nx.describe(G)
1472
+ Number of nodes : 5
1473
+ Number of edges : 4
1474
+ Directed : False
1475
+ Multigraph : False
1476
+ Tree : True
1477
+ Bipartite : True
1478
+ Average degree (min, max) : 1.60 (1, 2)
1479
+ Number of connected components : 1
1480
+
1481
+ >>> def augment_description(G):
1482
+ ... return {"Average Shortest Path Length": nx.average_shortest_path_length(G)}
1483
+ >>> nx.describe(G, describe_hook=augment_description)
1484
+ Number of nodes : 5
1485
+ Number of edges : 4
1486
+ Directed : False
1487
+ Multigraph : False
1488
+ Tree : True
1489
+ Bipartite : True
1490
+ Average degree (min, max) : 1.60 (1, 2)
1491
+ Number of connected components : 1
1492
+ Average Shortest Path Length : 2.0
1493
+
1494
+ >>> G.name = "Path Graph of 5 nodes"
1495
+ >>> nx.describe(G)
1496
+ Name of Graph : Path Graph of 5 nodes
1497
+ Number of nodes : 5
1498
+ Number of edges : 4
1499
+ Directed : False
1500
+ Multigraph : False
1501
+ Tree : True
1502
+ Bipartite : True
1503
+ Average degree (min, max) : 1.60 (1, 2)
1504
+ Number of connected components : 1
1505
+
1506
+ """
1507
+ info_dict = _create_describe_info_dict(G)
1508
+
1509
+ if describe_hook is not None:
1510
+ additional_info = describe_hook(G)
1511
+ info_dict.update(additional_info)
1512
+
1513
+ max_key_len = max(len(k) for k in info_dict)
1514
+ for key, val in info_dict.items():
1515
+ print(f"{key:<{max_key_len}} : {val}")
1516
+
1517
+
1518
+ def _create_describe_info_dict(G):
1519
+ info = {}
1520
+ if G.name != "":
1521
+ info["Name of Graph"] = G.name
1522
+ info.update(
1523
+ {
1524
+ "Number of nodes": len(G),
1525
+ "Number of edges": G.number_of_edges(),
1526
+ "Directed": G.is_directed(),
1527
+ "Multigraph": G.is_multigraph(),
1528
+ "Tree": nx.is_tree(G),
1529
+ "Bipartite": nx.is_bipartite(G),
1530
+ }
1531
+ )
1532
+ if len(G) == 0:
1533
+ return info
1534
+
1535
+ degree_values = dict(nx.degree(G)).values()
1536
+ avg_degree = sum(degree_values) / len(G)
1537
+ max_degree, min_degree = max(degree_values), min(degree_values)
1538
+ info["Average degree (min, max)"] = f"{avg_degree:.2f} ({min_degree}, {max_degree})"
1539
+
1540
+ if G.is_directed():
1541
+ info["Number of strongly connected components"] = (
1542
+ nx.number_strongly_connected_components(G)
1543
+ )
1544
+ info["Number of weakly connected components"] = (
1545
+ nx.number_weakly_connected_components(G)
1546
+ )
1547
+ else:
1548
+ info["Number of connected components"] = nx.number_connected_components(G)
1549
+ return info
lib/python3.12/site-packages/networkx/classes/graph.py ADDED
@@ -0,0 +1,2082 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Base class for undirected graphs.
2
+
3
+ The Graph class allows any hashable object as a node
4
+ and can associate key/value attribute pairs with each undirected edge.
5
+
6
+ Self-loops are allowed but multiple edges are not (see MultiGraph).
7
+
8
+ For directed graphs see DiGraph and MultiDiGraph.
9
+ """
10
+
11
+ from copy import deepcopy
12
+ from functools import cached_property
13
+
14
+ import networkx as nx
15
+ from networkx import convert
16
+ from networkx.classes.coreviews import AdjacencyView
17
+ from networkx.classes.reportviews import DegreeView, EdgeView, NodeView
18
+ from networkx.exception import NetworkXError
19
+
20
+ __all__ = ["Graph"]
21
+
22
+
23
+ class _CachedPropertyResetterAdj:
24
+ """Data Descriptor class for _adj that resets ``adj`` cached_property when needed
25
+
26
+ This assumes that the ``cached_property`` ``G.adj`` should be reset whenever
27
+ ``G._adj`` is set to a new value.
28
+
29
+ This object sits on a class and ensures that any instance of that
30
+ class clears its cached property "adj" whenever the underlying
31
+ instance attribute "_adj" is set to a new object. It only affects
32
+ the set process of the obj._adj attribute. All get/del operations
33
+ act as they normally would.
34
+
35
+ For info on Data Descriptors see: https://docs.python.org/3/howto/descriptor.html
36
+ """
37
+
38
+ def __set__(self, obj, value):
39
+ od = obj.__dict__
40
+ od["_adj"] = value
41
+ # reset cached properties
42
+ props = ["adj", "edges", "degree"]
43
+ for prop in props:
44
+ if prop in od:
45
+ del od[prop]
46
+
47
+
48
+ class _CachedPropertyResetterNode:
49
+ """Data Descriptor class for _node that resets ``nodes`` cached_property when needed
50
+
51
+ This assumes that the ``cached_property`` ``G.node`` should be reset whenever
52
+ ``G._node`` is set to a new value.
53
+
54
+ This object sits on a class and ensures that any instance of that
55
+ class clears its cached property "nodes" whenever the underlying
56
+ instance attribute "_node" is set to a new object. It only affects
57
+ the set process of the obj._adj attribute. All get/del operations
58
+ act as they normally would.
59
+
60
+ For info on Data Descriptors see: https://docs.python.org/3/howto/descriptor.html
61
+ """
62
+
63
+ def __set__(self, obj, value):
64
+ od = obj.__dict__
65
+ od["_node"] = value
66
+ # reset cached properties
67
+ if "nodes" in od:
68
+ del od["nodes"]
69
+
70
+
71
+ class Graph:
72
+ """
73
+ Base class for undirected graphs.
74
+
75
+ A Graph stores nodes and edges with optional data, or attributes.
76
+
77
+ Graphs hold undirected edges. Self loops are allowed but multiple
78
+ (parallel) edges are not.
79
+
80
+ Nodes can be arbitrary (hashable) Python objects with optional
81
+ key/value attributes, except that `None` is not allowed as a node.
82
+
83
+ Edges are represented as links between nodes with optional
84
+ key/value attributes.
85
+
86
+ Parameters
87
+ ----------
88
+ incoming_graph_data : input graph (optional, default: None)
89
+ Data to initialize graph. If None (default) an empty
90
+ graph is created. The data can be any format that is supported
91
+ by the to_networkx_graph() function, currently including edge list,
92
+ dict of dicts, dict of lists, NetworkX graph, 2D NumPy array, SciPy
93
+ sparse matrix, or PyGraphviz graph.
94
+
95
+ attr : keyword arguments, optional (default= no attributes)
96
+ Attributes to add to graph as key=value pairs.
97
+
98
+ See Also
99
+ --------
100
+ DiGraph
101
+ MultiGraph
102
+ MultiDiGraph
103
+
104
+ Examples
105
+ --------
106
+ Create an empty graph structure (a "null graph") with no nodes and
107
+ no edges.
108
+
109
+ >>> G = nx.Graph()
110
+
111
+ G can be grown in several ways.
112
+
113
+ **Nodes:**
114
+
115
+ Add one node at a time:
116
+
117
+ >>> G.add_node(1)
118
+
119
+ Add the nodes from any container (a list, dict, set or
120
+ even the lines from a file or the nodes from another graph).
121
+
122
+ >>> G.add_nodes_from([2, 3])
123
+ >>> G.add_nodes_from(range(100, 110))
124
+ >>> H = nx.path_graph(10)
125
+ >>> G.add_nodes_from(H)
126
+
127
+ In addition to strings and integers any hashable Python object
128
+ (except None) can represent a node, e.g. a customized node object,
129
+ or even another Graph.
130
+
131
+ >>> G.add_node(H)
132
+
133
+ **Edges:**
134
+
135
+ G can also be grown by adding edges.
136
+
137
+ Add one edge,
138
+
139
+ >>> G.add_edge(1, 2)
140
+
141
+ a list of edges,
142
+
143
+ >>> G.add_edges_from([(1, 2), (1, 3)])
144
+
145
+ or a collection of edges,
146
+
147
+ >>> G.add_edges_from(H.edges)
148
+
149
+ If some edges connect nodes not yet in the graph, the nodes
150
+ are added automatically. There are no errors when adding
151
+ nodes or edges that already exist.
152
+
153
+ **Attributes:**
154
+
155
+ Each graph, node, and edge can hold key/value attribute pairs
156
+ in an associated attribute dictionary (the keys must be hashable).
157
+ By default these are empty, but can be added or changed using
158
+ add_edge, add_node or direct manipulation of the attribute
159
+ dictionaries named graph, node and edge respectively.
160
+
161
+ >>> G = nx.Graph(day="Friday")
162
+ >>> G.graph
163
+ {'day': 'Friday'}
164
+
165
+ Add node attributes using add_node(), add_nodes_from() or G.nodes
166
+
167
+ >>> G.add_node(1, time="5pm")
168
+ >>> G.add_nodes_from([3], time="2pm")
169
+ >>> G.nodes[1]
170
+ {'time': '5pm'}
171
+ >>> G.nodes[1]["room"] = 714 # node must exist already to use G.nodes
172
+ >>> del G.nodes[1]["room"] # remove attribute
173
+ >>> list(G.nodes(data=True))
174
+ [(1, {'time': '5pm'}), (3, {'time': '2pm'})]
175
+
176
+ Add edge attributes using add_edge(), add_edges_from(), subscript
177
+ notation, or G.edges.
178
+
179
+ >>> G.add_edge(1, 2, weight=4.7)
180
+ >>> G.add_edges_from([(3, 4), (4, 5)], color="red")
181
+ >>> G.add_edges_from([(1, 2, {"color": "blue"}), (2, 3, {"weight": 8})])
182
+ >>> G[1][2]["weight"] = 4.7
183
+ >>> G.edges[1, 2]["weight"] = 4
184
+
185
+ Warning: we protect the graph data structure by making `G.edges` a
186
+ read-only dict-like structure. However, you can assign to attributes
187
+ in e.g. `G.edges[1, 2]`. Thus, use 2 sets of brackets to add/change
188
+ data attributes: `G.edges[1, 2]['weight'] = 4`
189
+ (For multigraphs: `MG.edges[u, v, key][name] = value`).
190
+
191
+ **Shortcuts:**
192
+
193
+ Many common graph features allow python syntax to speed reporting.
194
+
195
+ >>> 1 in G # check if node in graph
196
+ True
197
+ >>> [n for n in G if n < 3] # iterate through nodes
198
+ [1, 2]
199
+ >>> len(G) # number of nodes in graph
200
+ 5
201
+
202
+ Often the best way to traverse all edges of a graph is via the neighbors.
203
+ The neighbors are reported as an adjacency-dict `G.adj` or `G.adjacency()`
204
+
205
+ >>> for n, nbrsdict in G.adjacency():
206
+ ... for nbr, eattr in nbrsdict.items():
207
+ ... if "weight" in eattr:
208
+ ... # Do something useful with the edges
209
+ ... pass
210
+
211
+ But the edges() method is often more convenient:
212
+
213
+ >>> for u, v, weight in G.edges.data("weight"):
214
+ ... if weight is not None:
215
+ ... # Do something useful with the edges
216
+ ... pass
217
+
218
+ **Reporting:**
219
+
220
+ Simple graph information is obtained using object-attributes and methods.
221
+ Reporting typically provides views instead of containers to reduce memory
222
+ usage. The views update as the graph is updated similarly to dict-views.
223
+ The objects `nodes`, `edges` and `adj` provide access to data attributes
224
+ via lookup (e.g. `nodes[n]`, `edges[u, v]`, `adj[u][v]`) and iteration
225
+ (e.g. `nodes.items()`, `nodes.data('color')`,
226
+ `nodes.data('color', default='blue')` and similarly for `edges`)
227
+ Views exist for `nodes`, `edges`, `neighbors()`/`adj` and `degree`.
228
+
229
+ For details on these and other miscellaneous methods, see below.
230
+
231
+ **Subclasses (Advanced):**
232
+
233
+ The Graph class uses a dict-of-dict-of-dict data structure.
234
+ The outer dict (node_dict) holds adjacency information keyed by node.
235
+ The next dict (adjlist_dict) represents the adjacency information and holds
236
+ edge data keyed by neighbor. The inner dict (edge_attr_dict) represents
237
+ the edge data and holds edge attribute values keyed by attribute names.
238
+
239
+ Each of these three dicts can be replaced in a subclass by a user defined
240
+ dict-like object. In general, the dict-like features should be
241
+ maintained but extra features can be added. To replace one of the
242
+ dicts create a new graph class by changing the class(!) variable
243
+ holding the factory for that dict-like structure.
244
+
245
+ node_dict_factory : function, (default: dict)
246
+ Factory function to be used to create the dict containing node
247
+ attributes, keyed by node id.
248
+ It should require no arguments and return a dict-like object
249
+
250
+ node_attr_dict_factory: function, (default: dict)
251
+ Factory function to be used to create the node attribute
252
+ dict which holds attribute values keyed by attribute name.
253
+ It should require no arguments and return a dict-like object
254
+
255
+ adjlist_outer_dict_factory : function, (default: dict)
256
+ Factory function to be used to create the outer-most dict
257
+ in the data structure that holds adjacency info keyed by node.
258
+ It should require no arguments and return a dict-like object.
259
+
260
+ adjlist_inner_dict_factory : function, (default: dict)
261
+ Factory function to be used to create the adjacency list
262
+ dict which holds edge data keyed by neighbor.
263
+ It should require no arguments and return a dict-like object
264
+
265
+ edge_attr_dict_factory : function, (default: dict)
266
+ Factory function to be used to create the edge attribute
267
+ dict which holds attribute values keyed by attribute name.
268
+ It should require no arguments and return a dict-like object.
269
+
270
+ graph_attr_dict_factory : function, (default: dict)
271
+ Factory function to be used to create the graph attribute
272
+ dict which holds attribute values keyed by attribute name.
273
+ It should require no arguments and return a dict-like object.
274
+
275
+ Typically, if your extension doesn't impact the data structure all
276
+ methods will inherit without issue except: `to_directed/to_undirected`.
277
+ By default these methods create a DiGraph/Graph class and you probably
278
+ want them to create your extension of a DiGraph/Graph. To facilitate
279
+ this we define two class variables that you can set in your subclass.
280
+
281
+ to_directed_class : callable, (default: DiGraph or MultiDiGraph)
282
+ Class to create a new graph structure in the `to_directed` method.
283
+ If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used.
284
+
285
+ to_undirected_class : callable, (default: Graph or MultiGraph)
286
+ Class to create a new graph structure in the `to_undirected` method.
287
+ If `None`, a NetworkX class (Graph or MultiGraph) is used.
288
+
289
+ **Subclassing Example**
290
+
291
+ Create a low memory graph class that effectively disallows edge
292
+ attributes by using a single attribute dict for all edges.
293
+ This reduces the memory used, but you lose edge attributes.
294
+
295
+ >>> class ThinGraph(nx.Graph):
296
+ ... all_edge_dict = {"weight": 1}
297
+ ...
298
+ ... def single_edge_dict(self):
299
+ ... return self.all_edge_dict
300
+ ...
301
+ ... edge_attr_dict_factory = single_edge_dict
302
+ >>> G = ThinGraph()
303
+ >>> G.add_edge(2, 1)
304
+ >>> G[2][1]
305
+ {'weight': 1}
306
+ >>> G.add_edge(2, 2)
307
+ >>> G[2][1] is G[2][2]
308
+ True
309
+ """
310
+
311
+ __networkx_backend__ = "networkx"
312
+
313
+ _adj = _CachedPropertyResetterAdj()
314
+ _node = _CachedPropertyResetterNode()
315
+
316
+ node_dict_factory = dict
317
+ node_attr_dict_factory = dict
318
+ adjlist_outer_dict_factory = dict
319
+ adjlist_inner_dict_factory = dict
320
+ edge_attr_dict_factory = dict
321
+ graph_attr_dict_factory = dict
322
+
323
+ def to_directed_class(self):
324
+ """Returns the class to use for empty directed copies.
325
+
326
+ If you subclass the base classes, use this to designate
327
+ what directed class to use for `to_directed()` copies.
328
+ """
329
+ return nx.DiGraph
330
+
331
+ def to_undirected_class(self):
332
+ """Returns the class to use for empty undirected copies.
333
+
334
+ If you subclass the base classes, use this to designate
335
+ what directed class to use for `to_directed()` copies.
336
+ """
337
+ return Graph
338
+
339
+ # This __new__ method just does what Python itself does automatically.
340
+ # We include it here as part of the dispatchable/backend interface.
341
+ # If your goal is to understand how the graph classes work, you can ignore
342
+ # this method, even when subclassing the base classes. If you are subclassing
343
+ # in order to provide a backend that allows class instantiation, this method
344
+ # can be overridden to return your own backend graph class.
345
+ @nx._dispatchable(name="graph__new__", graphs=None, returns_graph=True)
346
+ def __new__(cls, *args, **kwargs):
347
+ return object.__new__(cls)
348
+
349
+ def __init__(self, incoming_graph_data=None, **attr):
350
+ """Initialize a graph with edges, name, or graph attributes.
351
+
352
+ Parameters
353
+ ----------
354
+ incoming_graph_data : input graph (optional, default: None)
355
+ Data to initialize graph. If None (default) an empty
356
+ graph is created. The data can be an edge list, or any
357
+ NetworkX graph object. If the corresponding optional Python
358
+ packages are installed the data can also be a 2D NumPy array, a
359
+ SciPy sparse array, or a PyGraphviz graph.
360
+
361
+ attr : keyword arguments, optional (default= no attributes)
362
+ Attributes to add to graph as key=value pairs.
363
+
364
+ See Also
365
+ --------
366
+ convert
367
+
368
+ Examples
369
+ --------
370
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
371
+ >>> G = nx.Graph(name="my graph")
372
+ >>> e = [(1, 2), (2, 3), (3, 4)] # list of edges
373
+ >>> G = nx.Graph(e)
374
+
375
+ Arbitrary graph attribute pairs (key=value) may be assigned
376
+
377
+ >>> G = nx.Graph(e, day="Friday")
378
+ >>> G.graph
379
+ {'day': 'Friday'}
380
+
381
+ """
382
+ self.graph = self.graph_attr_dict_factory() # dictionary for graph attributes
383
+ self._node = self.node_dict_factory() # empty node attribute dict
384
+ self._adj = self.adjlist_outer_dict_factory() # empty adjacency dict
385
+ self.__networkx_cache__ = {}
386
+ # attempt to load graph with data
387
+ if incoming_graph_data is not None:
388
+ convert.to_networkx_graph(incoming_graph_data, create_using=self)
389
+ # load graph attributes (must be after convert)
390
+ attr.pop("backend", None) # Ignore explicit `backend="networkx"`
391
+ self.graph.update(attr)
392
+
393
+ @cached_property
394
+ def adj(self):
395
+ """Graph adjacency object holding the neighbors of each node.
396
+
397
+ This object is a read-only dict-like structure with node keys
398
+ and neighbor-dict values. The neighbor-dict is keyed by neighbor
399
+ to the edge-data-dict. So `G.adj[3][2]['color'] = 'blue'` sets
400
+ the color of the edge `(3, 2)` to `"blue"`.
401
+
402
+ Iterating over G.adj behaves like a dict. Useful idioms include
403
+ `for nbr, datadict in G.adj[n].items():`.
404
+
405
+ The neighbor information is also provided by subscripting the graph.
406
+ So `for nbr, foovalue in G[node].data('foo', default=1):` works.
407
+
408
+ For directed graphs, `G.adj` holds outgoing (successor) info.
409
+ """
410
+ return AdjacencyView(self._adj)
411
+
412
+ @property
413
+ def name(self):
414
+ """String identifier of the graph.
415
+
416
+ This graph attribute appears in the attribute dict G.graph
417
+ keyed by the string `"name"`. as well as an attribute (technically
418
+ a property) `G.name`. This is entirely user controlled.
419
+ """
420
+ return self.graph.get("name", "")
421
+
422
+ @name.setter
423
+ def name(self, s):
424
+ self.graph["name"] = s
425
+ nx._clear_cache(self)
426
+
427
+ def __str__(self):
428
+ """Returns a short summary of the graph.
429
+
430
+ Returns
431
+ -------
432
+ info : string
433
+ Graph information including the graph name (if any), graph type, and the
434
+ number of nodes and edges.
435
+
436
+ Examples
437
+ --------
438
+ >>> G = nx.Graph(name="foo")
439
+ >>> str(G)
440
+ "Graph named 'foo' with 0 nodes and 0 edges"
441
+
442
+ >>> G = nx.path_graph(3)
443
+ >>> str(G)
444
+ 'Graph with 3 nodes and 2 edges'
445
+
446
+ """
447
+ return "".join(
448
+ [
449
+ type(self).__name__,
450
+ f" named {self.name!r}" if self.name else "",
451
+ f" with {self.number_of_nodes()} nodes and {self.number_of_edges()} edges",
452
+ ]
453
+ )
454
+
455
+ def __iter__(self):
456
+ """Iterate over the nodes. Use: 'for n in G'.
457
+
458
+ Returns
459
+ -------
460
+ niter : iterator
461
+ An iterator over all nodes in the graph.
462
+
463
+ Examples
464
+ --------
465
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
466
+ >>> [n for n in G]
467
+ [0, 1, 2, 3]
468
+ >>> list(G)
469
+ [0, 1, 2, 3]
470
+ """
471
+ return iter(self._node)
472
+
473
+ def __contains__(self, n):
474
+ """Returns True if n is a node, False otherwise. Use: 'n in G'.
475
+
476
+ Examples
477
+ --------
478
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
479
+ >>> 1 in G
480
+ True
481
+ """
482
+ try:
483
+ return n in self._node
484
+ except TypeError:
485
+ return False
486
+
487
+ def __len__(self):
488
+ """Returns the number of nodes in the graph. Use: 'len(G)'.
489
+
490
+ Returns
491
+ -------
492
+ nnodes : int
493
+ The number of nodes in the graph.
494
+
495
+ See Also
496
+ --------
497
+ number_of_nodes: identical method
498
+ order: identical method
499
+
500
+ Examples
501
+ --------
502
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
503
+ >>> len(G)
504
+ 4
505
+
506
+ """
507
+ return len(self._node)
508
+
509
+ def __getitem__(self, n):
510
+ """Returns a dict of neighbors of node n. Use: 'G[n]'.
511
+
512
+ Parameters
513
+ ----------
514
+ n : node
515
+ A node in the graph.
516
+
517
+ Returns
518
+ -------
519
+ adj_dict : dictionary
520
+ The adjacency dictionary for nodes connected to n.
521
+
522
+ Notes
523
+ -----
524
+ G[n] is the same as G.adj[n] and similar to G.neighbors(n)
525
+ (which is an iterator over G.adj[n])
526
+
527
+ Examples
528
+ --------
529
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
530
+ >>> G[0]
531
+ AtlasView({1: {}})
532
+ """
533
+ return self.adj[n]
534
+
535
+ def add_node(self, node_for_adding, **attr):
536
+ """Add a single node `node_for_adding` and update node attributes.
537
+
538
+ Parameters
539
+ ----------
540
+ node_for_adding : node
541
+ A node can be any hashable Python object except None.
542
+ attr : keyword arguments, optional
543
+ Set or change node attributes using key=value.
544
+
545
+ See Also
546
+ --------
547
+ add_nodes_from
548
+
549
+ Examples
550
+ --------
551
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
552
+ >>> G.add_node(1)
553
+ >>> G.add_node("Hello")
554
+ >>> K3 = nx.Graph([(0, 1), (1, 2), (2, 0)])
555
+ >>> G.add_node(K3)
556
+ >>> G.number_of_nodes()
557
+ 3
558
+
559
+ Use keywords set/change node attributes:
560
+
561
+ >>> G.add_node(1, size=10)
562
+ >>> G.add_node(3, weight=0.4, UTM=("13S", 382871, 3972649))
563
+
564
+ Notes
565
+ -----
566
+ A hashable object is one that can be used as a key in a Python
567
+ dictionary. This includes strings, numbers, tuples of strings
568
+ and numbers, etc.
569
+
570
+ On many platforms hashable items also include mutables such as
571
+ NetworkX Graphs, though one should be careful that the hash
572
+ doesn't change on mutables.
573
+ """
574
+ if node_for_adding not in self._node:
575
+ if node_for_adding is None:
576
+ raise ValueError("None cannot be a node")
577
+ self._adj[node_for_adding] = self.adjlist_inner_dict_factory()
578
+ attr_dict = self._node[node_for_adding] = self.node_attr_dict_factory()
579
+ attr_dict.update(attr)
580
+ else: # update attr even if node already exists
581
+ self._node[node_for_adding].update(attr)
582
+ nx._clear_cache(self)
583
+
584
+ def add_nodes_from(self, nodes_for_adding, **attr):
585
+ """Add multiple nodes.
586
+
587
+ Parameters
588
+ ----------
589
+ nodes_for_adding : iterable container
590
+ A container of nodes (list, dict, set, etc.).
591
+ OR
592
+ A container of (node, attribute dict) tuples.
593
+ Node attributes are updated using the attribute dict.
594
+ attr : keyword arguments, optional (default= no attributes)
595
+ Update attributes for all nodes in nodes.
596
+ Node attributes specified in nodes as a tuple take
597
+ precedence over attributes specified via keyword arguments.
598
+
599
+ See Also
600
+ --------
601
+ add_node
602
+
603
+ Notes
604
+ -----
605
+ When adding nodes from an iterator over the graph you are changing,
606
+ a `RuntimeError` can be raised with message:
607
+ `RuntimeError: dictionary changed size during iteration`. This
608
+ happens when the graph's underlying dictionary is modified during
609
+ iteration. To avoid this error, evaluate the iterator into a separate
610
+ object, e.g. by using `list(iterator_of_nodes)`, and pass this
611
+ object to `G.add_nodes_from`.
612
+
613
+ Examples
614
+ --------
615
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
616
+ >>> G.add_nodes_from("Hello")
617
+ >>> K3 = nx.Graph([(0, 1), (1, 2), (2, 0)])
618
+ >>> G.add_nodes_from(K3)
619
+ >>> sorted(G.nodes(), key=str)
620
+ [0, 1, 2, 'H', 'e', 'l', 'o']
621
+
622
+ Use keywords to update specific node attributes for every node.
623
+
624
+ >>> G.add_nodes_from([1, 2], size=10)
625
+ >>> G.add_nodes_from([3, 4], weight=0.4)
626
+
627
+ Use (node, attrdict) tuples to update attributes for specific nodes.
628
+
629
+ >>> G.add_nodes_from([(1, dict(size=11)), (2, {"color": "blue"})])
630
+ >>> G.nodes[1]["size"]
631
+ 11
632
+ >>> H = nx.Graph()
633
+ >>> H.add_nodes_from(G.nodes(data=True))
634
+ >>> H.nodes[1]["size"]
635
+ 11
636
+
637
+ Evaluate an iterator over a graph if using it to modify the same graph
638
+
639
+ >>> G = nx.Graph([(0, 1), (1, 2), (3, 4)])
640
+ >>> # wrong way - will raise RuntimeError
641
+ >>> # G.add_nodes_from(n + 1 for n in G.nodes)
642
+ >>> # correct way
643
+ >>> G.add_nodes_from(list(n + 1 for n in G.nodes))
644
+ """
645
+ for n in nodes_for_adding:
646
+ try:
647
+ newnode = n not in self._node
648
+ newdict = attr
649
+ except TypeError:
650
+ n, ndict = n
651
+ newnode = n not in self._node
652
+ newdict = attr.copy()
653
+ newdict.update(ndict)
654
+ if newnode:
655
+ if n is None:
656
+ raise ValueError("None cannot be a node")
657
+ self._adj[n] = self.adjlist_inner_dict_factory()
658
+ self._node[n] = self.node_attr_dict_factory()
659
+ self._node[n].update(newdict)
660
+ nx._clear_cache(self)
661
+
662
+ def remove_node(self, n):
663
+ """Remove node n.
664
+
665
+ Removes the node n and all adjacent edges.
666
+ Attempting to remove a nonexistent node will raise an exception.
667
+
668
+ Parameters
669
+ ----------
670
+ n : node
671
+ A node in the graph
672
+
673
+ Raises
674
+ ------
675
+ NetworkXError
676
+ If n is not in the graph.
677
+
678
+ See Also
679
+ --------
680
+ remove_nodes_from
681
+
682
+ Examples
683
+ --------
684
+ >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
685
+ >>> list(G.edges)
686
+ [(0, 1), (1, 2)]
687
+ >>> G.remove_node(1)
688
+ >>> list(G.edges)
689
+ []
690
+
691
+ """
692
+ adj = self._adj
693
+ try:
694
+ nbrs = list(adj[n]) # list handles self-loops (allows mutation)
695
+ del self._node[n]
696
+ except KeyError as err: # NetworkXError if n not in self
697
+ raise NetworkXError(f"The node {n} is not in the graph.") from err
698
+ for u in nbrs:
699
+ del adj[u][n] # remove all edges n-u in graph
700
+ del adj[n] # now remove node
701
+ nx._clear_cache(self)
702
+
703
+ def remove_nodes_from(self, nodes):
704
+ """Remove multiple nodes.
705
+
706
+ Parameters
707
+ ----------
708
+ nodes : iterable container
709
+ A container of nodes (list, dict, set, etc.). If a node
710
+ in the container is not in the graph it is silently
711
+ ignored.
712
+
713
+ See Also
714
+ --------
715
+ remove_node
716
+
717
+ Notes
718
+ -----
719
+ When removing nodes from an iterator over the graph you are changing,
720
+ a `RuntimeError` will be raised with message:
721
+ `RuntimeError: dictionary changed size during iteration`. This
722
+ happens when the graph's underlying dictionary is modified during
723
+ iteration. To avoid this error, evaluate the iterator into a separate
724
+ object, e.g. by using `list(iterator_of_nodes)`, and pass this
725
+ object to `G.remove_nodes_from`.
726
+
727
+ Examples
728
+ --------
729
+ >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
730
+ >>> e = list(G.nodes)
731
+ >>> e
732
+ [0, 1, 2]
733
+ >>> G.remove_nodes_from(e)
734
+ >>> list(G.nodes)
735
+ []
736
+
737
+ Evaluate an iterator over a graph if using it to modify the same graph
738
+
739
+ >>> G = nx.Graph([(0, 1), (1, 2), (3, 4)])
740
+ >>> # this command will fail, as the graph's dict is modified during iteration
741
+ >>> # G.remove_nodes_from(n for n in G.nodes if n < 2)
742
+ >>> # this command will work, since the dictionary underlying graph is not modified
743
+ >>> G.remove_nodes_from(list(n for n in G.nodes if n < 2))
744
+ """
745
+ adj = self._adj
746
+ for n in nodes:
747
+ try:
748
+ del self._node[n]
749
+ for u in list(adj[n]): # list handles self-loops
750
+ del adj[u][n] # (allows mutation of dict in loop)
751
+ del adj[n]
752
+ except KeyError:
753
+ pass
754
+ nx._clear_cache(self)
755
+
756
+ @cached_property
757
+ def nodes(self):
758
+ """A NodeView of the Graph as G.nodes or G.nodes().
759
+
760
+ Can be used as `G.nodes` for data lookup and for set-like operations.
761
+ Can also be used as `G.nodes(data='color', default=None)` to return a
762
+ NodeDataView which reports specific node data but no set operations.
763
+ It presents a dict-like interface as well with `G.nodes.items()`
764
+ iterating over `(node, nodedata)` 2-tuples and `G.nodes[3]['foo']`
765
+ providing the value of the `foo` attribute for node `3`. In addition,
766
+ a view `G.nodes.data('foo')` provides a dict-like interface to the
767
+ `foo` attribute of each node. `G.nodes.data('foo', default=1)`
768
+ provides a default for nodes that do not have attribute `foo`.
769
+
770
+ Parameters
771
+ ----------
772
+ data : string or bool, optional (default=False)
773
+ The node attribute returned in 2-tuple (n, ddict[data]).
774
+ If True, return entire node attribute dict as (n, ddict).
775
+ If False, return just the nodes n.
776
+
777
+ default : value, optional (default=None)
778
+ Value used for nodes that don't have the requested attribute.
779
+ Only relevant if data is not True or False.
780
+
781
+ Returns
782
+ -------
783
+ NodeView
784
+ Allows set-like operations over the nodes as well as node
785
+ attribute dict lookup and calling to get a NodeDataView.
786
+ A NodeDataView iterates over `(n, data)` and has no set operations.
787
+ A NodeView iterates over `n` and includes set operations.
788
+
789
+ When called, if data is False, an iterator over nodes.
790
+ Otherwise an iterator of 2-tuples (node, attribute value)
791
+ where the attribute is specified in `data`.
792
+ If data is True then the attribute becomes the
793
+ entire data dictionary.
794
+
795
+ Notes
796
+ -----
797
+ If your node data is not needed, it is simpler and equivalent
798
+ to use the expression ``for n in G``, or ``list(G)``.
799
+
800
+ Examples
801
+ --------
802
+ There are two simple ways of getting a list of all nodes in the graph:
803
+
804
+ >>> G = nx.path_graph(3)
805
+ >>> list(G.nodes)
806
+ [0, 1, 2]
807
+ >>> list(G)
808
+ [0, 1, 2]
809
+
810
+ To get the node data along with the nodes:
811
+
812
+ >>> G.add_node(1, time="5pm")
813
+ >>> G.nodes[0]["foo"] = "bar"
814
+ >>> list(G.nodes(data=True))
815
+ [(0, {'foo': 'bar'}), (1, {'time': '5pm'}), (2, {})]
816
+ >>> list(G.nodes.data())
817
+ [(0, {'foo': 'bar'}), (1, {'time': '5pm'}), (2, {})]
818
+
819
+ >>> list(G.nodes(data="foo"))
820
+ [(0, 'bar'), (1, None), (2, None)]
821
+ >>> list(G.nodes.data("foo"))
822
+ [(0, 'bar'), (1, None), (2, None)]
823
+
824
+ >>> list(G.nodes(data="time"))
825
+ [(0, None), (1, '5pm'), (2, None)]
826
+ >>> list(G.nodes.data("time"))
827
+ [(0, None), (1, '5pm'), (2, None)]
828
+
829
+ >>> list(G.nodes(data="time", default="Not Available"))
830
+ [(0, 'Not Available'), (1, '5pm'), (2, 'Not Available')]
831
+ >>> list(G.nodes.data("time", default="Not Available"))
832
+ [(0, 'Not Available'), (1, '5pm'), (2, 'Not Available')]
833
+
834
+ If some of your nodes have an attribute and the rest are assumed
835
+ to have a default attribute value you can create a dictionary
836
+ from node/attribute pairs using the `default` keyword argument
837
+ to guarantee the value is never None::
838
+
839
+ >>> G = nx.Graph()
840
+ >>> G.add_node(0)
841
+ >>> G.add_node(1, weight=2)
842
+ >>> G.add_node(2, weight=3)
843
+ >>> dict(G.nodes(data="weight", default=1))
844
+ {0: 1, 1: 2, 2: 3}
845
+
846
+ """
847
+ return NodeView(self)
848
+
849
+ def number_of_nodes(self):
850
+ """Returns the number of nodes in the graph.
851
+
852
+ Returns
853
+ -------
854
+ nnodes : int
855
+ The number of nodes in the graph.
856
+
857
+ See Also
858
+ --------
859
+ order: identical method
860
+ __len__: identical method
861
+
862
+ Examples
863
+ --------
864
+ >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
865
+ >>> G.number_of_nodes()
866
+ 3
867
+ """
868
+ return len(self._node)
869
+
870
+ def order(self):
871
+ """Returns the number of nodes in the graph.
872
+
873
+ Returns
874
+ -------
875
+ nnodes : int
876
+ The number of nodes in the graph.
877
+
878
+ See Also
879
+ --------
880
+ number_of_nodes: identical method
881
+ __len__: identical method
882
+
883
+ Examples
884
+ --------
885
+ >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
886
+ >>> G.order()
887
+ 3
888
+ """
889
+ return len(self._node)
890
+
891
+ def has_node(self, n):
892
+ """Returns True if the graph contains the node n.
893
+
894
+ Identical to `n in G`
895
+
896
+ Parameters
897
+ ----------
898
+ n : node
899
+
900
+ Examples
901
+ --------
902
+ >>> G = nx.path_graph(3) # or DiGraph, MultiGraph, MultiDiGraph, etc
903
+ >>> G.has_node(0)
904
+ True
905
+
906
+ It is more readable and simpler to use
907
+
908
+ >>> 0 in G
909
+ True
910
+
911
+ """
912
+ try:
913
+ return n in self._node
914
+ except TypeError:
915
+ return False
916
+
917
+ def add_edge(self, u_of_edge, v_of_edge, **attr):
918
+ """Add an edge between u and v.
919
+
920
+ The nodes u and v will be automatically added if they are
921
+ not already in the graph.
922
+
923
+ Edge attributes can be specified with keywords or by directly
924
+ accessing the edge's attribute dictionary. See examples below.
925
+
926
+ Parameters
927
+ ----------
928
+ u_of_edge, v_of_edge : nodes
929
+ Nodes can be, for example, strings or numbers.
930
+ Nodes must be hashable (and not None) Python objects.
931
+ attr : keyword arguments, optional
932
+ Edge data (or labels or objects) can be assigned using
933
+ keyword arguments.
934
+
935
+ See Also
936
+ --------
937
+ add_edges_from : add a collection of edges
938
+
939
+ Notes
940
+ -----
941
+ Adding an edge that already exists updates the edge data.
942
+
943
+ Many NetworkX algorithms designed for weighted graphs use
944
+ an edge attribute (by default `weight`) to hold a numerical value.
945
+
946
+ Examples
947
+ --------
948
+ The following all add the edge e=(1, 2) to graph G:
949
+
950
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
951
+ >>> e = (1, 2)
952
+ >>> G.add_edge(1, 2) # explicit two-node form
953
+ >>> G.add_edge(*e) # single edge as tuple of two nodes
954
+ >>> G.add_edges_from([(1, 2)]) # add edges from iterable container
955
+
956
+ Associate data to edges using keywords:
957
+
958
+ >>> G.add_edge(1, 2, weight=3)
959
+ >>> G.add_edge(1, 3, weight=7, capacity=15, length=342.7)
960
+
961
+ For non-string attribute keys, use subscript notation.
962
+
963
+ >>> G.add_edge(1, 2)
964
+ >>> G[1][2].update({0: 5})
965
+ >>> G.edges[1, 2].update({0: 5})
966
+ """
967
+ u, v = u_of_edge, v_of_edge
968
+ # add nodes
969
+ if u not in self._node:
970
+ if u is None:
971
+ raise ValueError("None cannot be a node")
972
+ self._adj[u] = self.adjlist_inner_dict_factory()
973
+ self._node[u] = self.node_attr_dict_factory()
974
+ if v not in self._node:
975
+ if v is None:
976
+ raise ValueError("None cannot be a node")
977
+ self._adj[v] = self.adjlist_inner_dict_factory()
978
+ self._node[v] = self.node_attr_dict_factory()
979
+ # add the edge
980
+ datadict = self._adj[u].get(v, self.edge_attr_dict_factory())
981
+ datadict.update(attr)
982
+ self._adj[u][v] = datadict
983
+ self._adj[v][u] = datadict
984
+ nx._clear_cache(self)
985
+
986
+ def add_edges_from(self, ebunch_to_add, **attr):
987
+ """Add all the edges in ebunch_to_add.
988
+
989
+ Parameters
990
+ ----------
991
+ ebunch_to_add : container of edges
992
+ Each edge given in the container will be added to the
993
+ graph. The edges must be given as 2-tuples (u, v) or
994
+ 3-tuples (u, v, d) where d is a dictionary containing edge data.
995
+ attr : keyword arguments, optional
996
+ Edge data (or labels or objects) can be assigned using
997
+ keyword arguments.
998
+
999
+ See Also
1000
+ --------
1001
+ add_edge : add a single edge
1002
+ add_weighted_edges_from : convenient way to add weighted edges
1003
+
1004
+ Notes
1005
+ -----
1006
+ Adding the same edge twice has no effect but any edge data
1007
+ will be updated when each duplicate edge is added.
1008
+
1009
+ Edge attributes specified in an ebunch take precedence over
1010
+ attributes specified via keyword arguments.
1011
+
1012
+ When adding edges from an iterator over the graph you are changing,
1013
+ a `RuntimeError` can be raised with message:
1014
+ `RuntimeError: dictionary changed size during iteration`. This
1015
+ happens when the graph's underlying dictionary is modified during
1016
+ iteration. To avoid this error, evaluate the iterator into a separate
1017
+ object, e.g. by using `list(iterator_of_edges)`, and pass this
1018
+ object to `G.add_edges_from`.
1019
+
1020
+ Examples
1021
+ --------
1022
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
1023
+ >>> G.add_edges_from([(0, 1), (1, 2)]) # using a list of edge tuples
1024
+ >>> e = zip(range(0, 3), range(1, 4))
1025
+ >>> G.add_edges_from(e) # Add the path graph 0-1-2-3
1026
+
1027
+ Associate data to edges
1028
+
1029
+ >>> G.add_edges_from([(1, 2), (2, 3)], weight=3)
1030
+ >>> G.add_edges_from([(3, 4), (1, 4)], label="WN2898")
1031
+
1032
+ Evaluate an iterator over a graph if using it to modify the same graph
1033
+
1034
+ >>> G = nx.Graph([(1, 2), (2, 3), (3, 4)])
1035
+ >>> # Grow graph by one new node, adding edges to all existing nodes.
1036
+ >>> # wrong way - will raise RuntimeError
1037
+ >>> # G.add_edges_from(((5, n) for n in G.nodes))
1038
+ >>> # correct way - note that there will be no self-edge for node 5
1039
+ >>> G.add_edges_from(list((5, n) for n in G.nodes))
1040
+ """
1041
+ for e in ebunch_to_add:
1042
+ ne = len(e)
1043
+ if ne == 3:
1044
+ u, v, dd = e
1045
+ elif ne == 2:
1046
+ u, v = e
1047
+ dd = {} # doesn't need edge_attr_dict_factory
1048
+ else:
1049
+ raise NetworkXError(f"Edge tuple {e} must be a 2-tuple or 3-tuple.")
1050
+ if u not in self._node:
1051
+ if u is None:
1052
+ raise ValueError("None cannot be a node")
1053
+ self._adj[u] = self.adjlist_inner_dict_factory()
1054
+ self._node[u] = self.node_attr_dict_factory()
1055
+ if v not in self._node:
1056
+ if v is None:
1057
+ raise ValueError("None cannot be a node")
1058
+ self._adj[v] = self.adjlist_inner_dict_factory()
1059
+ self._node[v] = self.node_attr_dict_factory()
1060
+ datadict = self._adj[u].get(v, self.edge_attr_dict_factory())
1061
+ datadict.update(attr)
1062
+ datadict.update(dd)
1063
+ self._adj[u][v] = datadict
1064
+ self._adj[v][u] = datadict
1065
+ nx._clear_cache(self)
1066
+
1067
+ def add_weighted_edges_from(self, ebunch_to_add, weight="weight", **attr):
1068
+ """Add weighted edges in `ebunch_to_add` with specified weight attr
1069
+
1070
+ Parameters
1071
+ ----------
1072
+ ebunch_to_add : container of edges
1073
+ Each edge given in the list or container will be added
1074
+ to the graph. The edges must be given as 3-tuples (u, v, w)
1075
+ where w is a number.
1076
+ weight : string, optional (default= 'weight')
1077
+ The attribute name for the edge weights to be added.
1078
+ attr : keyword arguments, optional (default= no attributes)
1079
+ Edge attributes to add/update for all edges.
1080
+
1081
+ See Also
1082
+ --------
1083
+ add_edge : add a single edge
1084
+ add_edges_from : add multiple edges
1085
+
1086
+ Notes
1087
+ -----
1088
+ Adding the same edge twice for Graph/DiGraph simply updates
1089
+ the edge data. For MultiGraph/MultiDiGraph, duplicate edges
1090
+ are stored.
1091
+
1092
+ When adding edges from an iterator over the graph you are changing,
1093
+ a `RuntimeError` can be raised with message:
1094
+ `RuntimeError: dictionary changed size during iteration`. This
1095
+ happens when the graph's underlying dictionary is modified during
1096
+ iteration. To avoid this error, evaluate the iterator into a separate
1097
+ object, e.g. by using `list(iterator_of_edges)`, and pass this
1098
+ object to `G.add_weighted_edges_from`.
1099
+
1100
+ Examples
1101
+ --------
1102
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
1103
+ >>> G.add_weighted_edges_from([(0, 1, 3.0), (1, 2, 7.5)])
1104
+
1105
+ Evaluate an iterator over edges before passing it
1106
+
1107
+ >>> G = nx.Graph([(1, 2), (2, 3), (3, 4)])
1108
+ >>> weight = 0.1
1109
+ >>> # Grow graph by one new node, adding edges to all existing nodes.
1110
+ >>> # wrong way - will raise RuntimeError
1111
+ >>> # G.add_weighted_edges_from(((5, n, weight) for n in G.nodes))
1112
+ >>> # correct way - note that there will be no self-edge for node 5
1113
+ >>> G.add_weighted_edges_from(list((5, n, weight) for n in G.nodes))
1114
+ """
1115
+ self.add_edges_from(((u, v, {weight: d}) for u, v, d in ebunch_to_add), **attr)
1116
+ nx._clear_cache(self)
1117
+
1118
+ def remove_edge(self, u, v):
1119
+ """Remove the edge between u and v.
1120
+
1121
+ Parameters
1122
+ ----------
1123
+ u, v : nodes
1124
+ Remove the edge between nodes u and v.
1125
+
1126
+ Raises
1127
+ ------
1128
+ NetworkXError
1129
+ If there is not an edge between u and v.
1130
+
1131
+ See Also
1132
+ --------
1133
+ remove_edges_from : remove a collection of edges
1134
+
1135
+ Examples
1136
+ --------
1137
+ >>> G = nx.path_graph(4) # or DiGraph, etc
1138
+ >>> G.remove_edge(0, 1)
1139
+ >>> e = (1, 2)
1140
+ >>> G.remove_edge(*e) # unpacks e from an edge tuple
1141
+ >>> e = (2, 3, {"weight": 7}) # an edge with attribute data
1142
+ >>> G.remove_edge(*e[:2]) # select first part of edge tuple
1143
+ """
1144
+ try:
1145
+ del self._adj[u][v]
1146
+ if u != v: # self-loop needs only one entry removed
1147
+ del self._adj[v][u]
1148
+ except KeyError as err:
1149
+ raise NetworkXError(f"The edge {u}-{v} is not in the graph") from err
1150
+ nx._clear_cache(self)
1151
+
1152
+ def remove_edges_from(self, ebunch):
1153
+ """Remove all edges specified in ebunch.
1154
+
1155
+ Parameters
1156
+ ----------
1157
+ ebunch: list or container of edge tuples
1158
+ Each edge given in the list or container will be removed
1159
+ from the graph. The edges can be:
1160
+
1161
+ - 2-tuples (u, v) edge between u and v.
1162
+ - 3-tuples (u, v, k) where k is ignored.
1163
+
1164
+ See Also
1165
+ --------
1166
+ remove_edge : remove a single edge
1167
+
1168
+ Notes
1169
+ -----
1170
+ Will fail silently if an edge in ebunch is not in the graph.
1171
+
1172
+ Examples
1173
+ --------
1174
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1175
+ >>> ebunch = [(1, 2), (2, 3)]
1176
+ >>> G.remove_edges_from(ebunch)
1177
+ """
1178
+ adj = self._adj
1179
+ for e in ebunch:
1180
+ u, v = e[:2] # ignore edge data if present
1181
+ if u in adj and v in adj[u]:
1182
+ del adj[u][v]
1183
+ if u != v: # self loop needs only one entry removed
1184
+ del adj[v][u]
1185
+ nx._clear_cache(self)
1186
+
1187
+ def update(self, edges=None, nodes=None):
1188
+ """Update the graph using nodes/edges/graphs as input.
1189
+
1190
+ Like dict.update, this method takes a graph as input, adding the
1191
+ graph's nodes and edges to this graph. It can also take two inputs:
1192
+ edges and nodes. Finally it can take either edges or nodes.
1193
+ To specify only nodes the keyword `nodes` must be used.
1194
+
1195
+ The collections of edges and nodes are treated similarly to
1196
+ the add_edges_from/add_nodes_from methods. When iterated, they
1197
+ should yield 2-tuples (u, v) or 3-tuples (u, v, datadict).
1198
+
1199
+ Parameters
1200
+ ----------
1201
+ edges : Graph object, collection of edges, or None
1202
+ The first parameter can be a graph or some edges. If it has
1203
+ attributes `nodes` and `edges`, then it is taken to be a
1204
+ Graph-like object and those attributes are used as collections
1205
+ of nodes and edges to be added to the graph.
1206
+ If the first parameter does not have those attributes, it is
1207
+ treated as a collection of edges and added to the graph.
1208
+ If the first argument is None, no edges are added.
1209
+ nodes : collection of nodes, or None
1210
+ The second parameter is treated as a collection of nodes
1211
+ to be added to the graph unless it is None.
1212
+ If `edges is None` and `nodes is None` an exception is raised.
1213
+ If the first parameter is a Graph, then `nodes` is ignored.
1214
+
1215
+ Examples
1216
+ --------
1217
+ >>> G = nx.path_graph(5)
1218
+ >>> G.update(nx.complete_graph(range(4, 10)))
1219
+ >>> from itertools import combinations
1220
+ >>> edges = (
1221
+ ... (u, v, {"power": u * v})
1222
+ ... for u, v in combinations(range(10, 20), 2)
1223
+ ... if u * v < 225
1224
+ ... )
1225
+ >>> nodes = [1000] # for singleton, use a container
1226
+ >>> G.update(edges, nodes)
1227
+
1228
+ Notes
1229
+ -----
1230
+ It you want to update the graph using an adjacency structure
1231
+ it is straightforward to obtain the edges/nodes from adjacency.
1232
+ The following examples provide common cases, your adjacency may
1233
+ be slightly different and require tweaks of these examples::
1234
+
1235
+ >>> # dict-of-set/list/tuple
1236
+ >>> adj = {1: {2, 3}, 2: {1, 3}, 3: {1, 2}}
1237
+ >>> e = [(u, v) for u, nbrs in adj.items() for v in nbrs]
1238
+ >>> G.update(edges=e, nodes=adj)
1239
+
1240
+ >>> DG = nx.DiGraph()
1241
+ >>> # dict-of-dict-of-attribute
1242
+ >>> adj = {1: {2: 1.3, 3: 0.7}, 2: {1: 1.4}, 3: {1: 0.7}}
1243
+ >>> e = [
1244
+ ... (u, v, {"weight": d})
1245
+ ... for u, nbrs in adj.items()
1246
+ ... for v, d in nbrs.items()
1247
+ ... ]
1248
+ >>> DG.update(edges=e, nodes=adj)
1249
+
1250
+ >>> # dict-of-dict-of-dict
1251
+ >>> adj = {1: {2: {"weight": 1.3}, 3: {"color": 0.7, "weight": 1.2}}}
1252
+ >>> e = [
1253
+ ... (u, v, {"weight": d})
1254
+ ... for u, nbrs in adj.items()
1255
+ ... for v, d in nbrs.items()
1256
+ ... ]
1257
+ >>> DG.update(edges=e, nodes=adj)
1258
+
1259
+ >>> # predecessor adjacency (dict-of-set)
1260
+ >>> pred = {1: {2, 3}, 2: {3}, 3: {3}}
1261
+ >>> e = [(v, u) for u, nbrs in pred.items() for v in nbrs]
1262
+
1263
+ >>> # MultiGraph dict-of-dict-of-dict-of-attribute
1264
+ >>> MDG = nx.MultiDiGraph()
1265
+ >>> adj = {
1266
+ ... 1: {2: {0: {"weight": 1.3}, 1: {"weight": 1.2}}},
1267
+ ... 3: {2: {0: {"weight": 0.7}}},
1268
+ ... }
1269
+ >>> e = [
1270
+ ... (u, v, ekey, d)
1271
+ ... for u, nbrs in adj.items()
1272
+ ... for v, keydict in nbrs.items()
1273
+ ... for ekey, d in keydict.items()
1274
+ ... ]
1275
+ >>> MDG.update(edges=e)
1276
+
1277
+ See Also
1278
+ --------
1279
+ add_edges_from: add multiple edges to a graph
1280
+ add_nodes_from: add multiple nodes to a graph
1281
+ """
1282
+ if edges is not None:
1283
+ if nodes is not None:
1284
+ self.add_nodes_from(nodes)
1285
+ self.add_edges_from(edges)
1286
+ else:
1287
+ # check if edges is a Graph object
1288
+ try:
1289
+ graph_nodes = edges.nodes
1290
+ graph_edges = edges.edges
1291
+ except AttributeError:
1292
+ # edge not Graph-like
1293
+ self.add_edges_from(edges)
1294
+ else: # edges is Graph-like
1295
+ self.add_nodes_from(graph_nodes.data())
1296
+ self.add_edges_from(graph_edges.data())
1297
+ self.graph.update(edges.graph)
1298
+ elif nodes is not None:
1299
+ self.add_nodes_from(nodes)
1300
+ else:
1301
+ raise NetworkXError("update needs nodes or edges input")
1302
+
1303
+ def has_edge(self, u, v):
1304
+ """Returns True if the edge (u, v) is in the graph.
1305
+
1306
+ This is the same as `v in G[u]` without KeyError exceptions.
1307
+
1308
+ Parameters
1309
+ ----------
1310
+ u, v : nodes
1311
+ Nodes can be, for example, strings or numbers.
1312
+ Nodes must be hashable (and not None) Python objects.
1313
+
1314
+ Returns
1315
+ -------
1316
+ edge_ind : bool
1317
+ True if edge is in the graph, False otherwise.
1318
+
1319
+ Examples
1320
+ --------
1321
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1322
+ >>> G.has_edge(0, 1) # using two nodes
1323
+ True
1324
+ >>> e = (0, 1)
1325
+ >>> G.has_edge(*e) # e is a 2-tuple (u, v)
1326
+ True
1327
+ >>> e = (0, 1, {"weight": 7})
1328
+ >>> G.has_edge(*e[:2]) # e is a 3-tuple (u, v, data_dictionary)
1329
+ True
1330
+
1331
+ The following syntax are equivalent:
1332
+
1333
+ >>> G.has_edge(0, 1)
1334
+ True
1335
+ >>> 1 in G[0] # though this gives KeyError if 0 not in G
1336
+ True
1337
+
1338
+ """
1339
+ try:
1340
+ return v in self._adj[u]
1341
+ except KeyError:
1342
+ return False
1343
+
1344
+ def neighbors(self, n):
1345
+ """Returns an iterator over all neighbors of node n.
1346
+
1347
+ This is identical to `iter(G[n])`
1348
+
1349
+ Parameters
1350
+ ----------
1351
+ n : node
1352
+ A node in the graph
1353
+
1354
+ Returns
1355
+ -------
1356
+ neighbors : iterator
1357
+ An iterator over all neighbors of node n
1358
+
1359
+ Raises
1360
+ ------
1361
+ NetworkXError
1362
+ If the node n is not in the graph.
1363
+
1364
+ Examples
1365
+ --------
1366
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1367
+ >>> [n for n in G.neighbors(0)]
1368
+ [1]
1369
+
1370
+ Notes
1371
+ -----
1372
+ Alternate ways to access the neighbors are ``G.adj[n]`` or ``G[n]``:
1373
+
1374
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
1375
+ >>> G.add_edge("a", "b", weight=7)
1376
+ >>> G["a"]
1377
+ AtlasView({'b': {'weight': 7}})
1378
+ >>> G = nx.path_graph(4)
1379
+ >>> [n for n in G[0]]
1380
+ [1]
1381
+ """
1382
+ try:
1383
+ return iter(self._adj[n])
1384
+ except KeyError as err:
1385
+ raise NetworkXError(f"The node {n} is not in the graph.") from err
1386
+
1387
+ @cached_property
1388
+ def edges(self):
1389
+ """An EdgeView of the Graph as G.edges or G.edges().
1390
+
1391
+ edges(self, nbunch=None, data=False, default=None)
1392
+
1393
+ The EdgeView provides set-like operations on the edge-tuples
1394
+ as well as edge attribute lookup. When called, it also provides
1395
+ an EdgeDataView object which allows control of access to edge
1396
+ attributes (but does not provide set-like operations).
1397
+ Hence, `G.edges[u, v]['color']` provides the value of the color
1398
+ attribute for edge `(u, v)` while
1399
+ `for (u, v, c) in G.edges.data('color', default='red'):`
1400
+ iterates through all the edges yielding the color attribute
1401
+ with default `'red'` if no color attribute exists.
1402
+
1403
+ Parameters
1404
+ ----------
1405
+ nbunch : single node, container, or all nodes (default= all nodes)
1406
+ The view will only report edges from these nodes.
1407
+ data : string or bool, optional (default=False)
1408
+ The edge attribute returned in 3-tuple (u, v, ddict[data]).
1409
+ If True, return edge attribute dict in 3-tuple (u, v, ddict).
1410
+ If False, return 2-tuple (u, v).
1411
+ default : value, optional (default=None)
1412
+ Value used for edges that don't have the requested attribute.
1413
+ Only relevant if data is not True or False.
1414
+
1415
+ Returns
1416
+ -------
1417
+ edges : EdgeView
1418
+ A view of edge attributes, usually it iterates over (u, v)
1419
+ or (u, v, d) tuples of edges, but can also be used for
1420
+ attribute lookup as `edges[u, v]['foo']`.
1421
+
1422
+ Notes
1423
+ -----
1424
+ Nodes in nbunch that are not in the graph will be (quietly) ignored.
1425
+ For directed graphs this returns the out-edges.
1426
+
1427
+ Examples
1428
+ --------
1429
+ >>> G = nx.path_graph(3) # or MultiGraph, etc
1430
+ >>> G.add_edge(2, 3, weight=5)
1431
+ >>> [e for e in G.edges]
1432
+ [(0, 1), (1, 2), (2, 3)]
1433
+ >>> G.edges.data() # default data is {} (empty dict)
1434
+ EdgeDataView([(0, 1, {}), (1, 2, {}), (2, 3, {'weight': 5})])
1435
+ >>> G.edges.data("weight", default=1)
1436
+ EdgeDataView([(0, 1, 1), (1, 2, 1), (2, 3, 5)])
1437
+ >>> G.edges([0, 3]) # only edges from these nodes
1438
+ EdgeDataView([(0, 1), (3, 2)])
1439
+ >>> G.edges(0) # only edges from node 0
1440
+ EdgeDataView([(0, 1)])
1441
+ """
1442
+ return EdgeView(self)
1443
+
1444
+ def get_edge_data(self, u, v, default=None):
1445
+ """Returns the attribute dictionary associated with edge (u, v).
1446
+
1447
+ This is identical to `G[u][v]` except the default is returned
1448
+ instead of an exception if the edge doesn't exist.
1449
+
1450
+ Parameters
1451
+ ----------
1452
+ u, v : nodes
1453
+ default: any Python object (default=None)
1454
+ Value to return if the edge (u, v) is not found.
1455
+
1456
+ Returns
1457
+ -------
1458
+ edge_dict : dictionary
1459
+ The edge attribute dictionary.
1460
+
1461
+ Examples
1462
+ --------
1463
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1464
+ >>> G[0][1]
1465
+ {}
1466
+
1467
+ Warning: Assigning to `G[u][v]` is not permitted.
1468
+ But it is safe to assign attributes `G[u][v]['foo']`
1469
+
1470
+ >>> G[0][1]["weight"] = 7
1471
+ >>> G[0][1]["weight"]
1472
+ 7
1473
+ >>> G[1][0]["weight"]
1474
+ 7
1475
+
1476
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1477
+ >>> G.get_edge_data(0, 1) # default edge data is {}
1478
+ {}
1479
+ >>> e = (0, 1)
1480
+ >>> G.get_edge_data(*e) # tuple form
1481
+ {}
1482
+ >>> G.get_edge_data("a", "b", default=0) # edge not in graph, return 0
1483
+ 0
1484
+ """
1485
+ try:
1486
+ return self._adj[u][v]
1487
+ except KeyError:
1488
+ return default
1489
+
1490
+ def adjacency(self):
1491
+ """Returns an iterator over (node, adjacency dict) tuples for all nodes.
1492
+
1493
+ For directed graphs, only outgoing neighbors/adjacencies are included.
1494
+
1495
+ Returns
1496
+ -------
1497
+ adj_iter : iterator
1498
+ An iterator over (node, adjacency dictionary) for all nodes in
1499
+ the graph.
1500
+
1501
+ Examples
1502
+ --------
1503
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1504
+ >>> [(n, nbrdict) for n, nbrdict in G.adjacency()]
1505
+ [(0, {1: {}}), (1, {0: {}, 2: {}}), (2, {1: {}, 3: {}}), (3, {2: {}})]
1506
+
1507
+ """
1508
+ return iter(self._adj.items())
1509
+
1510
+ @cached_property
1511
+ def degree(self):
1512
+ """A DegreeView for the Graph as G.degree or G.degree().
1513
+
1514
+ The node degree is the number of edges adjacent to the node.
1515
+ The weighted node degree is the sum of the edge weights for
1516
+ edges incident to that node.
1517
+
1518
+ This object provides an iterator for (node, degree) as well as
1519
+ lookup for the degree for a single node.
1520
+
1521
+ Parameters
1522
+ ----------
1523
+ nbunch : single node, container, or all nodes (default= all nodes)
1524
+ The view will only report edges incident to these nodes.
1525
+
1526
+ weight : string or None, optional (default=None)
1527
+ The name of an edge attribute that holds the numerical value used
1528
+ as a weight. If None, then each edge has weight 1.
1529
+ The degree is the sum of the edge weights adjacent to the node.
1530
+
1531
+ Returns
1532
+ -------
1533
+ DegreeView or int
1534
+ If multiple nodes are requested (the default), returns a `DegreeView`
1535
+ mapping nodes to their degree.
1536
+ If a single node is requested, returns the degree of the node as an integer.
1537
+
1538
+ Examples
1539
+ --------
1540
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1541
+ >>> G.degree[0] # node 0 has degree 1
1542
+ 1
1543
+ >>> list(G.degree([0, 1, 2]))
1544
+ [(0, 1), (1, 2), (2, 2)]
1545
+ """
1546
+ return DegreeView(self)
1547
+
1548
+ def clear(self):
1549
+ """Remove all nodes and edges from the graph.
1550
+
1551
+ This also removes the name, and all graph, node, and edge attributes.
1552
+
1553
+ Examples
1554
+ --------
1555
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1556
+ >>> G.clear()
1557
+ >>> list(G.nodes)
1558
+ []
1559
+ >>> list(G.edges)
1560
+ []
1561
+
1562
+ """
1563
+ self._adj.clear()
1564
+ self._node.clear()
1565
+ self.graph.clear()
1566
+ nx._clear_cache(self)
1567
+
1568
+ def clear_edges(self):
1569
+ """Remove all edges from the graph without altering nodes.
1570
+
1571
+ Examples
1572
+ --------
1573
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1574
+ >>> G.clear_edges()
1575
+ >>> list(G.nodes)
1576
+ [0, 1, 2, 3]
1577
+ >>> list(G.edges)
1578
+ []
1579
+ """
1580
+ for nbr_dict in self._adj.values():
1581
+ nbr_dict.clear()
1582
+ nx._clear_cache(self)
1583
+
1584
+ def is_multigraph(self):
1585
+ """Returns True if graph is a multigraph, False otherwise."""
1586
+ return False
1587
+
1588
+ def is_directed(self):
1589
+ """Returns True if graph is directed, False otherwise."""
1590
+ return False
1591
+
1592
+ def copy(self, as_view=False):
1593
+ """Returns a copy of the graph.
1594
+
1595
+ The copy method by default returns an independent shallow copy
1596
+ of the graph and attributes. That is, if an attribute is a
1597
+ container, that container is shared by the original an the copy.
1598
+ Use Python's `copy.deepcopy` for new containers.
1599
+
1600
+ If `as_view` is True then a view is returned instead of a copy.
1601
+
1602
+ Notes
1603
+ -----
1604
+ All copies reproduce the graph structure, but data attributes
1605
+ may be handled in different ways. There are four types of copies
1606
+ of a graph that people might want.
1607
+
1608
+ Deepcopy -- A "deepcopy" copies the graph structure as well as
1609
+ all data attributes and any objects they might contain.
1610
+ The entire graph object is new so that changes in the copy
1611
+ do not affect the original object. (see Python's copy.deepcopy)
1612
+
1613
+ Data Reference (Shallow) -- For a shallow copy the graph structure
1614
+ is copied but the edge, node and graph attribute dicts are
1615
+ references to those in the original graph. This saves
1616
+ time and memory but could cause confusion if you change an attribute
1617
+ in one graph and it changes the attribute in the other.
1618
+ NetworkX does not provide this level of shallow copy.
1619
+
1620
+ Independent Shallow -- This copy creates new independent attribute
1621
+ dicts and then does a shallow copy of the attributes. That is, any
1622
+ attributes that are containers are shared between the new graph
1623
+ and the original. This is exactly what `dict.copy()` provides.
1624
+ You can obtain this style copy using:
1625
+
1626
+ >>> G = nx.path_graph(5)
1627
+ >>> H = G.copy()
1628
+ >>> H = G.copy(as_view=False)
1629
+ >>> H = nx.Graph(G)
1630
+ >>> H = G.__class__(G)
1631
+
1632
+ Fresh Data -- For fresh data, the graph structure is copied while
1633
+ new empty data attribute dicts are created. The resulting graph
1634
+ is independent of the original and it has no edge, node or graph
1635
+ attributes. Fresh copies are not enabled. Instead use:
1636
+
1637
+ >>> H = G.__class__()
1638
+ >>> H.add_nodes_from(G)
1639
+ >>> H.add_edges_from(G.edges)
1640
+
1641
+ View -- Inspired by dict-views, graph-views act like read-only
1642
+ versions of the original graph, providing a copy of the original
1643
+ structure without requiring any memory for copying the information.
1644
+
1645
+ See the Python copy module for more information on shallow
1646
+ and deep copies, https://docs.python.org/3/library/copy.html.
1647
+
1648
+ Parameters
1649
+ ----------
1650
+ as_view : bool, optional (default=False)
1651
+ If True, the returned graph-view provides a read-only view
1652
+ of the original graph without actually copying any data.
1653
+
1654
+ Returns
1655
+ -------
1656
+ G : Graph
1657
+ A copy of the graph.
1658
+
1659
+ See Also
1660
+ --------
1661
+ to_directed: return a directed copy of the graph.
1662
+
1663
+ Examples
1664
+ --------
1665
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1666
+ >>> H = G.copy()
1667
+
1668
+ """
1669
+ if as_view is True:
1670
+ return nx.graphviews.generic_graph_view(self)
1671
+ G = self.__class__()
1672
+ G.graph.update(self.graph)
1673
+ G.add_nodes_from((n, d.copy()) for n, d in self._node.items())
1674
+ G.add_edges_from(
1675
+ (u, v, datadict.copy())
1676
+ for u, nbrs in self._adj.items()
1677
+ for v, datadict in nbrs.items()
1678
+ )
1679
+ return G
1680
+
1681
+ def to_directed(self, as_view=False):
1682
+ """Returns a directed representation of the graph.
1683
+
1684
+ Returns
1685
+ -------
1686
+ G : DiGraph
1687
+ A directed graph with the same name, same nodes, and with
1688
+ each edge (u, v, data) replaced by two directed edges
1689
+ (u, v, data) and (v, u, data).
1690
+
1691
+ Notes
1692
+ -----
1693
+ This returns a "deepcopy" of the edge, node, and
1694
+ graph attributes which attempts to completely copy
1695
+ all of the data and references.
1696
+
1697
+ This is in contrast to the similar D=DiGraph(G) which returns a
1698
+ shallow copy of the data.
1699
+
1700
+ See the Python copy module for more information on shallow
1701
+ and deep copies, https://docs.python.org/3/library/copy.html.
1702
+
1703
+ Warning: If you have subclassed Graph to use dict-like objects
1704
+ in the data structure, those changes do not transfer to the
1705
+ DiGraph created by this method.
1706
+
1707
+ Examples
1708
+ --------
1709
+ >>> G = nx.Graph() # or MultiGraph, etc
1710
+ >>> G.add_edge(0, 1)
1711
+ >>> H = G.to_directed()
1712
+ >>> list(H.edges)
1713
+ [(0, 1), (1, 0)]
1714
+
1715
+ If already directed, return a (deep) copy
1716
+
1717
+ >>> G = nx.DiGraph() # or MultiDiGraph, etc
1718
+ >>> G.add_edge(0, 1)
1719
+ >>> H = G.to_directed()
1720
+ >>> list(H.edges)
1721
+ [(0, 1)]
1722
+ """
1723
+ graph_class = self.to_directed_class()
1724
+ if as_view is True:
1725
+ return nx.graphviews.generic_graph_view(self, graph_class)
1726
+ # deepcopy when not a view
1727
+ G = graph_class()
1728
+ G.graph.update(deepcopy(self.graph))
1729
+ G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
1730
+ G.add_edges_from(
1731
+ (u, v, deepcopy(data))
1732
+ for u, nbrs in self._adj.items()
1733
+ for v, data in nbrs.items()
1734
+ )
1735
+ return G
1736
+
1737
+ def to_undirected(self, as_view=False):
1738
+ """Returns an undirected copy of the graph.
1739
+
1740
+ Parameters
1741
+ ----------
1742
+ as_view : bool (optional, default=False)
1743
+ If True return a view of the original undirected graph.
1744
+
1745
+ Returns
1746
+ -------
1747
+ G : Graph/MultiGraph
1748
+ A deepcopy of the graph.
1749
+
1750
+ See Also
1751
+ --------
1752
+ Graph, copy, add_edge, add_edges_from
1753
+
1754
+ Notes
1755
+ -----
1756
+ This returns a "deepcopy" of the edge, node, and
1757
+ graph attributes which attempts to completely copy
1758
+ all of the data and references.
1759
+
1760
+ This is in contrast to the similar `G = nx.DiGraph(D)` which returns a
1761
+ shallow copy of the data.
1762
+
1763
+ See the Python copy module for more information on shallow
1764
+ and deep copies, https://docs.python.org/3/library/copy.html.
1765
+
1766
+ Warning: If you have subclassed DiGraph to use dict-like objects
1767
+ in the data structure, those changes do not transfer to the
1768
+ Graph created by this method.
1769
+
1770
+ Examples
1771
+ --------
1772
+ >>> G = nx.path_graph(2) # or MultiGraph, etc
1773
+ >>> H = G.to_directed()
1774
+ >>> list(H.edges)
1775
+ [(0, 1), (1, 0)]
1776
+ >>> G2 = H.to_undirected()
1777
+ >>> list(G2.edges)
1778
+ [(0, 1)]
1779
+ """
1780
+ graph_class = self.to_undirected_class()
1781
+ if as_view is True:
1782
+ return nx.graphviews.generic_graph_view(self, graph_class)
1783
+ # deepcopy when not a view
1784
+ G = graph_class()
1785
+ G.graph.update(deepcopy(self.graph))
1786
+ G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
1787
+ G.add_edges_from(
1788
+ (u, v, deepcopy(d))
1789
+ for u, nbrs in self._adj.items()
1790
+ for v, d in nbrs.items()
1791
+ )
1792
+ return G
1793
+
1794
+ def subgraph(self, nodes):
1795
+ """Returns a SubGraph view of the subgraph induced on `nodes`.
1796
+
1797
+ The induced subgraph of the graph contains the nodes in `nodes`
1798
+ and the edges between those nodes.
1799
+
1800
+ Parameters
1801
+ ----------
1802
+ nodes : list, iterable
1803
+ A container of nodes which will be iterated through once.
1804
+
1805
+ Returns
1806
+ -------
1807
+ G : SubGraph View
1808
+ A subgraph view of the graph. The graph structure cannot be
1809
+ changed but node/edge attributes can and are shared with the
1810
+ original graph.
1811
+
1812
+ Notes
1813
+ -----
1814
+ The graph, edge and node attributes are shared with the original graph.
1815
+ Changes to the graph structure is ruled out by the view, but changes
1816
+ to attributes are reflected in the original graph.
1817
+
1818
+ To create a subgraph with its own copy of the edge/node attributes use:
1819
+ G.subgraph(nodes).copy()
1820
+
1821
+ For an inplace reduction of a graph to a subgraph you can remove nodes:
1822
+ G.remove_nodes_from([n for n in G if n not in set(nodes)])
1823
+
1824
+ Subgraph views are sometimes NOT what you want. In most cases where
1825
+ you want to do more than simply look at the induced edges, it makes
1826
+ more sense to just create the subgraph as its own graph with code like:
1827
+
1828
+ ::
1829
+
1830
+ # Create a subgraph SG based on a (possibly multigraph) G
1831
+ SG = G.__class__()
1832
+ SG.add_nodes_from((n, G.nodes[n]) for n in largest_wcc)
1833
+ if SG.is_multigraph():
1834
+ SG.add_edges_from(
1835
+ (n, nbr, key, d)
1836
+ for n, nbrs in G.adj.items()
1837
+ if n in largest_wcc
1838
+ for nbr, keydict in nbrs.items()
1839
+ if nbr in largest_wcc
1840
+ for key, d in keydict.items()
1841
+ )
1842
+ else:
1843
+ SG.add_edges_from(
1844
+ (n, nbr, d)
1845
+ for n, nbrs in G.adj.items()
1846
+ if n in largest_wcc
1847
+ for nbr, d in nbrs.items()
1848
+ if nbr in largest_wcc
1849
+ )
1850
+ SG.graph.update(G.graph)
1851
+
1852
+ Subgraphs are not guaranteed to preserve the order of nodes or edges
1853
+ as they appear in the original graph. For example:
1854
+
1855
+ >>> G = nx.Graph()
1856
+ >>> G.add_nodes_from(reversed(range(10)))
1857
+ >>> list(G)
1858
+ [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
1859
+ >>> list(G.subgraph([1, 3, 2]))
1860
+ [1, 2, 3]
1861
+
1862
+ Examples
1863
+ --------
1864
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1865
+ >>> H = G.subgraph([0, 1, 2])
1866
+ >>> list(H.edges)
1867
+ [(0, 1), (1, 2)]
1868
+ """
1869
+ induced_nodes = nx.filters.show_nodes(self.nbunch_iter(nodes))
1870
+ # if already a subgraph, don't make a chain
1871
+ subgraph = nx.subgraph_view
1872
+ if hasattr(self, "_NODE_OK"):
1873
+ return subgraph(
1874
+ self._graph, filter_node=induced_nodes, filter_edge=self._EDGE_OK
1875
+ )
1876
+ return subgraph(self, filter_node=induced_nodes)
1877
+
1878
+ def edge_subgraph(self, edges):
1879
+ """Returns the subgraph induced by the specified edges.
1880
+
1881
+ The induced subgraph contains each edge in `edges` and each
1882
+ node incident to any one of those edges.
1883
+
1884
+ Parameters
1885
+ ----------
1886
+ edges : iterable
1887
+ An iterable of edges in this graph.
1888
+
1889
+ Returns
1890
+ -------
1891
+ G : Graph
1892
+ An edge-induced subgraph of this graph with the same edge
1893
+ attributes.
1894
+
1895
+ Notes
1896
+ -----
1897
+ The graph, edge, and node attributes in the returned subgraph
1898
+ view are references to the corresponding attributes in the original
1899
+ graph. The view is read-only.
1900
+
1901
+ To create a full graph version of the subgraph with its own copy
1902
+ of the edge or node attributes, use::
1903
+
1904
+ G.edge_subgraph(edges).copy()
1905
+
1906
+ Examples
1907
+ --------
1908
+ >>> G = nx.path_graph(5)
1909
+ >>> H = G.edge_subgraph([(0, 1), (3, 4)])
1910
+ >>> list(H.nodes)
1911
+ [0, 1, 3, 4]
1912
+ >>> list(H.edges)
1913
+ [(0, 1), (3, 4)]
1914
+
1915
+ """
1916
+ return nx.edge_subgraph(self, edges)
1917
+
1918
+ def size(self, weight=None):
1919
+ """Returns the number of edges or total of all edge weights.
1920
+
1921
+ Parameters
1922
+ ----------
1923
+ weight : string or None, optional (default=None)
1924
+ The edge attribute that holds the numerical value used
1925
+ as a weight. If None, then each edge has weight 1.
1926
+
1927
+ Returns
1928
+ -------
1929
+ size : numeric
1930
+ The number of edges or
1931
+ (if weight keyword is provided) the total weight sum.
1932
+
1933
+ If weight is None, returns an int. Otherwise a float
1934
+ (or more general numeric if the weights are more general).
1935
+
1936
+ See Also
1937
+ --------
1938
+ number_of_edges
1939
+
1940
+ Examples
1941
+ --------
1942
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1943
+ >>> G.size()
1944
+ 3
1945
+
1946
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
1947
+ >>> G.add_edge("a", "b", weight=2)
1948
+ >>> G.add_edge("b", "c", weight=4)
1949
+ >>> G.size()
1950
+ 2
1951
+ >>> G.size(weight="weight")
1952
+ 6.0
1953
+ """
1954
+ s = sum(d for v, d in self.degree(weight=weight))
1955
+ # If `weight` is None, the sum of the degrees is guaranteed to be
1956
+ # even, so we can perform integer division and hence return an
1957
+ # integer. Otherwise, the sum of the weighted degrees is not
1958
+ # guaranteed to be an integer, so we perform "real" division.
1959
+ return s // 2 if weight is None else s / 2
1960
+
1961
+ def number_of_edges(self, u=None, v=None):
1962
+ """Returns the number of edges between two nodes.
1963
+
1964
+ Parameters
1965
+ ----------
1966
+ u, v : nodes, optional (default=all edges)
1967
+ If u and v are specified, return the number of edges between
1968
+ u and v. Otherwise return the total number of all edges.
1969
+
1970
+ Returns
1971
+ -------
1972
+ nedges : int
1973
+ The number of edges in the graph. If nodes `u` and `v` are
1974
+ specified return the number of edges between those nodes. If
1975
+ the graph is directed, this only returns the number of edges
1976
+ from `u` to `v`.
1977
+
1978
+ See Also
1979
+ --------
1980
+ size
1981
+
1982
+ Examples
1983
+ --------
1984
+ For undirected graphs, this method counts the total number of
1985
+ edges in the graph:
1986
+
1987
+ >>> G = nx.path_graph(4)
1988
+ >>> G.number_of_edges()
1989
+ 3
1990
+
1991
+ If you specify two nodes, this counts the total number of edges
1992
+ joining the two nodes:
1993
+
1994
+ >>> G.number_of_edges(0, 1)
1995
+ 1
1996
+
1997
+ For directed graphs, this method can count the total number of
1998
+ directed edges from `u` to `v`:
1999
+
2000
+ >>> G = nx.DiGraph()
2001
+ >>> G.add_edge(0, 1)
2002
+ >>> G.add_edge(1, 0)
2003
+ >>> G.number_of_edges(0, 1)
2004
+ 1
2005
+
2006
+ """
2007
+ if u is None:
2008
+ return int(self.size())
2009
+ if v in self._adj[u]:
2010
+ return 1
2011
+ return 0
2012
+
2013
+ def nbunch_iter(self, nbunch=None):
2014
+ """Returns an iterator over nodes contained in nbunch that are
2015
+ also in the graph.
2016
+
2017
+ The nodes in an iterable nbunch are checked for membership in the graph
2018
+ and if not are silently ignored.
2019
+
2020
+ Parameters
2021
+ ----------
2022
+ nbunch : single node, container, or all nodes (default= all nodes)
2023
+ The view will only report edges incident to these nodes.
2024
+
2025
+ Returns
2026
+ -------
2027
+ niter : iterator
2028
+ An iterator over nodes in nbunch that are also in the graph.
2029
+ If nbunch is None, iterate over all nodes in the graph.
2030
+
2031
+ Raises
2032
+ ------
2033
+ NetworkXError
2034
+ If nbunch is not a node or sequence of nodes.
2035
+ If a node in nbunch is not hashable.
2036
+
2037
+ See Also
2038
+ --------
2039
+ Graph.__iter__
2040
+
2041
+ Notes
2042
+ -----
2043
+ When nbunch is an iterator, the returned iterator yields values
2044
+ directly from nbunch, becoming exhausted when nbunch is exhausted.
2045
+
2046
+ To test whether nbunch is a single node, one can use
2047
+ "if nbunch in self:", even after processing with this routine.
2048
+
2049
+ If nbunch is not a node or a (possibly empty) sequence/iterator
2050
+ or None, a :exc:`NetworkXError` is raised. Also, if any object in
2051
+ nbunch is not hashable, a :exc:`NetworkXError` is raised.
2052
+ """
2053
+ if nbunch is None: # include all nodes via iterator
2054
+ bunch = iter(self._adj)
2055
+ elif nbunch in self: # if nbunch is a single node
2056
+ bunch = iter([nbunch])
2057
+ else: # if nbunch is a sequence of nodes
2058
+
2059
+ def bunch_iter(nlist, adj):
2060
+ try:
2061
+ for n in nlist:
2062
+ if n in adj:
2063
+ yield n
2064
+ except TypeError as err:
2065
+ exc, message = err, err.args[0]
2066
+ # capture error for non-sequence/iterator nbunch.
2067
+ if "iter" in message:
2068
+ exc = NetworkXError(
2069
+ "nbunch is not a node or a sequence of nodes."
2070
+ )
2071
+ # capture single nodes that are not in the graph.
2072
+ if "object is not iterable" in message:
2073
+ exc = NetworkXError(f"Node {nbunch} is not in the graph.")
2074
+ # capture error for unhashable node.
2075
+ if "hashable" in message:
2076
+ exc = NetworkXError(
2077
+ f"Node {n} in sequence nbunch is not a valid node."
2078
+ )
2079
+ raise exc
2080
+
2081
+ bunch = bunch_iter(nbunch, self._adj)
2082
+ return bunch
lib/python3.12/site-packages/networkx/classes/graphviews.py ADDED
@@ -0,0 +1,269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """View of Graphs as SubGraph, Reverse, Directed, Undirected.
2
+
3
+ In some algorithms it is convenient to temporarily morph
4
+ a graph to exclude some nodes or edges. It should be better
5
+ to do that via a view than to remove and then re-add.
6
+ In other algorithms it is convenient to temporarily morph
7
+ a graph to reverse directed edges, or treat a directed graph
8
+ as undirected, etc. This module provides those graph views.
9
+
10
+ The resulting views are essentially read-only graphs that
11
+ report data from the original graph object. We provide an
12
+ attribute G._graph which points to the underlying graph object.
13
+
14
+ Note: Since graphviews look like graphs, one can end up with
15
+ view-of-view-of-view chains. Be careful with chains because
16
+ they become very slow with about 15 nested views.
17
+ For the common simple case of node induced subgraphs created
18
+ from the graph class, we short-cut the chain by returning a
19
+ subgraph of the original graph directly rather than a subgraph
20
+ of a subgraph. We are careful not to disrupt any edge filter in
21
+ the middle subgraph. In general, determining how to short-cut
22
+ the chain is tricky and much harder with restricted_views than
23
+ with induced subgraphs.
24
+ Often it is easiest to use .copy() to avoid chains.
25
+ """
26
+
27
+ import networkx as nx
28
+ from networkx.classes.coreviews import (
29
+ FilterAdjacency,
30
+ FilterAtlas,
31
+ FilterMultiAdjacency,
32
+ UnionAdjacency,
33
+ UnionMultiAdjacency,
34
+ )
35
+ from networkx.classes.filters import no_filter
36
+ from networkx.exception import NetworkXError
37
+ from networkx.utils import not_implemented_for
38
+
39
+ __all__ = ["generic_graph_view", "subgraph_view", "reverse_view"]
40
+
41
+
42
+ def generic_graph_view(G, create_using=None):
43
+ """Returns a read-only view of `G`.
44
+
45
+ The graph `G` and its attributes are not copied but viewed through the new graph object
46
+ of the same class as `G` (or of the class specified in `create_using`).
47
+
48
+ Parameters
49
+ ----------
50
+ G : graph
51
+ A directed/undirected graph/multigraph.
52
+
53
+ create_using : NetworkX graph constructor, optional (default=None)
54
+ Graph type to create. If graph instance, then cleared before populated.
55
+ If `None`, then the appropriate Graph type is inferred from `G`.
56
+
57
+ Returns
58
+ -------
59
+ newG : graph
60
+ A view of the input graph `G` and its attributes as viewed through
61
+ the `create_using` class.
62
+
63
+ Raises
64
+ ------
65
+ NetworkXError
66
+ If `G` is a multigraph (or multidigraph) but `create_using` is not, or vice versa.
67
+
68
+ Notes
69
+ -----
70
+ The returned graph view is read-only (cannot modify the graph).
71
+ Yet the view reflects any changes in `G`. The intent is to mimic dict views.
72
+
73
+ Examples
74
+ --------
75
+ >>> G = nx.Graph()
76
+ >>> G.add_edge(1, 2, weight=0.3)
77
+ >>> G.add_edge(2, 3, weight=0.5)
78
+ >>> G.edges(data=True)
79
+ EdgeDataView([(1, 2, {'weight': 0.3}), (2, 3, {'weight': 0.5})])
80
+
81
+ The view exposes the attributes from the original graph.
82
+
83
+ >>> viewG = nx.graphviews.generic_graph_view(G)
84
+ >>> viewG.edges(data=True)
85
+ EdgeDataView([(1, 2, {'weight': 0.3}), (2, 3, {'weight': 0.5})])
86
+
87
+ Changes to `G` are reflected in `viewG`.
88
+
89
+ >>> G.remove_edge(2, 3)
90
+ >>> G.edges(data=True)
91
+ EdgeDataView([(1, 2, {'weight': 0.3})])
92
+
93
+ >>> viewG.edges(data=True)
94
+ EdgeDataView([(1, 2, {'weight': 0.3})])
95
+
96
+ We can change the graph type with the `create_using` parameter.
97
+
98
+ >>> type(G)
99
+ <class 'networkx.classes.graph.Graph'>
100
+ >>> viewDG = nx.graphviews.generic_graph_view(G, create_using=nx.DiGraph)
101
+ >>> type(viewDG)
102
+ <class 'networkx.classes.digraph.DiGraph'>
103
+ """
104
+ if create_using is None:
105
+ newG = G.__class__()
106
+ else:
107
+ newG = nx.empty_graph(0, create_using)
108
+ if G.is_multigraph() != newG.is_multigraph():
109
+ raise NetworkXError("Multigraph for G must agree with create_using")
110
+ newG = nx.freeze(newG)
111
+
112
+ # create view by assigning attributes from G
113
+ newG._graph = G
114
+ newG.graph = G.graph
115
+
116
+ newG._node = G._node
117
+ if newG.is_directed():
118
+ if G.is_directed():
119
+ newG._succ = G._succ
120
+ newG._pred = G._pred
121
+ # newG._adj is synced with _succ
122
+ else:
123
+ newG._succ = G._adj
124
+ newG._pred = G._adj
125
+ # newG._adj is synced with _succ
126
+ elif G.is_directed():
127
+ if G.is_multigraph():
128
+ newG._adj = UnionMultiAdjacency(G._succ, G._pred)
129
+ else:
130
+ newG._adj = UnionAdjacency(G._succ, G._pred)
131
+ else:
132
+ newG._adj = G._adj
133
+ return newG
134
+
135
+
136
+ def subgraph_view(G, *, filter_node=no_filter, filter_edge=no_filter):
137
+ """View of `G` applying a filter on nodes and edges.
138
+
139
+ `subgraph_view` provides a read-only view of the input graph that excludes
140
+ nodes and edges based on the outcome of two filter functions `filter_node`
141
+ and `filter_edge`.
142
+
143
+ The `filter_node` function takes one argument --- the node --- and returns
144
+ `True` if the node should be included in the subgraph, and `False` if it
145
+ should not be included.
146
+
147
+ The `filter_edge` function takes two (or three arguments if `G` is a
148
+ multi-graph) --- the nodes describing an edge, plus the edge-key if
149
+ parallel edges are possible --- and returns `True` if the edge should be
150
+ included in the subgraph, and `False` if it should not be included.
151
+
152
+ Both node and edge filter functions are called on graph elements as they
153
+ are queried, meaning there is no up-front cost to creating the view.
154
+
155
+ Parameters
156
+ ----------
157
+ G : networkx.Graph
158
+ A directed/undirected graph/multigraph
159
+
160
+ filter_node : callable, optional
161
+ A function taking a node as input, which returns `True` if the node
162
+ should appear in the view.
163
+
164
+ filter_edge : callable, optional
165
+ A function taking as input the two nodes describing an edge (plus the
166
+ edge-key if `G` is a multi-graph), which returns `True` if the edge
167
+ should appear in the view.
168
+
169
+ Returns
170
+ -------
171
+ graph : networkx.Graph
172
+ A read-only graph view of the input graph.
173
+
174
+ Examples
175
+ --------
176
+ >>> G = nx.path_graph(6)
177
+
178
+ Filter functions operate on the node, and return `True` if the node should
179
+ appear in the view:
180
+
181
+ >>> def filter_node(n1):
182
+ ... return n1 != 5
183
+ >>> view = nx.subgraph_view(G, filter_node=filter_node)
184
+ >>> view.nodes()
185
+ NodeView((0, 1, 2, 3, 4))
186
+
187
+ We can use a closure pattern to filter graph elements based on additional
188
+ data --- for example, filtering on edge data attached to the graph:
189
+
190
+ >>> G[3][4]["cross_me"] = False
191
+ >>> def filter_edge(n1, n2):
192
+ ... return G[n1][n2].get("cross_me", True)
193
+ >>> view = nx.subgraph_view(G, filter_edge=filter_edge)
194
+ >>> view.edges()
195
+ EdgeView([(0, 1), (1, 2), (2, 3), (4, 5)])
196
+
197
+ >>> view = nx.subgraph_view(
198
+ ... G,
199
+ ... filter_node=filter_node,
200
+ ... filter_edge=filter_edge,
201
+ ... )
202
+ >>> view.nodes()
203
+ NodeView((0, 1, 2, 3, 4))
204
+ >>> view.edges()
205
+ EdgeView([(0, 1), (1, 2), (2, 3)])
206
+ """
207
+ newG = nx.freeze(G.__class__())
208
+ newG._NODE_OK = filter_node
209
+ newG._EDGE_OK = filter_edge
210
+
211
+ # create view by assigning attributes from G
212
+ newG._graph = G
213
+ newG.graph = G.graph
214
+
215
+ newG._node = FilterAtlas(G._node, filter_node)
216
+ if G.is_multigraph():
217
+ Adj = FilterMultiAdjacency
218
+
219
+ def reverse_edge(u, v, k=None):
220
+ return filter_edge(v, u, k)
221
+
222
+ else:
223
+ Adj = FilterAdjacency
224
+
225
+ def reverse_edge(u, v, k=None):
226
+ return filter_edge(v, u)
227
+
228
+ if G.is_directed():
229
+ newG._succ = Adj(G._succ, filter_node, filter_edge)
230
+ newG._pred = Adj(G._pred, filter_node, reverse_edge)
231
+ # newG._adj is synced with _succ
232
+ else:
233
+ newG._adj = Adj(G._adj, filter_node, filter_edge)
234
+ return newG
235
+
236
+
237
+ @not_implemented_for("undirected")
238
+ def reverse_view(G):
239
+ """View of `G` with edge directions reversed
240
+
241
+ `reverse_view` returns a read-only view of the input graph where
242
+ edge directions are reversed.
243
+
244
+ Identical to digraph.reverse(copy=False)
245
+
246
+ Parameters
247
+ ----------
248
+ G : networkx.DiGraph
249
+
250
+ Returns
251
+ -------
252
+ graph : networkx.DiGraph
253
+
254
+ Examples
255
+ --------
256
+ >>> G = nx.DiGraph()
257
+ >>> G.add_edge(1, 2)
258
+ >>> G.add_edge(2, 3)
259
+ >>> G.edges()
260
+ OutEdgeView([(1, 2), (2, 3)])
261
+
262
+ >>> view = nx.reverse_view(G)
263
+ >>> view.edges()
264
+ OutEdgeView([(2, 1), (3, 2)])
265
+ """
266
+ newG = generic_graph_view(G)
267
+ newG._succ, newG._pred = G._pred, G._succ
268
+ # newG._adj is synced with _succ
269
+ return newG
lib/python3.12/site-packages/networkx/classes/multidigraph.py ADDED
@@ -0,0 +1,977 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Base class for MultiDiGraph."""
2
+
3
+ from copy import deepcopy
4
+ from functools import cached_property
5
+
6
+ import networkx as nx
7
+ from networkx import convert
8
+ from networkx.classes.coreviews import MultiAdjacencyView
9
+ from networkx.classes.digraph import DiGraph
10
+ from networkx.classes.multigraph import MultiGraph
11
+ from networkx.classes.reportviews import (
12
+ DiMultiDegreeView,
13
+ InMultiDegreeView,
14
+ InMultiEdgeView,
15
+ OutMultiDegreeView,
16
+ OutMultiEdgeView,
17
+ )
18
+ from networkx.exception import NetworkXError
19
+
20
+ __all__ = ["MultiDiGraph"]
21
+
22
+
23
+ class MultiDiGraph(MultiGraph, DiGraph):
24
+ """A directed graph class that can store multiedges.
25
+
26
+ Multiedges are multiple edges between two nodes. Each edge
27
+ can hold optional data or attributes.
28
+
29
+ A MultiDiGraph holds directed edges. Self loops are allowed.
30
+
31
+ Nodes can be arbitrary (hashable) Python objects with optional
32
+ key/value attributes. By convention `None` is not used as a node.
33
+
34
+ Edges are represented as links between nodes with optional
35
+ key/value attributes.
36
+
37
+ Parameters
38
+ ----------
39
+ incoming_graph_data : input graph (optional, default: None)
40
+ Data to initialize graph. If None (default) an empty
41
+ graph is created. The data can be any format that is supported
42
+ by the to_networkx_graph() function, currently including edge list,
43
+ dict of dicts, dict of lists, NetworkX graph, 2D NumPy array, SciPy
44
+ sparse matrix, or PyGraphviz graph.
45
+
46
+ multigraph_input : bool or None (default None)
47
+ Note: Only used when `incoming_graph_data` is a dict.
48
+ If True, `incoming_graph_data` is assumed to be a
49
+ dict-of-dict-of-dict-of-dict structure keyed by
50
+ node to neighbor to edge keys to edge data for multi-edges.
51
+ A NetworkXError is raised if this is not the case.
52
+ If False, :func:`to_networkx_graph` is used to try to determine
53
+ the dict's graph data structure as either a dict-of-dict-of-dict
54
+ keyed by node to neighbor to edge data, or a dict-of-iterable
55
+ keyed by node to neighbors.
56
+ If None, the treatment for True is tried, but if it fails,
57
+ the treatment for False is tried.
58
+
59
+ attr : keyword arguments, optional (default= no attributes)
60
+ Attributes to add to graph as key=value pairs.
61
+
62
+ See Also
63
+ --------
64
+ Graph
65
+ DiGraph
66
+ MultiGraph
67
+
68
+ Examples
69
+ --------
70
+ Create an empty graph structure (a "null graph") with no nodes and
71
+ no edges.
72
+
73
+ >>> G = nx.MultiDiGraph()
74
+
75
+ G can be grown in several ways.
76
+
77
+ **Nodes:**
78
+
79
+ Add one node at a time:
80
+
81
+ >>> G.add_node(1)
82
+
83
+ Add the nodes from any container (a list, dict, set or
84
+ even the lines from a file or the nodes from another graph).
85
+
86
+ >>> G.add_nodes_from([2, 3])
87
+ >>> G.add_nodes_from(range(100, 110))
88
+ >>> H = nx.path_graph(10)
89
+ >>> G.add_nodes_from(H)
90
+
91
+ In addition to strings and integers any hashable Python object
92
+ (except None) can represent a node, e.g. a customized node object,
93
+ or even another Graph.
94
+
95
+ >>> G.add_node(H)
96
+
97
+ **Edges:**
98
+
99
+ G can also be grown by adding edges.
100
+
101
+ Add one edge,
102
+
103
+ >>> key = G.add_edge(1, 2)
104
+
105
+ a list of edges,
106
+
107
+ >>> keys = G.add_edges_from([(1, 2), (1, 3)])
108
+
109
+ or a collection of edges,
110
+
111
+ >>> keys = G.add_edges_from(H.edges)
112
+
113
+ If some edges connect nodes not yet in the graph, the nodes
114
+ are added automatically. If an edge already exists, an additional
115
+ edge is created and stored using a key to identify the edge.
116
+ By default the key is the lowest unused integer.
117
+
118
+ >>> keys = G.add_edges_from([(4, 5, dict(route=282)), (4, 5, dict(route=37))])
119
+ >>> G[4]
120
+ AdjacencyView({5: {0: {}, 1: {'route': 282}, 2: {'route': 37}}})
121
+
122
+ **Attributes:**
123
+
124
+ Each graph, node, and edge can hold key/value attribute pairs
125
+ in an associated attribute dictionary (the keys must be hashable).
126
+ By default these are empty, but can be added or changed using
127
+ add_edge, add_node or direct manipulation of the attribute
128
+ dictionaries named graph, node and edge respectively.
129
+
130
+ >>> G = nx.MultiDiGraph(day="Friday")
131
+ >>> G.graph
132
+ {'day': 'Friday'}
133
+
134
+ Add node attributes using add_node(), add_nodes_from() or G.nodes
135
+
136
+ >>> G.add_node(1, time="5pm")
137
+ >>> G.add_nodes_from([3], time="2pm")
138
+ >>> G.nodes[1]
139
+ {'time': '5pm'}
140
+ >>> G.nodes[1]["room"] = 714
141
+ >>> del G.nodes[1]["room"] # remove attribute
142
+ >>> list(G.nodes(data=True))
143
+ [(1, {'time': '5pm'}), (3, {'time': '2pm'})]
144
+
145
+ Add edge attributes using add_edge(), add_edges_from(), subscript
146
+ notation, or G.edges.
147
+
148
+ >>> key = G.add_edge(1, 2, weight=4.7)
149
+ >>> keys = G.add_edges_from([(3, 4), (4, 5)], color="red")
150
+ >>> keys = G.add_edges_from([(1, 2, {"color": "blue"}), (2, 3, {"weight": 8})])
151
+ >>> G[1][2][0]["weight"] = 4.7
152
+ >>> G.edges[1, 2, 0]["weight"] = 4
153
+
154
+ Warning: we protect the graph data structure by making `G.edges[1,
155
+ 2, 0]` a read-only dict-like structure. However, you can assign to
156
+ attributes in e.g. `G.edges[1, 2, 0]`. Thus, use 2 sets of brackets
157
+ to add/change data attributes: `G.edges[1, 2, 0]['weight'] = 4`
158
+ (for multigraphs the edge key is required: `MG.edges[u, v,
159
+ key][name] = value`).
160
+
161
+ **Shortcuts:**
162
+
163
+ Many common graph features allow python syntax to speed reporting.
164
+
165
+ >>> 1 in G # check if node in graph
166
+ True
167
+ >>> [n for n in G if n < 3] # iterate through nodes
168
+ [1, 2]
169
+ >>> len(G) # number of nodes in graph
170
+ 5
171
+ >>> G[1] # adjacency dict-like view mapping neighbor -> edge key -> edge attributes
172
+ AdjacencyView({2: {0: {'weight': 4}, 1: {'color': 'blue'}}})
173
+
174
+ Often the best way to traverse all edges of a graph is via the neighbors.
175
+ The neighbors are available as an adjacency-view `G.adj` object or via
176
+ the method `G.adjacency()`.
177
+
178
+ >>> for n, nbrsdict in G.adjacency():
179
+ ... for nbr, keydict in nbrsdict.items():
180
+ ... for key, eattr in keydict.items():
181
+ ... if "weight" in eattr:
182
+ ... # Do something useful with the edges
183
+ ... pass
184
+
185
+ But the edges() method is often more convenient:
186
+
187
+ >>> for u, v, keys, weight in G.edges(data="weight", keys=True):
188
+ ... if weight is not None:
189
+ ... # Do something useful with the edges
190
+ ... pass
191
+
192
+ **Reporting:**
193
+
194
+ Simple graph information is obtained using methods and object-attributes.
195
+ Reporting usually provides views instead of containers to reduce memory
196
+ usage. The views update as the graph is updated similarly to dict-views.
197
+ The objects `nodes`, `edges` and `adj` provide access to data attributes
198
+ via lookup (e.g. `nodes[n]`, `edges[u, v, k]`, `adj[u][v]`) and iteration
199
+ (e.g. `nodes.items()`, `nodes.data('color')`,
200
+ `nodes.data('color', default='blue')` and similarly for `edges`)
201
+ Views exist for `nodes`, `edges`, `neighbors()`/`adj` and `degree`.
202
+
203
+ For details on these and other miscellaneous methods, see below.
204
+
205
+ **Subclasses (Advanced):**
206
+
207
+ The MultiDiGraph class uses a dict-of-dict-of-dict-of-dict structure.
208
+ The outer dict (node_dict) holds adjacency information keyed by node.
209
+ The next dict (adjlist_dict) represents the adjacency information
210
+ and holds edge_key dicts keyed by neighbor. The edge_key dict holds
211
+ each edge_attr dict keyed by edge key. The inner dict
212
+ (edge_attr_dict) represents the edge data and holds edge attribute
213
+ values keyed by attribute names.
214
+
215
+ Each of these four dicts in the dict-of-dict-of-dict-of-dict
216
+ structure can be replaced by a user defined dict-like object.
217
+ In general, the dict-like features should be maintained but
218
+ extra features can be added. To replace one of the dicts create
219
+ a new graph class by changing the class(!) variable holding the
220
+ factory for that dict-like structure. The variable names are
221
+ node_dict_factory, node_attr_dict_factory, adjlist_inner_dict_factory,
222
+ adjlist_outer_dict_factory, edge_key_dict_factory, edge_attr_dict_factory
223
+ and graph_attr_dict_factory.
224
+
225
+ node_dict_factory : function, (default: dict)
226
+ Factory function to be used to create the dict containing node
227
+ attributes, keyed by node id.
228
+ It should require no arguments and return a dict-like object
229
+
230
+ node_attr_dict_factory: function, (default: dict)
231
+ Factory function to be used to create the node attribute
232
+ dict which holds attribute values keyed by attribute name.
233
+ It should require no arguments and return a dict-like object
234
+
235
+ adjlist_outer_dict_factory : function, (default: dict)
236
+ Factory function to be used to create the outer-most dict
237
+ in the data structure that holds adjacency info keyed by node.
238
+ It should require no arguments and return a dict-like object.
239
+
240
+ adjlist_inner_dict_factory : function, (default: dict)
241
+ Factory function to be used to create the adjacency list
242
+ dict which holds multiedge key dicts keyed by neighbor.
243
+ It should require no arguments and return a dict-like object.
244
+
245
+ edge_key_dict_factory : function, (default: dict)
246
+ Factory function to be used to create the edge key dict
247
+ which holds edge data keyed by edge key.
248
+ It should require no arguments and return a dict-like object.
249
+
250
+ edge_attr_dict_factory : function, (default: dict)
251
+ Factory function to be used to create the edge attribute
252
+ dict which holds attribute values keyed by attribute name.
253
+ It should require no arguments and return a dict-like object.
254
+
255
+ graph_attr_dict_factory : function, (default: dict)
256
+ Factory function to be used to create the graph attribute
257
+ dict which holds attribute values keyed by attribute name.
258
+ It should require no arguments and return a dict-like object.
259
+
260
+ Typically, if your extension doesn't impact the data structure all
261
+ methods will inherited without issue except: `to_directed/to_undirected`.
262
+ By default these methods create a DiGraph/Graph class and you probably
263
+ want them to create your extension of a DiGraph/Graph. To facilitate
264
+ this we define two class variables that you can set in your subclass.
265
+
266
+ to_directed_class : callable, (default: DiGraph or MultiDiGraph)
267
+ Class to create a new graph structure in the `to_directed` method.
268
+ If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used.
269
+
270
+ to_undirected_class : callable, (default: Graph or MultiGraph)
271
+ Class to create a new graph structure in the `to_undirected` method.
272
+ If `None`, a NetworkX class (Graph or MultiGraph) is used.
273
+
274
+ **Subclassing Example**
275
+
276
+ Create a low memory graph class that effectively disallows edge
277
+ attributes by using a single attribute dict for all edges.
278
+ This reduces the memory used, but you lose edge attributes.
279
+
280
+ >>> class ThinGraph(nx.Graph):
281
+ ... all_edge_dict = {"weight": 1}
282
+ ...
283
+ ... def single_edge_dict(self):
284
+ ... return self.all_edge_dict
285
+ ...
286
+ ... edge_attr_dict_factory = single_edge_dict
287
+ >>> G = ThinGraph()
288
+ >>> G.add_edge(2, 1)
289
+ >>> G[2][1]
290
+ {'weight': 1}
291
+ >>> G.add_edge(2, 2)
292
+ >>> G[2][1] is G[2][2]
293
+ True
294
+ """
295
+
296
+ # node_dict_factory = dict # already assigned in Graph
297
+ # adjlist_outer_dict_factory = dict
298
+ # adjlist_inner_dict_factory = dict
299
+ edge_key_dict_factory = dict
300
+ # edge_attr_dict_factory = dict
301
+
302
+ # This __new__ method just does what Python itself does automatically.
303
+ # We include it here as part of the dispatchable/backend interface.
304
+ # If your goal is to understand how the graph classes work, you can ignore
305
+ # this method, even when subclassing the base classes. If you are subclassing
306
+ # in order to provide a backend that allows class instantiation, this method
307
+ # can be overridden to return your own backend graph class.
308
+ @nx._dispatchable(name="multidigraph__new__", graphs=None, returns_graph=True)
309
+ def __new__(cls, *args, **kwargs):
310
+ return object.__new__(cls)
311
+
312
+ def __init__(self, incoming_graph_data=None, multigraph_input=None, **attr):
313
+ """Initialize a graph with edges, name, or graph attributes.
314
+
315
+ Parameters
316
+ ----------
317
+ incoming_graph_data : input graph
318
+ Data to initialize graph. If incoming_graph_data=None (default)
319
+ an empty graph is created. The data can be an edge list, or any
320
+ NetworkX graph object. If the corresponding optional Python
321
+ packages are installed the data can also be a 2D NumPy array, a
322
+ SciPy sparse array, or a PyGraphviz graph.
323
+
324
+ multigraph_input : bool or None (default None)
325
+ Note: Only used when `incoming_graph_data` is a dict.
326
+ If True, `incoming_graph_data` is assumed to be a
327
+ dict-of-dict-of-dict-of-dict structure keyed by
328
+ node to neighbor to edge keys to edge data for multi-edges.
329
+ A NetworkXError is raised if this is not the case.
330
+ If False, :func:`to_networkx_graph` is used to try to determine
331
+ the dict's graph data structure as either a dict-of-dict-of-dict
332
+ keyed by node to neighbor to edge data, or a dict-of-iterable
333
+ keyed by node to neighbors.
334
+ If None, the treatment for True is tried, but if it fails,
335
+ the treatment for False is tried.
336
+
337
+ attr : keyword arguments, optional (default= no attributes)
338
+ Attributes to add to graph as key=value pairs.
339
+
340
+ See Also
341
+ --------
342
+ convert
343
+
344
+ Examples
345
+ --------
346
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
347
+ >>> G = nx.Graph(name="my graph")
348
+ >>> e = [(1, 2), (2, 3), (3, 4)] # list of edges
349
+ >>> G = nx.Graph(e)
350
+
351
+ Arbitrary graph attribute pairs (key=value) may be assigned
352
+
353
+ >>> G = nx.Graph(e, day="Friday")
354
+ >>> G.graph
355
+ {'day': 'Friday'}
356
+
357
+ """
358
+ attr.pop("backend", None) # Ignore explicit `backend="networkx"`
359
+ # multigraph_input can be None/True/False. So check "is not False"
360
+ if isinstance(incoming_graph_data, dict) and multigraph_input is not False:
361
+ DiGraph.__init__(self)
362
+ try:
363
+ convert.from_dict_of_dicts(
364
+ incoming_graph_data, create_using=self, multigraph_input=True
365
+ )
366
+ self.graph.update(attr)
367
+ except Exception as err:
368
+ if multigraph_input is True:
369
+ raise nx.NetworkXError(
370
+ f"converting multigraph_input raised:\n{type(err)}: {err}"
371
+ )
372
+ DiGraph.__init__(self, incoming_graph_data, **attr)
373
+ else:
374
+ DiGraph.__init__(self, incoming_graph_data, **attr)
375
+
376
+ @cached_property
377
+ def adj(self):
378
+ """Graph adjacency object holding the neighbors of each node.
379
+
380
+ This object is a read-only dict-like structure with node keys
381
+ and neighbor-dict values. The neighbor-dict is keyed by neighbor
382
+ to the edgekey-dict. So `G.adj[3][2][0]['color'] = 'blue'` sets
383
+ the color of the edge `(3, 2, 0)` to `"blue"`.
384
+
385
+ Iterating over G.adj behaves like a dict. Useful idioms include
386
+ `for nbr, datadict in G.adj[n].items():`.
387
+
388
+ The neighbor information is also provided by subscripting the graph.
389
+ So `for nbr, foovalue in G[node].data('foo', default=1):` works.
390
+
391
+ For directed graphs, `G.adj` holds outgoing (successor) info.
392
+ """
393
+ return MultiAdjacencyView(self._succ)
394
+
395
+ @cached_property
396
+ def succ(self):
397
+ """Graph adjacency object holding the successors of each node.
398
+
399
+ This object is a read-only dict-like structure with node keys
400
+ and neighbor-dict values. The neighbor-dict is keyed by neighbor
401
+ to the edgekey-dict. So `G.adj[3][2][0]['color'] = 'blue'` sets
402
+ the color of the edge `(3, 2, 0)` to `"blue"`.
403
+
404
+ Iterating over G.adj behaves like a dict. Useful idioms include
405
+ `for nbr, datadict in G.adj[n].items():`.
406
+
407
+ The neighbor information is also provided by subscripting the graph.
408
+ So `for nbr, foovalue in G[node].data('foo', default=1):` works.
409
+
410
+ For directed graphs, `G.succ` is identical to `G.adj`.
411
+ """
412
+ return MultiAdjacencyView(self._succ)
413
+
414
+ @cached_property
415
+ def pred(self):
416
+ """Graph adjacency object holding the predecessors of each node.
417
+
418
+ This object is a read-only dict-like structure with node keys
419
+ and neighbor-dict values. The neighbor-dict is keyed by neighbor
420
+ to the edgekey-dict. So `G.adj[3][2][0]['color'] = 'blue'` sets
421
+ the color of the edge `(3, 2, 0)` to `"blue"`.
422
+
423
+ Iterating over G.adj behaves like a dict. Useful idioms include
424
+ `for nbr, datadict in G.adj[n].items():`.
425
+ """
426
+ return MultiAdjacencyView(self._pred)
427
+
428
+ def add_edge(self, u_for_edge, v_for_edge, key=None, **attr):
429
+ """Add an edge between u and v.
430
+
431
+ The nodes u and v will be automatically added if they are
432
+ not already in the graph.
433
+
434
+ Edge attributes can be specified with keywords or by directly
435
+ accessing the edge's attribute dictionary. See examples below.
436
+
437
+ Parameters
438
+ ----------
439
+ u_for_edge, v_for_edge : nodes
440
+ Nodes can be, for example, strings or numbers.
441
+ Nodes must be hashable (and not None) Python objects.
442
+ key : hashable identifier, optional (default=lowest unused integer)
443
+ Used to distinguish multiedges between a pair of nodes.
444
+ attr : keyword arguments, optional
445
+ Edge data (or labels or objects) can be assigned using
446
+ keyword arguments.
447
+
448
+ Returns
449
+ -------
450
+ The edge key assigned to the edge.
451
+
452
+ See Also
453
+ --------
454
+ add_edges_from : add a collection of edges
455
+
456
+ Notes
457
+ -----
458
+ To replace/update edge data, use the optional key argument
459
+ to identify a unique edge. Otherwise a new edge will be created.
460
+
461
+ NetworkX algorithms designed for weighted graphs cannot use
462
+ multigraphs directly because it is not clear how to handle
463
+ multiedge weights. Convert to Graph using edge attribute
464
+ 'weight' to enable weighted graph algorithms.
465
+
466
+ Default keys are generated using the method `new_edge_key()`.
467
+ This method can be overridden by subclassing the base class and
468
+ providing a custom `new_edge_key()` method.
469
+
470
+ Examples
471
+ --------
472
+ The following all add the edge e=(1, 2) to graph G:
473
+
474
+ >>> G = nx.MultiDiGraph()
475
+ >>> e = (1, 2)
476
+ >>> key = G.add_edge(1, 2) # explicit two-node form
477
+ >>> G.add_edge(*e) # single edge as tuple of two nodes
478
+ 1
479
+ >>> G.add_edges_from([(1, 2)]) # add edges from iterable container
480
+ [2]
481
+
482
+ Associate data to edges using keywords:
483
+
484
+ >>> key = G.add_edge(1, 2, weight=3)
485
+ >>> key = G.add_edge(1, 2, key=0, weight=4) # update data for key=0
486
+ >>> key = G.add_edge(1, 3, weight=7, capacity=15, length=342.7)
487
+
488
+ For non-string attribute keys, use subscript notation.
489
+
490
+ >>> ekey = G.add_edge(1, 2)
491
+ >>> G[1][2][0].update({0: 5})
492
+ >>> G.edges[1, 2, 0].update({0: 5})
493
+ """
494
+ u, v = u_for_edge, v_for_edge
495
+ # add nodes
496
+ if u not in self._succ:
497
+ if u is None:
498
+ raise ValueError("None cannot be a node")
499
+ self._succ[u] = self.adjlist_inner_dict_factory()
500
+ self._pred[u] = self.adjlist_inner_dict_factory()
501
+ self._node[u] = self.node_attr_dict_factory()
502
+ if v not in self._succ:
503
+ if v is None:
504
+ raise ValueError("None cannot be a node")
505
+ self._succ[v] = self.adjlist_inner_dict_factory()
506
+ self._pred[v] = self.adjlist_inner_dict_factory()
507
+ self._node[v] = self.node_attr_dict_factory()
508
+ if key is None:
509
+ key = self.new_edge_key(u, v)
510
+ if v in self._succ[u]:
511
+ keydict = self._adj[u][v]
512
+ datadict = keydict.get(key, self.edge_attr_dict_factory())
513
+ datadict.update(attr)
514
+ keydict[key] = datadict
515
+ else:
516
+ # selfloops work this way without special treatment
517
+ datadict = self.edge_attr_dict_factory()
518
+ datadict.update(attr)
519
+ keydict = self.edge_key_dict_factory()
520
+ keydict[key] = datadict
521
+ self._succ[u][v] = keydict
522
+ self._pred[v][u] = keydict
523
+ nx._clear_cache(self)
524
+ return key
525
+
526
+ def remove_edge(self, u, v, key=None):
527
+ """Remove an edge between u and v.
528
+
529
+ Parameters
530
+ ----------
531
+ u, v : nodes
532
+ Remove an edge between nodes u and v.
533
+ key : hashable identifier, optional (default=None)
534
+ Used to distinguish multiple edges between a pair of nodes.
535
+ If None, remove a single edge between u and v. If there are
536
+ multiple edges, removes the last edge added in terms of
537
+ insertion order.
538
+
539
+ Raises
540
+ ------
541
+ NetworkXError
542
+ If there is not an edge between u and v, or
543
+ if there is no edge with the specified key.
544
+
545
+ See Also
546
+ --------
547
+ remove_edges_from : remove a collection of edges
548
+
549
+ Examples
550
+ --------
551
+ >>> G = nx.MultiDiGraph()
552
+ >>> nx.add_path(G, [0, 1, 2, 3])
553
+ >>> G.remove_edge(0, 1)
554
+ >>> e = (1, 2)
555
+ >>> G.remove_edge(*e) # unpacks e from an edge tuple
556
+
557
+ For multiple edges
558
+
559
+ >>> G = nx.MultiDiGraph()
560
+ >>> G.add_edges_from([(1, 2), (1, 2), (1, 2)]) # key_list returned
561
+ [0, 1, 2]
562
+
563
+ When ``key=None`` (the default), edges are removed in the opposite
564
+ order that they were added:
565
+
566
+ >>> G.remove_edge(1, 2)
567
+ >>> G.edges(keys=True)
568
+ OutMultiEdgeView([(1, 2, 0), (1, 2, 1)])
569
+
570
+ For edges with keys
571
+
572
+ >>> G = nx.MultiDiGraph()
573
+ >>> G.add_edge(1, 2, key="first")
574
+ 'first'
575
+ >>> G.add_edge(1, 2, key="second")
576
+ 'second'
577
+ >>> G.remove_edge(1, 2, key="first")
578
+ >>> G.edges(keys=True)
579
+ OutMultiEdgeView([(1, 2, 'second')])
580
+
581
+ """
582
+ try:
583
+ d = self._adj[u][v]
584
+ except KeyError as err:
585
+ raise NetworkXError(f"The edge {u}-{v} is not in the graph.") from err
586
+ # remove the edge with specified data
587
+ if key is None:
588
+ d.popitem()
589
+ else:
590
+ try:
591
+ del d[key]
592
+ except KeyError as err:
593
+ msg = f"The edge {u}-{v} with key {key} is not in the graph."
594
+ raise NetworkXError(msg) from err
595
+ if len(d) == 0:
596
+ # remove the key entries if last edge
597
+ del self._succ[u][v]
598
+ del self._pred[v][u]
599
+ nx._clear_cache(self)
600
+
601
+ @cached_property
602
+ def edges(self):
603
+ """An OutMultiEdgeView of the Graph as G.edges or G.edges().
604
+
605
+ edges(self, nbunch=None, data=False, keys=False, default=None)
606
+
607
+ The OutMultiEdgeView provides set-like operations on the edge-tuples
608
+ as well as edge attribute lookup. When called, it also provides
609
+ an EdgeDataView object which allows control of access to edge
610
+ attributes (but does not provide set-like operations).
611
+ Hence, ``G.edges[u, v, k]['color']`` provides the value of the color
612
+ attribute for the edge from ``u`` to ``v`` with key ``k`` while
613
+ ``for (u, v, k, c) in G.edges(data='color', default='red', keys=True):``
614
+ iterates through all the edges yielding the color attribute with
615
+ default `'red'` if no color attribute exists.
616
+
617
+ Edges are returned as tuples with optional data and keys
618
+ in the order (node, neighbor, key, data). If ``keys=True`` is not
619
+ provided, the tuples will just be (node, neighbor, data), but
620
+ multiple tuples with the same node and neighbor will be
621
+ generated when multiple edges between two nodes exist.
622
+
623
+ Parameters
624
+ ----------
625
+ nbunch : single node, container, or all nodes (default= all nodes)
626
+ The view will only report edges from these nodes.
627
+ data : string or bool, optional (default=False)
628
+ The edge attribute returned in 3-tuple (u, v, ddict[data]).
629
+ If True, return edge attribute dict in 3-tuple (u, v, ddict).
630
+ If False, return 2-tuple (u, v).
631
+ keys : bool, optional (default=False)
632
+ If True, return edge keys with each edge, creating (u, v, k,
633
+ d) tuples when data is also requested (the default) and (u,
634
+ v, k) tuples when data is not requested.
635
+ default : value, optional (default=None)
636
+ Value used for edges that don't have the requested attribute.
637
+ Only relevant if data is not True or False.
638
+
639
+ Returns
640
+ -------
641
+ edges : OutMultiEdgeView
642
+ A view of edge attributes, usually it iterates over (u, v)
643
+ (u, v, k) or (u, v, k, d) tuples of edges, but can also be
644
+ used for attribute lookup as ``edges[u, v, k]['foo']``.
645
+
646
+ Notes
647
+ -----
648
+ Nodes in nbunch that are not in the graph will be (quietly) ignored.
649
+ For directed graphs this returns the out-edges.
650
+
651
+ Examples
652
+ --------
653
+ >>> G = nx.MultiDiGraph()
654
+ >>> nx.add_path(G, [0, 1, 2])
655
+ >>> key = G.add_edge(2, 3, weight=5)
656
+ >>> key2 = G.add_edge(1, 2) # second edge between these nodes
657
+ >>> [e for e in G.edges()]
658
+ [(0, 1), (1, 2), (1, 2), (2, 3)]
659
+ >>> list(G.edges(data=True)) # default data is {} (empty dict)
660
+ [(0, 1, {}), (1, 2, {}), (1, 2, {}), (2, 3, {'weight': 5})]
661
+ >>> list(G.edges(data="weight", default=1))
662
+ [(0, 1, 1), (1, 2, 1), (1, 2, 1), (2, 3, 5)]
663
+ >>> list(G.edges(keys=True)) # default keys are integers
664
+ [(0, 1, 0), (1, 2, 0), (1, 2, 1), (2, 3, 0)]
665
+ >>> list(G.edges(data=True, keys=True))
666
+ [(0, 1, 0, {}), (1, 2, 0, {}), (1, 2, 1, {}), (2, 3, 0, {'weight': 5})]
667
+ >>> list(G.edges(data="weight", default=1, keys=True))
668
+ [(0, 1, 0, 1), (1, 2, 0, 1), (1, 2, 1, 1), (2, 3, 0, 5)]
669
+ >>> list(G.edges([0, 2]))
670
+ [(0, 1), (2, 3)]
671
+ >>> list(G.edges(0))
672
+ [(0, 1)]
673
+ >>> list(G.edges(1))
674
+ [(1, 2), (1, 2)]
675
+
676
+ See Also
677
+ --------
678
+ in_edges, out_edges
679
+ """
680
+ return OutMultiEdgeView(self)
681
+
682
+ # alias out_edges to edges
683
+ @cached_property
684
+ def out_edges(self):
685
+ return OutMultiEdgeView(self)
686
+
687
+ out_edges.__doc__ = edges.__doc__
688
+
689
+ @cached_property
690
+ def in_edges(self):
691
+ """A view of the in edges of the graph as G.in_edges or G.in_edges().
692
+
693
+ in_edges(self, nbunch=None, data=False, keys=False, default=None)
694
+
695
+ Parameters
696
+ ----------
697
+ nbunch : single node, container, or all nodes (default= all nodes)
698
+ The view will only report edges incident to these nodes.
699
+ data : string or bool, optional (default=False)
700
+ The edge attribute returned in 3-tuple (u, v, ddict[data]).
701
+ If True, return edge attribute dict in 3-tuple (u, v, ddict).
702
+ If False, return 2-tuple (u, v).
703
+ keys : bool, optional (default=False)
704
+ If True, return edge keys with each edge, creating 3-tuples
705
+ (u, v, k) or with data, 4-tuples (u, v, k, d).
706
+ default : value, optional (default=None)
707
+ Value used for edges that don't have the requested attribute.
708
+ Only relevant if data is not True or False.
709
+
710
+ Returns
711
+ -------
712
+ in_edges : InMultiEdgeView or InMultiEdgeDataView
713
+ A view of edge attributes, usually it iterates over (u, v)
714
+ or (u, v, k) or (u, v, k, d) tuples of edges, but can also be
715
+ used for attribute lookup as `edges[u, v, k]['foo']`.
716
+
717
+ See Also
718
+ --------
719
+ edges
720
+ """
721
+ return InMultiEdgeView(self)
722
+
723
+ @cached_property
724
+ def degree(self):
725
+ """A DegreeView for the Graph as G.degree or G.degree().
726
+
727
+ The node degree is the number of edges adjacent to the node.
728
+ The weighted node degree is the sum of the edge weights for
729
+ edges incident to that node.
730
+
731
+ This object provides an iterator for (node, degree) as well as
732
+ lookup for the degree for a single node.
733
+
734
+ Parameters
735
+ ----------
736
+ nbunch : single node, container, or all nodes (default= all nodes)
737
+ The view will only report edges incident to these nodes.
738
+
739
+ weight : string or None, optional (default=None)
740
+ The name of an edge attribute that holds the numerical value used
741
+ as a weight. If None, then each edge has weight 1.
742
+ The degree is the sum of the edge weights adjacent to the node.
743
+
744
+ Returns
745
+ -------
746
+ DiMultiDegreeView or int
747
+ If multiple nodes are requested (the default), returns a `DiMultiDegreeView`
748
+ mapping nodes to their degree.
749
+ If a single node is requested, returns the degree of the node as an integer.
750
+
751
+ See Also
752
+ --------
753
+ out_degree, in_degree
754
+
755
+ Examples
756
+ --------
757
+ >>> G = nx.MultiDiGraph()
758
+ >>> nx.add_path(G, [0, 1, 2, 3])
759
+ >>> G.degree(0) # node 0 with degree 1
760
+ 1
761
+ >>> list(G.degree([0, 1, 2]))
762
+ [(0, 1), (1, 2), (2, 2)]
763
+ >>> G.add_edge(0, 1) # parallel edge
764
+ 1
765
+ >>> list(G.degree([0, 1, 2])) # parallel edges are counted
766
+ [(0, 2), (1, 3), (2, 2)]
767
+
768
+ """
769
+ return DiMultiDegreeView(self)
770
+
771
+ @cached_property
772
+ def in_degree(self):
773
+ """A DegreeView for (node, in_degree) or in_degree for single node.
774
+
775
+ The node in-degree is the number of edges pointing into the node.
776
+ The weighted node degree is the sum of the edge weights for
777
+ edges incident to that node.
778
+
779
+ This object provides an iterator for (node, degree) as well as
780
+ lookup for the degree for a single node.
781
+
782
+ Parameters
783
+ ----------
784
+ nbunch : single node, container, or all nodes (default= all nodes)
785
+ The view will only report edges incident to these nodes.
786
+
787
+ weight : string or None, optional (default=None)
788
+ The edge attribute that holds the numerical value used
789
+ as a weight. If None, then each edge has weight 1.
790
+ The degree is the sum of the edge weights adjacent to the node.
791
+
792
+ Returns
793
+ -------
794
+ If a single node is requested
795
+ deg : int
796
+ Degree of the node
797
+
798
+ OR if multiple nodes are requested
799
+ nd_iter : iterator
800
+ The iterator returns two-tuples of (node, in-degree).
801
+
802
+ See Also
803
+ --------
804
+ degree, out_degree
805
+
806
+ Examples
807
+ --------
808
+ >>> G = nx.MultiDiGraph()
809
+ >>> nx.add_path(G, [0, 1, 2, 3])
810
+ >>> G.in_degree(0) # node 0 with degree 0
811
+ 0
812
+ >>> list(G.in_degree([0, 1, 2]))
813
+ [(0, 0), (1, 1), (2, 1)]
814
+ >>> G.add_edge(0, 1) # parallel edge
815
+ 1
816
+ >>> list(G.in_degree([0, 1, 2])) # parallel edges counted
817
+ [(0, 0), (1, 2), (2, 1)]
818
+
819
+ """
820
+ return InMultiDegreeView(self)
821
+
822
+ @cached_property
823
+ def out_degree(self):
824
+ """Returns an iterator for (node, out-degree) or out-degree for single node.
825
+
826
+ out_degree(self, nbunch=None, weight=None)
827
+
828
+ The node out-degree is the number of edges pointing out of the node.
829
+ This function returns the out-degree for a single node or an iterator
830
+ for a bunch of nodes or if nothing is passed as argument.
831
+
832
+ Parameters
833
+ ----------
834
+ nbunch : single node, container, or all nodes (default= all nodes)
835
+ The view will only report edges incident to these nodes.
836
+
837
+ weight : string or None, optional (default=None)
838
+ The edge attribute that holds the numerical value used
839
+ as a weight. If None, then each edge has weight 1.
840
+ The degree is the sum of the edge weights.
841
+
842
+ Returns
843
+ -------
844
+ If a single node is requested
845
+ deg : int
846
+ Degree of the node
847
+
848
+ OR if multiple nodes are requested
849
+ nd_iter : iterator
850
+ The iterator returns two-tuples of (node, out-degree).
851
+
852
+ See Also
853
+ --------
854
+ degree, in_degree
855
+
856
+ Examples
857
+ --------
858
+ >>> G = nx.MultiDiGraph()
859
+ >>> nx.add_path(G, [0, 1, 2, 3])
860
+ >>> G.out_degree(0) # node 0 with degree 1
861
+ 1
862
+ >>> list(G.out_degree([0, 1, 2]))
863
+ [(0, 1), (1, 1), (2, 1)]
864
+ >>> G.add_edge(0, 1) # parallel edge
865
+ 1
866
+ >>> list(G.out_degree([0, 1, 2])) # counts parallel edges
867
+ [(0, 2), (1, 1), (2, 1)]
868
+
869
+ """
870
+ return OutMultiDegreeView(self)
871
+
872
+ def is_multigraph(self):
873
+ """Returns True if graph is a multigraph, False otherwise."""
874
+ return True
875
+
876
+ def is_directed(self):
877
+ """Returns True if graph is directed, False otherwise."""
878
+ return True
879
+
880
+ def to_undirected(self, reciprocal=False, as_view=False):
881
+ """Returns an undirected representation of the digraph.
882
+
883
+ Parameters
884
+ ----------
885
+ reciprocal : bool (optional)
886
+ If True only keep edges that appear in both directions
887
+ in the original digraph.
888
+ as_view : bool (optional, default=False)
889
+ If True return an undirected view of the original directed graph.
890
+
891
+ Returns
892
+ -------
893
+ G : MultiGraph
894
+ An undirected graph with the same name and nodes and
895
+ with edge (u, v, data) if either (u, v, data) or (v, u, data)
896
+ is in the digraph. If both edges exist in digraph and
897
+ their edge data is different, only one edge is created
898
+ with an arbitrary choice of which edge data to use.
899
+ You must check and correct for this manually if desired.
900
+
901
+ See Also
902
+ --------
903
+ MultiGraph, copy, add_edge, add_edges_from
904
+
905
+ Notes
906
+ -----
907
+ This returns a "deepcopy" of the edge, node, and
908
+ graph attributes which attempts to completely copy
909
+ all of the data and references.
910
+
911
+ This is in contrast to the similar D=MultiDiGraph(G) which
912
+ returns a shallow copy of the data.
913
+
914
+ See the Python copy module for more information on shallow
915
+ and deep copies, https://docs.python.org/3/library/copy.html.
916
+
917
+ Warning: If you have subclassed MultiDiGraph to use dict-like
918
+ objects in the data structure, those changes do not transfer
919
+ to the MultiGraph created by this method.
920
+
921
+ Examples
922
+ --------
923
+ >>> G = nx.path_graph(2) # or MultiGraph, etc
924
+ >>> H = G.to_directed()
925
+ >>> list(H.edges)
926
+ [(0, 1), (1, 0)]
927
+ >>> G2 = H.to_undirected()
928
+ >>> list(G2.edges)
929
+ [(0, 1)]
930
+ """
931
+ graph_class = self.to_undirected_class()
932
+ if as_view is True:
933
+ return nx.graphviews.generic_graph_view(self, graph_class)
934
+ # deepcopy when not a view
935
+ G = graph_class()
936
+ G.graph.update(deepcopy(self.graph))
937
+ G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
938
+ if reciprocal is True:
939
+ G.add_edges_from(
940
+ (u, v, key, deepcopy(data))
941
+ for u, nbrs in self._adj.items()
942
+ for v, keydict in nbrs.items()
943
+ for key, data in keydict.items()
944
+ if v in self._pred[u] and key in self._pred[u][v]
945
+ )
946
+ else:
947
+ G.add_edges_from(
948
+ (u, v, key, deepcopy(data))
949
+ for u, nbrs in self._adj.items()
950
+ for v, keydict in nbrs.items()
951
+ for key, data in keydict.items()
952
+ )
953
+ return G
954
+
955
+ def reverse(self, copy=True):
956
+ """Returns the reverse of the graph.
957
+
958
+ The reverse is a graph with the same nodes and edges
959
+ but with the directions of the edges reversed.
960
+
961
+ Parameters
962
+ ----------
963
+ copy : bool optional (default=True)
964
+ If True, return a new DiGraph holding the reversed edges.
965
+ If False, the reverse graph is created using a view of
966
+ the original graph.
967
+ """
968
+ if copy:
969
+ H = self.__class__()
970
+ H.graph.update(deepcopy(self.graph))
971
+ H.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
972
+ H.add_edges_from(
973
+ (v, u, k, deepcopy(d))
974
+ for u, v, k, d in self.edges(keys=True, data=True)
975
+ )
976
+ return H
977
+ return nx.reverse_view(self)
lib/python3.12/site-packages/networkx/classes/multigraph.py ADDED
@@ -0,0 +1,1294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Base class for MultiGraph."""
2
+
3
+ from copy import deepcopy
4
+ from functools import cached_property
5
+
6
+ import networkx as nx
7
+ from networkx import NetworkXError, convert
8
+ from networkx.classes.coreviews import MultiAdjacencyView
9
+ from networkx.classes.graph import Graph
10
+ from networkx.classes.reportviews import MultiDegreeView, MultiEdgeView
11
+
12
+ __all__ = ["MultiGraph"]
13
+
14
+
15
+ class MultiGraph(Graph):
16
+ """
17
+ An undirected graph class that can store multiedges.
18
+
19
+ Multiedges are multiple edges between two nodes. Each edge
20
+ can hold optional data or attributes.
21
+
22
+ A MultiGraph holds undirected edges. Self loops are allowed.
23
+
24
+ Nodes can be arbitrary (hashable) Python objects with optional
25
+ key/value attributes. By convention `None` is not used as a node.
26
+
27
+ Edges are represented as links between nodes with optional
28
+ key/value attributes, in a MultiGraph each edge has a key to
29
+ distinguish between multiple edges that have the same source and
30
+ destination nodes.
31
+
32
+ Parameters
33
+ ----------
34
+ incoming_graph_data : input graph (optional, default: None)
35
+ Data to initialize graph. If None (default) an empty
36
+ graph is created. The data can be any format that is supported
37
+ by the to_networkx_graph() function, currently including edge list,
38
+ dict of dicts, dict of lists, NetworkX graph, 2D NumPy array,
39
+ SciPy sparse array, or PyGraphviz graph.
40
+
41
+ multigraph_input : bool or None (default None)
42
+ Note: Only used when `incoming_graph_data` is a dict.
43
+ If True, `incoming_graph_data` is assumed to be a
44
+ dict-of-dict-of-dict-of-dict structure keyed by
45
+ node to neighbor to edge keys to edge data for multi-edges.
46
+ A NetworkXError is raised if this is not the case.
47
+ If False, :func:`to_networkx_graph` is used to try to determine
48
+ the dict's graph data structure as either a dict-of-dict-of-dict
49
+ keyed by node to neighbor to edge data, or a dict-of-iterable
50
+ keyed by node to neighbors.
51
+ If None, the treatment for True is tried, but if it fails,
52
+ the treatment for False is tried.
53
+
54
+ attr : keyword arguments, optional (default= no attributes)
55
+ Attributes to add to graph as key=value pairs.
56
+
57
+ See Also
58
+ --------
59
+ Graph
60
+ DiGraph
61
+ MultiDiGraph
62
+
63
+ Examples
64
+ --------
65
+ Create an empty graph structure (a "null graph") with no nodes and
66
+ no edges.
67
+
68
+ >>> G = nx.MultiGraph()
69
+
70
+ G can be grown in several ways.
71
+
72
+ **Nodes:**
73
+
74
+ Add one node at a time:
75
+
76
+ >>> G.add_node(1)
77
+
78
+ Add the nodes from any container (a list, dict, set or
79
+ even the lines from a file or the nodes from another graph).
80
+
81
+ >>> G.add_nodes_from([2, 3])
82
+ >>> G.add_nodes_from(range(100, 110))
83
+ >>> H = nx.path_graph(10)
84
+ >>> G.add_nodes_from(H)
85
+
86
+ In addition to strings and integers any hashable Python object
87
+ (except None) can represent a node, e.g. a customized node object,
88
+ or even another Graph.
89
+
90
+ >>> G.add_node(H)
91
+
92
+ **Edges:**
93
+
94
+ G can also be grown by adding edges.
95
+
96
+ Add one edge,
97
+
98
+ >>> key = G.add_edge(1, 2)
99
+
100
+ a list of edges,
101
+
102
+ >>> keys = G.add_edges_from([(1, 2), (1, 3)])
103
+
104
+ or a collection of edges,
105
+
106
+ >>> keys = G.add_edges_from(H.edges)
107
+
108
+ If some edges connect nodes not yet in the graph, the nodes
109
+ are added automatically. If an edge already exists, an additional
110
+ edge is created and stored using a key to identify the edge.
111
+ By default the key is the lowest unused integer.
112
+
113
+ >>> keys = G.add_edges_from([(4, 5, {"route": 28}), (4, 5, {"route": 37})])
114
+ >>> G[4]
115
+ AdjacencyView({3: {0: {}}, 5: {0: {}, 1: {'route': 28}, 2: {'route': 37}}})
116
+
117
+ **Attributes:**
118
+
119
+ Each graph, node, and edge can hold key/value attribute pairs
120
+ in an associated attribute dictionary (the keys must be hashable).
121
+ By default these are empty, but can be added or changed using
122
+ add_edge, add_node or direct manipulation of the attribute
123
+ dictionaries named graph, node and edge respectively.
124
+
125
+ >>> G = nx.MultiGraph(day="Friday")
126
+ >>> G.graph
127
+ {'day': 'Friday'}
128
+
129
+ Add node attributes using add_node(), add_nodes_from() or G.nodes
130
+
131
+ >>> G.add_node(1, time="5pm")
132
+ >>> G.add_nodes_from([3], time="2pm")
133
+ >>> G.nodes[1]
134
+ {'time': '5pm'}
135
+ >>> G.nodes[1]["room"] = 714
136
+ >>> del G.nodes[1]["room"] # remove attribute
137
+ >>> list(G.nodes(data=True))
138
+ [(1, {'time': '5pm'}), (3, {'time': '2pm'})]
139
+
140
+ Add edge attributes using add_edge(), add_edges_from(), subscript
141
+ notation, or G.edges.
142
+
143
+ >>> key = G.add_edge(1, 2, weight=4.7)
144
+ >>> keys = G.add_edges_from([(3, 4), (4, 5)], color="red")
145
+ >>> keys = G.add_edges_from([(1, 2, {"color": "blue"}), (2, 3, {"weight": 8})])
146
+ >>> G[1][2][0]["weight"] = 4.7
147
+ >>> G.edges[1, 2, 0]["weight"] = 4
148
+
149
+ Warning: we protect the graph data structure by making `G.edges[1,
150
+ 2, 0]` a read-only dict-like structure. However, you can assign to
151
+ attributes in e.g. `G.edges[1, 2, 0]`. Thus, use 2 sets of brackets
152
+ to add/change data attributes: `G.edges[1, 2, 0]['weight'] = 4`.
153
+
154
+ **Shortcuts:**
155
+
156
+ Many common graph features allow python syntax to speed reporting.
157
+
158
+ >>> 1 in G # check if node in graph
159
+ True
160
+ >>> [n for n in G if n < 3] # iterate through nodes
161
+ [1, 2]
162
+ >>> len(G) # number of nodes in graph
163
+ 5
164
+ >>> G[1] # adjacency dict-like view mapping neighbor -> edge key -> edge attributes
165
+ AdjacencyView({2: {0: {'weight': 4}, 1: {'color': 'blue'}}})
166
+
167
+ Often the best way to traverse all edges of a graph is via the neighbors.
168
+ The neighbors are reported as an adjacency-dict `G.adj` or `G.adjacency()`.
169
+
170
+ >>> for n, nbrsdict in G.adjacency():
171
+ ... for nbr, keydict in nbrsdict.items():
172
+ ... for key, eattr in keydict.items():
173
+ ... if "weight" in eattr:
174
+ ... # Do something useful with the edges
175
+ ... pass
176
+
177
+ But the edges() method is often more convenient:
178
+
179
+ >>> for u, v, keys, weight in G.edges(data="weight", keys=True):
180
+ ... if weight is not None:
181
+ ... # Do something useful with the edges
182
+ ... pass
183
+
184
+ **Reporting:**
185
+
186
+ Simple graph information is obtained using methods and object-attributes.
187
+ Reporting usually provides views instead of containers to reduce memory
188
+ usage. The views update as the graph is updated similarly to dict-views.
189
+ The objects `nodes`, `edges` and `adj` provide access to data attributes
190
+ via lookup (e.g. `nodes[n]`, `edges[u, v, k]`, `adj[u][v]`) and iteration
191
+ (e.g. `nodes.items()`, `nodes.data('color')`,
192
+ `nodes.data('color', default='blue')` and similarly for `edges`)
193
+ Views exist for `nodes`, `edges`, `neighbors()`/`adj` and `degree`.
194
+
195
+ For details on these and other miscellaneous methods, see below.
196
+
197
+ **Subclasses (Advanced):**
198
+
199
+ The MultiGraph class uses a dict-of-dict-of-dict-of-dict data structure.
200
+ The outer dict (node_dict) holds adjacency information keyed by node.
201
+ The next dict (adjlist_dict) represents the adjacency information
202
+ and holds edge_key dicts keyed by neighbor. The edge_key dict holds
203
+ each edge_attr dict keyed by edge key. The inner dict
204
+ (edge_attr_dict) represents the edge data and holds edge attribute
205
+ values keyed by attribute names.
206
+
207
+ Each of these four dicts in the dict-of-dict-of-dict-of-dict
208
+ structure can be replaced by a user defined dict-like object.
209
+ In general, the dict-like features should be maintained but
210
+ extra features can be added. To replace one of the dicts create
211
+ a new graph class by changing the class(!) variable holding the
212
+ factory for that dict-like structure. The variable names are
213
+ node_dict_factory, node_attr_dict_factory, adjlist_inner_dict_factory,
214
+ adjlist_outer_dict_factory, edge_key_dict_factory, edge_attr_dict_factory
215
+ and graph_attr_dict_factory.
216
+
217
+ node_dict_factory : function, (default: dict)
218
+ Factory function to be used to create the dict containing node
219
+ attributes, keyed by node id.
220
+ It should require no arguments and return a dict-like object
221
+
222
+ node_attr_dict_factory: function, (default: dict)
223
+ Factory function to be used to create the node attribute
224
+ dict which holds attribute values keyed by attribute name.
225
+ It should require no arguments and return a dict-like object
226
+
227
+ adjlist_outer_dict_factory : function, (default: dict)
228
+ Factory function to be used to create the outer-most dict
229
+ in the data structure that holds adjacency info keyed by node.
230
+ It should require no arguments and return a dict-like object.
231
+
232
+ adjlist_inner_dict_factory : function, (default: dict)
233
+ Factory function to be used to create the adjacency list
234
+ dict which holds multiedge key dicts keyed by neighbor.
235
+ It should require no arguments and return a dict-like object.
236
+
237
+ edge_key_dict_factory : function, (default: dict)
238
+ Factory function to be used to create the edge key dict
239
+ which holds edge data keyed by edge key.
240
+ It should require no arguments and return a dict-like object.
241
+
242
+ edge_attr_dict_factory : function, (default: dict)
243
+ Factory function to be used to create the edge attribute
244
+ dict which holds attribute values keyed by attribute name.
245
+ It should require no arguments and return a dict-like object.
246
+
247
+ graph_attr_dict_factory : function, (default: dict)
248
+ Factory function to be used to create the graph attribute
249
+ dict which holds attribute values keyed by attribute name.
250
+ It should require no arguments and return a dict-like object.
251
+
252
+ Typically, if your extension doesn't impact the data structure all
253
+ methods will inherited without issue except: `to_directed/to_undirected`.
254
+ By default these methods create a DiGraph/Graph class and you probably
255
+ want them to create your extension of a DiGraph/Graph. To facilitate
256
+ this we define two class variables that you can set in your subclass.
257
+
258
+ to_directed_class : callable, (default: DiGraph or MultiDiGraph)
259
+ Class to create a new graph structure in the `to_directed` method.
260
+ If `None`, a NetworkX class (DiGraph or MultiDiGraph) is used.
261
+
262
+ to_undirected_class : callable, (default: Graph or MultiGraph)
263
+ Class to create a new graph structure in the `to_undirected` method.
264
+ If `None`, a NetworkX class (Graph or MultiGraph) is used.
265
+
266
+ **Subclassing Example**
267
+
268
+ Create a low memory graph class that effectively disallows edge
269
+ attributes by using a single attribute dict for all edges.
270
+ This reduces the memory used, but you lose edge attributes.
271
+
272
+ >>> class ThinGraph(nx.Graph):
273
+ ... all_edge_dict = {"weight": 1}
274
+ ...
275
+ ... def single_edge_dict(self):
276
+ ... return self.all_edge_dict
277
+ ...
278
+ ... edge_attr_dict_factory = single_edge_dict
279
+ >>> G = ThinGraph()
280
+ >>> G.add_edge(2, 1)
281
+ >>> G[2][1]
282
+ {'weight': 1}
283
+ >>> G.add_edge(2, 2)
284
+ >>> G[2][1] is G[2][2]
285
+ True
286
+ """
287
+
288
+ # node_dict_factory = dict # already assigned in Graph
289
+ # adjlist_outer_dict_factory = dict
290
+ # adjlist_inner_dict_factory = dict
291
+ edge_key_dict_factory = dict
292
+ # edge_attr_dict_factory = dict
293
+
294
+ def to_directed_class(self):
295
+ """Returns the class to use for empty directed copies.
296
+
297
+ If you subclass the base classes, use this to designate
298
+ what directed class to use for `to_directed()` copies.
299
+ """
300
+ return nx.MultiDiGraph
301
+
302
+ def to_undirected_class(self):
303
+ """Returns the class to use for empty undirected copies.
304
+
305
+ If you subclass the base classes, use this to designate
306
+ what directed class to use for `to_directed()` copies.
307
+ """
308
+ return MultiGraph
309
+
310
+ # This __new__ method just does what Python itself does automatically.
311
+ # We include it here as part of the dispatchable/backend interface.
312
+ # If your goal is to understand how the graph classes work, you can ignore
313
+ # this method, even when subclassing the base classes. If you are subclassing
314
+ # in order to provide a backend that allows class instantiation, this method
315
+ # can be overridden to return your own backend graph class.
316
+ @nx._dispatchable(name="multigraph__new__", graphs=None, returns_graph=True)
317
+ def __new__(cls, *args, **kwargs):
318
+ return object.__new__(cls)
319
+
320
+ def __init__(self, incoming_graph_data=None, multigraph_input=None, **attr):
321
+ """Initialize a graph with edges, name, or graph attributes.
322
+
323
+ Parameters
324
+ ----------
325
+ incoming_graph_data : input graph
326
+ Data to initialize graph. If incoming_graph_data=None (default)
327
+ an empty graph is created. The data can be an edge list, or any
328
+ NetworkX graph object. If the corresponding optional Python
329
+ packages are installed the data can also be a 2D NumPy array, a
330
+ SciPy sparse array, or a PyGraphviz graph.
331
+
332
+ multigraph_input : bool or None (default None)
333
+ Note: Only used when `incoming_graph_data` is a dict.
334
+ If True, `incoming_graph_data` is assumed to be a
335
+ dict-of-dict-of-dict-of-dict structure keyed by
336
+ node to neighbor to edge keys to edge data for multi-edges.
337
+ A NetworkXError is raised if this is not the case.
338
+ If False, :func:`to_networkx_graph` is used to try to determine
339
+ the dict's graph data structure as either a dict-of-dict-of-dict
340
+ keyed by node to neighbor to edge data, or a dict-of-iterable
341
+ keyed by node to neighbors.
342
+ If None, the treatment for True is tried, but if it fails,
343
+ the treatment for False is tried.
344
+
345
+ attr : keyword arguments, optional (default= no attributes)
346
+ Attributes to add to graph as key=value pairs.
347
+
348
+ See Also
349
+ --------
350
+ convert
351
+
352
+ Examples
353
+ --------
354
+ >>> G = nx.MultiGraph()
355
+ >>> G = nx.MultiGraph(name="my graph")
356
+ >>> e = [(1, 2), (1, 2), (2, 3), (3, 4)] # list of edges
357
+ >>> G = nx.MultiGraph(e)
358
+
359
+ Arbitrary graph attribute pairs (key=value) may be assigned
360
+
361
+ >>> G = nx.MultiGraph(e, day="Friday")
362
+ >>> G.graph
363
+ {'day': 'Friday'}
364
+
365
+ """
366
+ attr.pop("backend", None) # Ignore explicit `backend="networkx"`
367
+ # multigraph_input can be None/True/False. So check "is not False"
368
+ if isinstance(incoming_graph_data, dict) and multigraph_input is not False:
369
+ Graph.__init__(self)
370
+ try:
371
+ convert.from_dict_of_dicts(
372
+ incoming_graph_data, create_using=self, multigraph_input=True
373
+ )
374
+ self.graph.update(attr)
375
+ except Exception as err:
376
+ if multigraph_input is True:
377
+ raise nx.NetworkXError(
378
+ f"converting multigraph_input raised:\n{type(err)}: {err}"
379
+ )
380
+ Graph.__init__(self, incoming_graph_data, **attr)
381
+ else:
382
+ Graph.__init__(self, incoming_graph_data, **attr)
383
+
384
+ @cached_property
385
+ def adj(self):
386
+ """Graph adjacency object holding the neighbors of each node.
387
+
388
+ This object is a read-only dict-like structure with node keys
389
+ and neighbor-dict values. The neighbor-dict is keyed by neighbor
390
+ to the edgekey-data-dict. So `G.adj[3][2][0]['color'] = 'blue'` sets
391
+ the color of the edge `(3, 2, 0)` to `"blue"`.
392
+
393
+ Iterating over G.adj behaves like a dict. Useful idioms include
394
+ `for nbr, edgesdict in G.adj[n].items():`.
395
+
396
+ The neighbor information is also provided by subscripting the graph.
397
+
398
+ Examples
399
+ --------
400
+ >>> e = [(1, 2), (1, 2), (1, 3), (3, 4)] # list of edges
401
+ >>> G = nx.MultiGraph(e)
402
+ >>> G.edges[1, 2, 0]["weight"] = 3
403
+ >>> result = set()
404
+ >>> for edgekey, data in G[1][2].items():
405
+ ... result.add(data.get("weight", 1))
406
+ >>> result
407
+ {1, 3}
408
+
409
+ For directed graphs, `G.adj` holds outgoing (successor) info.
410
+ """
411
+ return MultiAdjacencyView(self._adj)
412
+
413
+ def new_edge_key(self, u, v):
414
+ """Returns an unused key for edges between nodes `u` and `v`.
415
+
416
+ The nodes `u` and `v` do not need to be already in the graph.
417
+
418
+ Notes
419
+ -----
420
+ In the standard MultiGraph class the new key is the number of existing
421
+ edges between `u` and `v` (increased if necessary to ensure unused).
422
+ The first edge will have key 0, then 1, etc. If an edge is removed
423
+ further new_edge_keys may not be in this order.
424
+
425
+ Parameters
426
+ ----------
427
+ u, v : nodes
428
+
429
+ Returns
430
+ -------
431
+ key : int
432
+ """
433
+ try:
434
+ keydict = self._adj[u][v]
435
+ except KeyError:
436
+ return 0
437
+ key = len(keydict)
438
+ while key in keydict:
439
+ key += 1
440
+ return key
441
+
442
+ def add_edge(self, u_for_edge, v_for_edge, key=None, **attr):
443
+ """Add an edge between u and v.
444
+
445
+ The nodes u and v will be automatically added if they are
446
+ not already in the graph.
447
+
448
+ Edge attributes can be specified with keywords or by directly
449
+ accessing the edge's attribute dictionary. See examples below.
450
+
451
+ Parameters
452
+ ----------
453
+ u_for_edge, v_for_edge : nodes
454
+ Nodes can be, for example, strings or numbers.
455
+ Nodes must be hashable (and not None) Python objects.
456
+ key : hashable identifier, optional (default=lowest unused integer)
457
+ Used to distinguish multiedges between a pair of nodes.
458
+ attr : keyword arguments, optional
459
+ Edge data (or labels or objects) can be assigned using
460
+ keyword arguments.
461
+
462
+ Returns
463
+ -------
464
+ The edge key assigned to the edge.
465
+
466
+ See Also
467
+ --------
468
+ add_edges_from : add a collection of edges
469
+
470
+ Notes
471
+ -----
472
+ To replace/update edge data, use the optional key argument
473
+ to identify a unique edge. Otherwise a new edge will be created.
474
+
475
+ NetworkX algorithms designed for weighted graphs cannot use
476
+ multigraphs directly because it is not clear how to handle
477
+ multiedge weights. Convert to Graph using edge attribute
478
+ 'weight' to enable weighted graph algorithms.
479
+
480
+ Default keys are generated using the method `new_edge_key()`.
481
+ This method can be overridden by subclassing the base class and
482
+ providing a custom `new_edge_key()` method.
483
+
484
+ Examples
485
+ --------
486
+ The following each add an additional edge e=(1, 2) to graph G:
487
+
488
+ >>> G = nx.MultiGraph()
489
+ >>> e = (1, 2)
490
+ >>> ekey = G.add_edge(1, 2) # explicit two-node form
491
+ >>> G.add_edge(*e) # single edge as tuple of two nodes
492
+ 1
493
+ >>> G.add_edges_from([(1, 2)]) # add edges from iterable container
494
+ [2]
495
+
496
+ Associate data to edges using keywords:
497
+
498
+ >>> ekey = G.add_edge(1, 2, weight=3)
499
+ >>> ekey = G.add_edge(1, 2, key=0, weight=4) # update data for key=0
500
+ >>> ekey = G.add_edge(1, 3, weight=7, capacity=15, length=342.7)
501
+
502
+ For non-string attribute keys, use subscript notation.
503
+
504
+ >>> ekey = G.add_edge(1, 2)
505
+ >>> G[1][2][0].update({0: 5})
506
+ >>> G.edges[1, 2, 0].update({0: 5})
507
+ """
508
+ u, v = u_for_edge, v_for_edge
509
+ # add nodes
510
+ if u not in self._adj:
511
+ if u is None:
512
+ raise ValueError("None cannot be a node")
513
+ self._adj[u] = self.adjlist_inner_dict_factory()
514
+ self._node[u] = self.node_attr_dict_factory()
515
+ if v not in self._adj:
516
+ if v is None:
517
+ raise ValueError("None cannot be a node")
518
+ self._adj[v] = self.adjlist_inner_dict_factory()
519
+ self._node[v] = self.node_attr_dict_factory()
520
+ if key is None:
521
+ key = self.new_edge_key(u, v)
522
+ if v in self._adj[u]:
523
+ keydict = self._adj[u][v]
524
+ datadict = keydict.get(key, self.edge_attr_dict_factory())
525
+ datadict.update(attr)
526
+ keydict[key] = datadict
527
+ else:
528
+ # selfloops work this way without special treatment
529
+ datadict = self.edge_attr_dict_factory()
530
+ datadict.update(attr)
531
+ keydict = self.edge_key_dict_factory()
532
+ keydict[key] = datadict
533
+ self._adj[u][v] = keydict
534
+ self._adj[v][u] = keydict
535
+ nx._clear_cache(self)
536
+ return key
537
+
538
+ def add_edges_from(self, ebunch_to_add, **attr):
539
+ """Add all the edges in ebunch_to_add.
540
+
541
+ Parameters
542
+ ----------
543
+ ebunch_to_add : container of edges
544
+ Each edge given in the container will be added to the
545
+ graph. The edges can be:
546
+
547
+ - 2-tuples (u, v) or
548
+ - 3-tuples (u, v, d) for an edge data dict d, or
549
+ - 3-tuples (u, v, k) for not iterable key k, or
550
+ - 4-tuples (u, v, k, d) for an edge with data and key k
551
+
552
+ attr : keyword arguments, optional
553
+ Edge data (or labels or objects) can be assigned using
554
+ keyword arguments.
555
+
556
+ Returns
557
+ -------
558
+ A list of edge keys assigned to the edges in `ebunch`.
559
+
560
+ See Also
561
+ --------
562
+ add_edge : add a single edge
563
+ add_weighted_edges_from : convenient way to add weighted edges
564
+
565
+ Notes
566
+ -----
567
+ Adding the same edge twice has no effect but any edge data
568
+ will be updated when each duplicate edge is added.
569
+
570
+ Edge attributes specified in an ebunch take precedence over
571
+ attributes specified via keyword arguments.
572
+
573
+ Default keys are generated using the method ``new_edge_key()``.
574
+ This method can be overridden by subclassing the base class and
575
+ providing a custom ``new_edge_key()`` method.
576
+
577
+ When adding edges from an iterator over the graph you are changing,
578
+ a `RuntimeError` can be raised with message:
579
+ `RuntimeError: dictionary changed size during iteration`. This
580
+ happens when the graph's underlying dictionary is modified during
581
+ iteration. To avoid this error, evaluate the iterator into a separate
582
+ object, e.g. by using `list(iterator_of_edges)`, and pass this
583
+ object to `G.add_edges_from`.
584
+
585
+ Examples
586
+ --------
587
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
588
+ >>> G.add_edges_from([(0, 1), (1, 2)]) # using a list of edge tuples
589
+ >>> e = zip(range(0, 3), range(1, 4))
590
+ >>> G.add_edges_from(e) # Add the path graph 0-1-2-3
591
+
592
+ Associate data to edges
593
+
594
+ >>> G.add_edges_from([(1, 2), (2, 3)], weight=3)
595
+ >>> G.add_edges_from([(3, 4), (1, 4)], label="WN2898")
596
+
597
+ Evaluate an iterator over a graph if using it to modify the same graph
598
+
599
+ >>> G = nx.MultiGraph([(1, 2), (2, 3), (3, 4)])
600
+ >>> # Grow graph by one new node, adding edges to all existing nodes.
601
+ >>> # wrong way - will raise RuntimeError
602
+ >>> # G.add_edges_from(((5, n) for n in G.nodes))
603
+ >>> # right way - note that there will be no self-edge for node 5
604
+ >>> assigned_keys = G.add_edges_from(list((5, n) for n in G.nodes))
605
+ """
606
+ keylist = []
607
+ for e in ebunch_to_add:
608
+ ne = len(e)
609
+ if ne == 4:
610
+ u, v, key, dd = e
611
+ elif ne == 3:
612
+ u, v, dd = e
613
+ key = None
614
+ elif ne == 2:
615
+ u, v = e
616
+ dd = {}
617
+ key = None
618
+ else:
619
+ msg = f"Edge tuple {e} must be a 2-tuple, 3-tuple or 4-tuple."
620
+ raise NetworkXError(msg)
621
+ ddd = {}
622
+ ddd.update(attr)
623
+ try:
624
+ ddd.update(dd)
625
+ except (TypeError, ValueError):
626
+ if ne != 3:
627
+ raise
628
+ key = dd # ne == 3 with 3rd value not dict, must be a key
629
+ key = self.add_edge(u, v, key)
630
+ self[u][v][key].update(ddd)
631
+ keylist.append(key)
632
+ nx._clear_cache(self)
633
+ return keylist
634
+
635
+ def remove_edge(self, u, v, key=None):
636
+ """Remove an edge between u and v.
637
+
638
+ Parameters
639
+ ----------
640
+ u, v : nodes
641
+ Remove an edge between nodes u and v.
642
+ key : hashable identifier, optional (default=None)
643
+ Used to distinguish multiple edges between a pair of nodes.
644
+ If None, remove a single edge between u and v. If there are
645
+ multiple edges, removes the last edge added in terms of
646
+ insertion order.
647
+
648
+ Raises
649
+ ------
650
+ NetworkXError
651
+ If there is not an edge between u and v, or
652
+ if there is no edge with the specified key.
653
+
654
+ See Also
655
+ --------
656
+ remove_edges_from : remove a collection of edges
657
+
658
+ Examples
659
+ --------
660
+ >>> G = nx.MultiGraph()
661
+ >>> nx.add_path(G, [0, 1, 2, 3])
662
+ >>> G.remove_edge(0, 1)
663
+ >>> e = (1, 2)
664
+ >>> G.remove_edge(*e) # unpacks e from an edge tuple
665
+
666
+ For multiple edges
667
+
668
+ >>> G = nx.MultiGraph() # or MultiDiGraph, etc
669
+ >>> G.add_edges_from([(1, 2), (1, 2), (1, 2)]) # key_list returned
670
+ [0, 1, 2]
671
+
672
+ When ``key=None`` (the default), edges are removed in the opposite
673
+ order that they were added:
674
+
675
+ >>> G.remove_edge(1, 2)
676
+ >>> G.edges(keys=True)
677
+ MultiEdgeView([(1, 2, 0), (1, 2, 1)])
678
+ >>> G.remove_edge(2, 1) # edges are not directed
679
+ >>> G.edges(keys=True)
680
+ MultiEdgeView([(1, 2, 0)])
681
+
682
+ For edges with keys
683
+
684
+ >>> G = nx.MultiGraph()
685
+ >>> G.add_edge(1, 2, key="first")
686
+ 'first'
687
+ >>> G.add_edge(1, 2, key="second")
688
+ 'second'
689
+ >>> G.remove_edge(1, 2, key="first")
690
+ >>> G.edges(keys=True)
691
+ MultiEdgeView([(1, 2, 'second')])
692
+
693
+ """
694
+ try:
695
+ d = self._adj[u][v]
696
+ except KeyError as err:
697
+ raise NetworkXError(f"The edge {u}-{v} is not in the graph.") from err
698
+ # remove the edge with specified data
699
+ if key is None:
700
+ d.popitem()
701
+ else:
702
+ try:
703
+ del d[key]
704
+ except KeyError as err:
705
+ msg = f"The edge {u}-{v} with key {key} is not in the graph."
706
+ raise NetworkXError(msg) from err
707
+ if len(d) == 0:
708
+ # remove the key entries if last edge
709
+ del self._adj[u][v]
710
+ if u != v: # check for selfloop
711
+ del self._adj[v][u]
712
+ nx._clear_cache(self)
713
+
714
+ def remove_edges_from(self, ebunch):
715
+ """Remove all edges specified in ebunch.
716
+
717
+ Parameters
718
+ ----------
719
+ ebunch: list or container of edge tuples
720
+ Each edge given in the list or container will be removed
721
+ from the graph. The edges can be:
722
+
723
+ - 2-tuples (u, v) A single edge between u and v is removed.
724
+ - 3-tuples (u, v, key) The edge identified by key is removed.
725
+ - 4-tuples (u, v, key, data) where data is ignored.
726
+
727
+ See Also
728
+ --------
729
+ remove_edge : remove a single edge
730
+
731
+ Notes
732
+ -----
733
+ Will fail silently if an edge in ebunch is not in the graph.
734
+
735
+ Examples
736
+ --------
737
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
738
+ >>> ebunch = [(1, 2), (2, 3)]
739
+ >>> G.remove_edges_from(ebunch)
740
+
741
+ Removing multiple copies of edges
742
+
743
+ >>> G = nx.MultiGraph()
744
+ >>> keys = G.add_edges_from([(1, 2), (1, 2), (1, 2)])
745
+ >>> G.remove_edges_from([(1, 2), (2, 1)]) # edges aren't directed
746
+ >>> list(G.edges())
747
+ [(1, 2)]
748
+ >>> G.remove_edges_from([(1, 2), (1, 2)]) # silently ignore extra copy
749
+ >>> list(G.edges) # now empty graph
750
+ []
751
+
752
+ When the edge is a 2-tuple ``(u, v)`` but there are multiple edges between
753
+ u and v in the graph, the most recent edge (in terms of insertion
754
+ order) is removed.
755
+
756
+ >>> G = nx.MultiGraph()
757
+ >>> for key in ("x", "y", "a"):
758
+ ... k = G.add_edge(0, 1, key=key)
759
+ >>> G.edges(keys=True)
760
+ MultiEdgeView([(0, 1, 'x'), (0, 1, 'y'), (0, 1, 'a')])
761
+ >>> G.remove_edges_from([(0, 1)])
762
+ >>> G.edges(keys=True)
763
+ MultiEdgeView([(0, 1, 'x'), (0, 1, 'y')])
764
+
765
+ """
766
+ for e in ebunch:
767
+ try:
768
+ self.remove_edge(*e[:3])
769
+ except NetworkXError:
770
+ pass
771
+ nx._clear_cache(self)
772
+
773
+ def has_edge(self, u, v, key=None):
774
+ """Returns True if the graph has an edge between nodes u and v.
775
+
776
+ This is the same as `v in G[u] or key in G[u][v]`
777
+ without KeyError exceptions.
778
+
779
+ Parameters
780
+ ----------
781
+ u, v : nodes
782
+ Nodes can be, for example, strings or numbers.
783
+
784
+ key : hashable identifier, optional (default=None)
785
+ If specified return True only if the edge with
786
+ key is found.
787
+
788
+ Returns
789
+ -------
790
+ edge_ind : bool
791
+ True if edge is in the graph, False otherwise.
792
+
793
+ Examples
794
+ --------
795
+ Can be called either using two nodes u, v, an edge tuple (u, v),
796
+ or an edge tuple (u, v, key).
797
+
798
+ >>> G = nx.MultiGraph() # or MultiDiGraph
799
+ >>> nx.add_path(G, [0, 1, 2, 3])
800
+ >>> G.has_edge(0, 1) # using two nodes
801
+ True
802
+ >>> e = (0, 1)
803
+ >>> G.has_edge(*e) # e is a 2-tuple (u, v)
804
+ True
805
+ >>> G.add_edge(0, 1, key="a")
806
+ 'a'
807
+ >>> G.has_edge(0, 1, key="a") # specify key
808
+ True
809
+ >>> G.has_edge(1, 0, key="a") # edges aren't directed
810
+ True
811
+ >>> e = (0, 1, "a")
812
+ >>> G.has_edge(*e) # e is a 3-tuple (u, v, 'a')
813
+ True
814
+
815
+ The following syntax are equivalent:
816
+
817
+ >>> G.has_edge(0, 1)
818
+ True
819
+ >>> 1 in G[0] # though this gives :exc:`KeyError` if 0 not in G
820
+ True
821
+ >>> 0 in G[1] # other order; also gives :exc:`KeyError` if 0 not in G
822
+ True
823
+
824
+ """
825
+ try:
826
+ if key is None:
827
+ return v in self._adj[u]
828
+ else:
829
+ return key in self._adj[u][v]
830
+ except KeyError:
831
+ return False
832
+
833
+ @cached_property
834
+ def edges(self):
835
+ """Returns an iterator over the edges.
836
+
837
+ edges(self, nbunch=None, data=False, keys=False, default=None)
838
+
839
+ The MultiEdgeView provides set-like operations on the edge-tuples
840
+ as well as edge attribute lookup. When called, it also provides
841
+ an EdgeDataView object which allows control of access to edge
842
+ attributes (but does not provide set-like operations).
843
+ Hence, ``G.edges[u, v, k]['color']`` provides the value of the color
844
+ attribute for the edge from ``u`` to ``v`` with key ``k`` while
845
+ ``for (u, v, k, c) in G.edges(data='color', keys=True, default="red"):``
846
+ iterates through all the edges yielding the color attribute with
847
+ default `'red'` if no color attribute exists.
848
+
849
+ Edges are returned as tuples with optional data and keys
850
+ in the order (node, neighbor, key, data). If ``keys=True`` is not
851
+ provided, the tuples will just be (node, neighbor, data), but
852
+ multiple tuples with the same node and neighbor will be generated
853
+ when multiple edges exist between two nodes.
854
+
855
+ Parameters
856
+ ----------
857
+ nbunch : single node, container, or all nodes (default= all nodes)
858
+ The view will only report edges from these nodes.
859
+ data : string or bool, optional (default=False)
860
+ The edge attribute returned in 3-tuple (u, v, ddict[data]).
861
+ If True, return edge attribute dict in 3-tuple (u, v, ddict).
862
+ If False, return 2-tuple (u, v).
863
+ keys : bool, optional (default=False)
864
+ If True, return edge keys with each edge, creating (u, v, k)
865
+ tuples or (u, v, k, d) tuples if data is also requested.
866
+ default : value, optional (default=None)
867
+ Value used for edges that don't have the requested attribute.
868
+ Only relevant if data is not True or False.
869
+
870
+ Returns
871
+ -------
872
+ edges : MultiEdgeView
873
+ A view of edge attributes, usually it iterates over (u, v)
874
+ (u, v, k) or (u, v, k, d) tuples of edges, but can also be
875
+ used for attribute lookup as ``edges[u, v, k]['foo']``.
876
+
877
+ Notes
878
+ -----
879
+ Nodes in nbunch that are not in the graph will be (quietly) ignored.
880
+ For directed graphs this returns the out-edges.
881
+
882
+ Examples
883
+ --------
884
+ >>> G = nx.MultiGraph()
885
+ >>> nx.add_path(G, [0, 1, 2])
886
+ >>> key = G.add_edge(2, 3, weight=5)
887
+ >>> key2 = G.add_edge(2, 1, weight=2) # multi-edge
888
+ >>> [e for e in G.edges()]
889
+ [(0, 1), (1, 2), (1, 2), (2, 3)]
890
+ >>> G.edges.data() # default data is {} (empty dict)
891
+ MultiEdgeDataView([(0, 1, {}), (1, 2, {}), (1, 2, {'weight': 2}), (2, 3, {'weight': 5})])
892
+ >>> G.edges.data("weight", default=1)
893
+ MultiEdgeDataView([(0, 1, 1), (1, 2, 1), (1, 2, 2), (2, 3, 5)])
894
+ >>> G.edges(keys=True) # default keys are integers
895
+ MultiEdgeView([(0, 1, 0), (1, 2, 0), (1, 2, 1), (2, 3, 0)])
896
+ >>> G.edges.data(keys=True)
897
+ MultiEdgeDataView([(0, 1, 0, {}), (1, 2, 0, {}), (1, 2, 1, {'weight': 2}), (2, 3, 0, {'weight': 5})])
898
+ >>> G.edges.data("weight", default=1, keys=True)
899
+ MultiEdgeDataView([(0, 1, 0, 1), (1, 2, 0, 1), (1, 2, 1, 2), (2, 3, 0, 5)])
900
+ >>> G.edges([0, 3]) # Note ordering of tuples from listed sources
901
+ MultiEdgeDataView([(0, 1), (3, 2)])
902
+ >>> G.edges([0, 3, 2, 1]) # Note ordering of tuples
903
+ MultiEdgeDataView([(0, 1), (3, 2), (2, 1), (2, 1)])
904
+ >>> G.edges(0)
905
+ MultiEdgeDataView([(0, 1)])
906
+ """
907
+ return MultiEdgeView(self)
908
+
909
+ def get_edge_data(self, u, v, key=None, default=None):
910
+ """Returns the attribute dictionary associated with edge (u, v,
911
+ key).
912
+
913
+ If a key is not provided, returns a dictionary mapping edge keys
914
+ to attribute dictionaries for each edge between u and v.
915
+
916
+ This is identical to `G[u][v][key]` except the default is returned
917
+ instead of an exception is the edge doesn't exist.
918
+
919
+ Parameters
920
+ ----------
921
+ u, v : nodes
922
+
923
+ default : any Python object (default=None)
924
+ Value to return if the specific edge (u, v, key) is not
925
+ found, OR if there are no edges between u and v and no key
926
+ is specified.
927
+
928
+ key : hashable identifier, optional (default=None)
929
+ Return data only for the edge with specified key, as an
930
+ attribute dictionary (rather than a dictionary mapping keys
931
+ to attribute dictionaries).
932
+
933
+ Returns
934
+ -------
935
+ edge_dict : dictionary
936
+ The edge attribute dictionary, OR a dictionary mapping edge
937
+ keys to attribute dictionaries for each of those edges if no
938
+ specific key is provided (even if there's only one edge
939
+ between u and v).
940
+
941
+ Examples
942
+ --------
943
+ >>> G = nx.MultiGraph() # or MultiDiGraph
944
+ >>> key = G.add_edge(0, 1, key="a", weight=7)
945
+ >>> G[0][1]["a"] # key='a'
946
+ {'weight': 7}
947
+ >>> G.edges[0, 1, "a"] # key='a'
948
+ {'weight': 7}
949
+
950
+ Warning: we protect the graph data structure by making
951
+ `G.edges` and `G[1][2]` read-only dict-like structures.
952
+ However, you can assign values to attributes in e.g.
953
+ `G.edges[1, 2, 'a']` or `G[1][2]['a']` using an additional
954
+ bracket as shown next. You need to specify all edge info
955
+ to assign to the edge data associated with an edge.
956
+
957
+ >>> G[0][1]["a"]["weight"] = 10
958
+ >>> G.edges[0, 1, "a"]["weight"] = 10
959
+ >>> G[0][1]["a"]["weight"]
960
+ 10
961
+ >>> G.edges[1, 0, "a"]["weight"]
962
+ 10
963
+
964
+ >>> G = nx.MultiGraph() # or MultiDiGraph
965
+ >>> nx.add_path(G, [0, 1, 2, 3])
966
+ >>> G.edges[0, 1, 0]["weight"] = 5
967
+ >>> G.get_edge_data(0, 1)
968
+ {0: {'weight': 5}}
969
+ >>> e = (0, 1)
970
+ >>> G.get_edge_data(*e) # tuple form
971
+ {0: {'weight': 5}}
972
+ >>> G.get_edge_data(3, 0) # edge not in graph, returns None
973
+ >>> G.get_edge_data(3, 0, default=0) # edge not in graph, return default
974
+ 0
975
+ >>> G.get_edge_data(1, 0, 0) # specific key gives back
976
+ {'weight': 5}
977
+ """
978
+ try:
979
+ if key is None:
980
+ return self._adj[u][v]
981
+ else:
982
+ return self._adj[u][v][key]
983
+ except KeyError:
984
+ return default
985
+
986
+ @cached_property
987
+ def degree(self):
988
+ """A DegreeView for the Graph as G.degree or G.degree().
989
+
990
+ The node degree is the number of edges adjacent to the node.
991
+ The weighted node degree is the sum of the edge weights for
992
+ edges incident to that node.
993
+
994
+ This object provides an iterator for (node, degree) as well as
995
+ lookup for the degree for a single node.
996
+
997
+ Parameters
998
+ ----------
999
+ nbunch : single node, container, or all nodes (default= all nodes)
1000
+ The view will only report edges incident to these nodes.
1001
+
1002
+ weight : string or None, optional (default=None)
1003
+ The name of an edge attribute that holds the numerical value used
1004
+ as a weight. If None, then each edge has weight 1.
1005
+ The degree is the sum of the edge weights adjacent to the node.
1006
+
1007
+ Returns
1008
+ -------
1009
+ MultiDegreeView or int
1010
+ If multiple nodes are requested (the default), returns a `MultiDegreeView`
1011
+ mapping nodes to their degree.
1012
+ If a single node is requested, returns the degree of the node as an integer.
1013
+
1014
+ Examples
1015
+ --------
1016
+ >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc
1017
+ >>> nx.add_path(G, [0, 1, 2, 3])
1018
+ >>> G.degree(0) # node 0 with degree 1
1019
+ 1
1020
+ >>> list(G.degree([0, 1]))
1021
+ [(0, 1), (1, 2)]
1022
+
1023
+ """
1024
+ return MultiDegreeView(self)
1025
+
1026
+ def is_multigraph(self):
1027
+ """Returns True if graph is a multigraph, False otherwise."""
1028
+ return True
1029
+
1030
+ def is_directed(self):
1031
+ """Returns True if graph is directed, False otherwise."""
1032
+ return False
1033
+
1034
+ def copy(self, as_view=False):
1035
+ """Returns a copy of the graph.
1036
+
1037
+ The copy method by default returns an independent shallow copy
1038
+ of the graph and attributes. That is, if an attribute is a
1039
+ container, that container is shared by the original an the copy.
1040
+ Use Python's `copy.deepcopy` for new containers.
1041
+
1042
+ If `as_view` is True then a view is returned instead of a copy.
1043
+
1044
+ Notes
1045
+ -----
1046
+ All copies reproduce the graph structure, but data attributes
1047
+ may be handled in different ways. There are four types of copies
1048
+ of a graph that people might want.
1049
+
1050
+ Deepcopy -- A "deepcopy" copies the graph structure as well as
1051
+ all data attributes and any objects they might contain.
1052
+ The entire graph object is new so that changes in the copy
1053
+ do not affect the original object. (see Python's copy.deepcopy)
1054
+
1055
+ Data Reference (Shallow) -- For a shallow copy the graph structure
1056
+ is copied but the edge, node and graph attribute dicts are
1057
+ references to those in the original graph. This saves
1058
+ time and memory but could cause confusion if you change an attribute
1059
+ in one graph and it changes the attribute in the other.
1060
+ NetworkX does not provide this level of shallow copy.
1061
+
1062
+ Independent Shallow -- This copy creates new independent attribute
1063
+ dicts and then does a shallow copy of the attributes. That is, any
1064
+ attributes that are containers are shared between the new graph
1065
+ and the original. This is exactly what `dict.copy()` provides.
1066
+ You can obtain this style copy using:
1067
+
1068
+ >>> G = nx.path_graph(5)
1069
+ >>> H = G.copy()
1070
+ >>> H = G.copy(as_view=False)
1071
+ >>> H = nx.Graph(G)
1072
+ >>> H = G.__class__(G)
1073
+
1074
+ Fresh Data -- For fresh data, the graph structure is copied while
1075
+ new empty data attribute dicts are created. The resulting graph
1076
+ is independent of the original and it has no edge, node or graph
1077
+ attributes. Fresh copies are not enabled. Instead use:
1078
+
1079
+ >>> H = G.__class__()
1080
+ >>> H.add_nodes_from(G)
1081
+ >>> H.add_edges_from(G.edges)
1082
+
1083
+ View -- Inspired by dict-views, graph-views act like read-only
1084
+ versions of the original graph, providing a copy of the original
1085
+ structure without requiring any memory for copying the information.
1086
+
1087
+ See the Python copy module for more information on shallow
1088
+ and deep copies, https://docs.python.org/3/library/copy.html.
1089
+
1090
+ Parameters
1091
+ ----------
1092
+ as_view : bool, optional (default=False)
1093
+ If True, the returned graph-view provides a read-only view
1094
+ of the original graph without actually copying any data.
1095
+
1096
+ Returns
1097
+ -------
1098
+ G : Graph
1099
+ A copy of the graph.
1100
+
1101
+ See Also
1102
+ --------
1103
+ to_directed: return a directed copy of the graph.
1104
+
1105
+ Examples
1106
+ --------
1107
+ >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1108
+ >>> H = G.copy()
1109
+
1110
+ """
1111
+ if as_view is True:
1112
+ return nx.graphviews.generic_graph_view(self)
1113
+ G = self.__class__()
1114
+ G.graph.update(self.graph)
1115
+ G.add_nodes_from((n, d.copy()) for n, d in self._node.items())
1116
+ G.add_edges_from(
1117
+ (u, v, key, datadict.copy())
1118
+ for u, nbrs in self._adj.items()
1119
+ for v, keydict in nbrs.items()
1120
+ for key, datadict in keydict.items()
1121
+ )
1122
+ return G
1123
+
1124
+ def to_directed(self, as_view=False):
1125
+ """Returns a directed representation of the graph.
1126
+
1127
+ Returns
1128
+ -------
1129
+ G : MultiDiGraph
1130
+ A directed graph with the same name, same nodes, and with
1131
+ each edge (u, v, k, data) replaced by two directed edges
1132
+ (u, v, k, data) and (v, u, k, data).
1133
+
1134
+ Notes
1135
+ -----
1136
+ This returns a "deepcopy" of the edge, node, and
1137
+ graph attributes which attempts to completely copy
1138
+ all of the data and references.
1139
+
1140
+ This is in contrast to the similar D=MultiDiGraph(G) which
1141
+ returns a shallow copy of the data.
1142
+
1143
+ See the Python copy module for more information on shallow
1144
+ and deep copies, https://docs.python.org/3/library/copy.html.
1145
+
1146
+ Warning: If you have subclassed MultiGraph to use dict-like objects
1147
+ in the data structure, those changes do not transfer to the
1148
+ MultiDiGraph created by this method.
1149
+
1150
+ Examples
1151
+ --------
1152
+ >>> G = nx.MultiGraph()
1153
+ >>> G.add_edge(0, 1)
1154
+ 0
1155
+ >>> G.add_edge(0, 1)
1156
+ 1
1157
+ >>> H = G.to_directed()
1158
+ >>> list(H.edges)
1159
+ [(0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1)]
1160
+
1161
+ If already directed, return a (deep) copy
1162
+
1163
+ >>> G = nx.MultiDiGraph()
1164
+ >>> G.add_edge(0, 1)
1165
+ 0
1166
+ >>> H = G.to_directed()
1167
+ >>> list(H.edges)
1168
+ [(0, 1, 0)]
1169
+ """
1170
+ graph_class = self.to_directed_class()
1171
+ if as_view is True:
1172
+ return nx.graphviews.generic_graph_view(self, graph_class)
1173
+ # deepcopy when not a view
1174
+ G = graph_class()
1175
+ G.graph.update(deepcopy(self.graph))
1176
+ G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
1177
+ G.add_edges_from(
1178
+ (u, v, key, deepcopy(datadict))
1179
+ for u, nbrs in self.adj.items()
1180
+ for v, keydict in nbrs.items()
1181
+ for key, datadict in keydict.items()
1182
+ )
1183
+ return G
1184
+
1185
+ def to_undirected(self, as_view=False):
1186
+ """Returns an undirected copy of the graph.
1187
+
1188
+ Returns
1189
+ -------
1190
+ G : Graph/MultiGraph
1191
+ A deepcopy of the graph.
1192
+
1193
+ See Also
1194
+ --------
1195
+ copy, add_edge, add_edges_from
1196
+
1197
+ Notes
1198
+ -----
1199
+ This returns a "deepcopy" of the edge, node, and
1200
+ graph attributes which attempts to completely copy
1201
+ all of the data and references.
1202
+
1203
+ This is in contrast to the similar `G = nx.MultiGraph(D)`
1204
+ which returns a shallow copy of the data.
1205
+
1206
+ See the Python copy module for more information on shallow
1207
+ and deep copies, https://docs.python.org/3/library/copy.html.
1208
+
1209
+ Warning: If you have subclassed MultiGraph to use dict-like
1210
+ objects in the data structure, those changes do not transfer
1211
+ to the MultiGraph created by this method.
1212
+
1213
+ Examples
1214
+ --------
1215
+ >>> G = nx.MultiGraph([(0, 1), (0, 1), (1, 2)])
1216
+ >>> H = G.to_directed()
1217
+ >>> list(H.edges)
1218
+ [(0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 2, 0), (2, 1, 0)]
1219
+ >>> G2 = H.to_undirected()
1220
+ >>> list(G2.edges)
1221
+ [(0, 1, 0), (0, 1, 1), (1, 2, 0)]
1222
+ """
1223
+ graph_class = self.to_undirected_class()
1224
+ if as_view is True:
1225
+ return nx.graphviews.generic_graph_view(self, graph_class)
1226
+ # deepcopy when not a view
1227
+ G = graph_class()
1228
+ G.graph.update(deepcopy(self.graph))
1229
+ G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
1230
+ G.add_edges_from(
1231
+ (u, v, key, deepcopy(datadict))
1232
+ for u, nbrs in self._adj.items()
1233
+ for v, keydict in nbrs.items()
1234
+ for key, datadict in keydict.items()
1235
+ )
1236
+ return G
1237
+
1238
+ def number_of_edges(self, u=None, v=None):
1239
+ """Returns the number of edges between two nodes.
1240
+
1241
+ Parameters
1242
+ ----------
1243
+ u, v : nodes, optional (Default=all edges)
1244
+ If u and v are specified, return the number of edges between
1245
+ u and v. Otherwise return the total number of all edges.
1246
+
1247
+ Returns
1248
+ -------
1249
+ nedges : int
1250
+ The number of edges in the graph. If nodes `u` and `v` are
1251
+ specified return the number of edges between those nodes. If
1252
+ the graph is directed, this only returns the number of edges
1253
+ from `u` to `v`.
1254
+
1255
+ See Also
1256
+ --------
1257
+ size
1258
+
1259
+ Examples
1260
+ --------
1261
+ For undirected multigraphs, this method counts the total number
1262
+ of edges in the graph::
1263
+
1264
+ >>> G = nx.MultiGraph()
1265
+ >>> G.add_edges_from([(0, 1), (0, 1), (1, 2)])
1266
+ [0, 1, 0]
1267
+ >>> G.number_of_edges()
1268
+ 3
1269
+
1270
+ If you specify two nodes, this counts the total number of edges
1271
+ joining the two nodes::
1272
+
1273
+ >>> G.number_of_edges(0, 1)
1274
+ 2
1275
+
1276
+ For directed multigraphs, this method can count the total number
1277
+ of directed edges from `u` to `v`::
1278
+
1279
+ >>> G = nx.MultiDiGraph()
1280
+ >>> G.add_edges_from([(0, 1), (0, 1), (1, 0)])
1281
+ [0, 1, 0]
1282
+ >>> G.number_of_edges(0, 1)
1283
+ 2
1284
+ >>> G.number_of_edges(1, 0)
1285
+ 1
1286
+
1287
+ """
1288
+ if u is None:
1289
+ return self.size()
1290
+ try:
1291
+ edgedata = self._adj[u][v]
1292
+ except KeyError:
1293
+ return 0 # no such edge
1294
+ return len(edgedata)
lib/python3.12/site-packages/networkx/classes/reportviews.py ADDED
@@ -0,0 +1,1447 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ View Classes provide node, edge and degree "views" of a graph.
3
+
4
+ Views for nodes, edges and degree are provided for all base graph classes.
5
+ A view means a read-only object that is quick to create, automatically
6
+ updated when the graph changes, and provides basic access like `n in V`,
7
+ `for n in V`, `V[n]` and sometimes set operations.
8
+
9
+ The views are read-only iterable containers that are updated as the
10
+ graph is updated. As with dicts, the graph should not be updated
11
+ while iterating through the view. Views can be iterated multiple times.
12
+
13
+ Edge and Node views also allow data attribute lookup.
14
+ The resulting attribute dict is writable as `G.edges[3, 4]['color']='red'`
15
+ Degree views allow lookup of degree values for single nodes.
16
+ Weighted degree is supported with the `weight` argument.
17
+
18
+ NodeView
19
+ ========
20
+
21
+ `V = G.nodes` (or `V = G.nodes()`) allows `len(V)`, `n in V`, set
22
+ operations e.g. "G.nodes & H.nodes", and `dd = G.nodes[n]`, where
23
+ `dd` is the node data dict. Iteration is over the nodes by default.
24
+
25
+ NodeDataView
26
+ ============
27
+
28
+ To iterate over (node, data) pairs, use arguments to `G.nodes()`
29
+ to create a DataView e.g. `DV = G.nodes(data='color', default='red')`.
30
+ The DataView iterates as `for n, color in DV` and allows
31
+ `(n, 'red') in DV`. Using `DV = G.nodes(data=True)`, the DataViews
32
+ use the full datadict in writeable form also allowing contain testing as
33
+ `(n, {'color': 'red'}) in VD`. DataViews allow set operations when
34
+ data attributes are hashable.
35
+
36
+ DegreeView
37
+ ==========
38
+
39
+ `V = G.degree` allows iteration over (node, degree) pairs as well
40
+ as lookup: `deg=V[n]`. There are many flavors of DegreeView
41
+ for In/Out/Directed/Multi. For Directed Graphs, `G.degree`
42
+ counts both in and out going edges. `G.out_degree` and
43
+ `G.in_degree` count only specific directions.
44
+ Weighted degree using edge data attributes is provide via
45
+ `V = G.degree(weight='attr_name')` where any string with the
46
+ attribute name can be used. `weight=None` is the default.
47
+ No set operations are implemented for degrees, use NodeView.
48
+
49
+ The argument `nbunch` restricts iteration to nodes in nbunch.
50
+ The DegreeView can still lookup any node even if nbunch is specified.
51
+
52
+ EdgeView
53
+ ========
54
+
55
+ `V = G.edges` or `V = G.edges()` allows iteration over edges as well as
56
+ `e in V`, set operations and edge data lookup `dd = G.edges[2, 3]`.
57
+ Iteration is over 2-tuples `(u, v)` for Graph/DiGraph. For multigraphs
58
+ edges 3-tuples `(u, v, key)` are the default but 2-tuples can be obtained
59
+ via `V = G.edges(keys=False)`.
60
+
61
+ Set operations for directed graphs treat the edges as a set of 2-tuples.
62
+ For undirected graphs, 2-tuples are not a unique representation of edges.
63
+ So long as the set being compared to contains unique representations
64
+ of its edges, the set operations will act as expected. If the other
65
+ set contains both `(0, 1)` and `(1, 0)` however, the result of set
66
+ operations may contain both representations of the same edge.
67
+
68
+ EdgeDataView
69
+ ============
70
+
71
+ Edge data can be reported using an EdgeDataView typically created
72
+ by calling an EdgeView: `DV = G.edges(data='weight', default=1)`.
73
+ The EdgeDataView allows iteration over edge tuples, membership checking
74
+ but no set operations.
75
+
76
+ Iteration depends on `data` and `default` and for multigraph `keys`
77
+ If `data is False` (the default) then iterate over 2-tuples `(u, v)`.
78
+ If `data is True` iterate over 3-tuples `(u, v, datadict)`.
79
+ Otherwise iterate over `(u, v, datadict.get(data, default))`.
80
+ For Multigraphs, if `keys is True`, replace `u, v` with `u, v, key`
81
+ to create 3-tuples and 4-tuples.
82
+
83
+ The argument `nbunch` restricts edges to those incident to nodes in nbunch.
84
+ """
85
+
86
+ from abc import ABC
87
+ from collections.abc import Mapping, Set
88
+
89
+ import networkx as nx
90
+
91
+ __all__ = [
92
+ "NodeView",
93
+ "NodeDataView",
94
+ "EdgeView",
95
+ "OutEdgeView",
96
+ "InEdgeView",
97
+ "EdgeDataView",
98
+ "OutEdgeDataView",
99
+ "InEdgeDataView",
100
+ "MultiEdgeView",
101
+ "OutMultiEdgeView",
102
+ "InMultiEdgeView",
103
+ "MultiEdgeDataView",
104
+ "OutMultiEdgeDataView",
105
+ "InMultiEdgeDataView",
106
+ "DegreeView",
107
+ "DiDegreeView",
108
+ "InDegreeView",
109
+ "OutDegreeView",
110
+ "MultiDegreeView",
111
+ "DiMultiDegreeView",
112
+ "InMultiDegreeView",
113
+ "OutMultiDegreeView",
114
+ ]
115
+
116
+
117
+ # NodeViews
118
+ class NodeView(Mapping, Set):
119
+ """A NodeView class to act as G.nodes for a NetworkX Graph
120
+
121
+ Set operations act on the nodes without considering data.
122
+ Iteration is over nodes. Node data can be looked up like a dict.
123
+ Use NodeDataView to iterate over node data or to specify a data
124
+ attribute for lookup. NodeDataView is created by calling the NodeView.
125
+
126
+ Parameters
127
+ ----------
128
+ graph : NetworkX graph-like class
129
+
130
+ Examples
131
+ --------
132
+ >>> G = nx.path_graph(3)
133
+ >>> NV = G.nodes()
134
+ >>> 2 in NV
135
+ True
136
+ >>> for n in NV:
137
+ ... print(n)
138
+ 0
139
+ 1
140
+ 2
141
+ >>> assert NV & {1, 2, 3} == {1, 2}
142
+
143
+ >>> G.add_node(2, color="blue")
144
+ >>> NV[2]
145
+ {'color': 'blue'}
146
+ >>> G.add_node(8, color="red")
147
+ >>> NDV = G.nodes(data=True)
148
+ >>> (2, NV[2]) in NDV
149
+ True
150
+ >>> for n, dd in NDV:
151
+ ... print((n, dd.get("color", "aqua")))
152
+ (0, 'aqua')
153
+ (1, 'aqua')
154
+ (2, 'blue')
155
+ (8, 'red')
156
+ >>> NDV[2] == NV[2]
157
+ True
158
+
159
+ >>> NVdata = G.nodes(data="color", default="aqua")
160
+ >>> (2, NVdata[2]) in NVdata
161
+ True
162
+ >>> for n, dd in NVdata:
163
+ ... print((n, dd))
164
+ (0, 'aqua')
165
+ (1, 'aqua')
166
+ (2, 'blue')
167
+ (8, 'red')
168
+ >>> NVdata[2] == NV[2] # NVdata gets 'color', NV gets datadict
169
+ False
170
+ """
171
+
172
+ __slots__ = ("_nodes",)
173
+
174
+ def __getstate__(self):
175
+ return {"_nodes": self._nodes}
176
+
177
+ def __setstate__(self, state):
178
+ self._nodes = state["_nodes"]
179
+
180
+ def __init__(self, graph):
181
+ self._nodes = graph._node
182
+
183
+ # Mapping methods
184
+ def __len__(self):
185
+ return len(self._nodes)
186
+
187
+ def __iter__(self):
188
+ return iter(self._nodes)
189
+
190
+ def __getitem__(self, n):
191
+ if isinstance(n, slice):
192
+ raise nx.NetworkXError(
193
+ f"{type(self).__name__} does not support slicing, "
194
+ f"try list(G.nodes)[{n.start}:{n.stop}:{n.step}]"
195
+ )
196
+ return self._nodes[n]
197
+
198
+ # Set methods
199
+ def __contains__(self, n):
200
+ return n in self._nodes
201
+
202
+ @classmethod
203
+ def _from_iterable(cls, it):
204
+ return set(it)
205
+
206
+ # DataView method
207
+ def __call__(self, data=False, default=None):
208
+ if data is False:
209
+ return self
210
+ return NodeDataView(self._nodes, data, default)
211
+
212
+ def data(self, data=True, default=None):
213
+ """
214
+ Return a read-only view of node data.
215
+
216
+ Parameters
217
+ ----------
218
+ data : bool or node data key, default=True
219
+ If ``data=True`` (the default), return a `NodeDataView` object that
220
+ maps each node to *all* of its attributes. `data` may also be an
221
+ arbitrary key, in which case the `NodeDataView` maps each node to
222
+ the value for the keyed attribute. In this case, if a node does
223
+ not have the `data` attribute, the `default` value is used.
224
+ default : object, default=None
225
+ The value used when a node does not have a specific attribute.
226
+
227
+ Returns
228
+ -------
229
+ NodeDataView
230
+ The layout of the returned NodeDataView depends on the value of the
231
+ `data` parameter.
232
+
233
+ Notes
234
+ -----
235
+ If ``data=False``, returns a `NodeView` object without data.
236
+
237
+ See Also
238
+ --------
239
+ NodeDataView
240
+
241
+ Examples
242
+ --------
243
+ >>> G = nx.Graph()
244
+ >>> G.add_nodes_from(
245
+ ... [
246
+ ... (0, {"color": "red", "weight": 10}),
247
+ ... (1, {"color": "blue"}),
248
+ ... (2, {"color": "yellow", "weight": 2}),
249
+ ... ]
250
+ ... )
251
+
252
+ Accessing node data with ``data=True`` (the default) returns a
253
+ NodeDataView mapping each node to all of its attributes:
254
+
255
+ >>> G.nodes.data()
256
+ NodeDataView({0: {'color': 'red', 'weight': 10}, 1: {'color': 'blue'}, 2: {'color': 'yellow', 'weight': 2}})
257
+
258
+ If `data` represents a key in the node attribute dict, a NodeDataView mapping
259
+ the nodes to the value for that specific key is returned:
260
+
261
+ >>> G.nodes.data("color")
262
+ NodeDataView({0: 'red', 1: 'blue', 2: 'yellow'}, data='color')
263
+
264
+ If a specific key is not found in an attribute dict, the value specified
265
+ by `default` is returned:
266
+
267
+ >>> G.nodes.data("weight", default=-999)
268
+ NodeDataView({0: 10, 1: -999, 2: 2}, data='weight')
269
+
270
+ Note that there is no check that the `data` key is in any of the
271
+ node attribute dictionaries:
272
+
273
+ >>> G.nodes.data("height")
274
+ NodeDataView({0: None, 1: None, 2: None}, data='height')
275
+ """
276
+ if data is False:
277
+ return self
278
+ return NodeDataView(self._nodes, data, default)
279
+
280
+ def __str__(self):
281
+ return str(list(self))
282
+
283
+ def __repr__(self):
284
+ return f"{self.__class__.__name__}({tuple(self)})"
285
+
286
+
287
+ class NodeDataView(Set):
288
+ """A DataView class for nodes of a NetworkX Graph
289
+
290
+ The main use for this class is to iterate through node-data pairs.
291
+ The data can be the entire data-dictionary for each node, or it
292
+ can be a specific attribute (with default) for each node.
293
+ Set operations are enabled with NodeDataView, but don't work in
294
+ cases where the data is not hashable. Use with caution.
295
+ Typically, set operations on nodes use NodeView, not NodeDataView.
296
+ That is, they use `G.nodes` instead of `G.nodes(data='foo')`.
297
+
298
+ Parameters
299
+ ==========
300
+ graph : NetworkX graph-like class
301
+ data : bool or string (default=False)
302
+ default : object (default=None)
303
+ """
304
+
305
+ __slots__ = ("_nodes", "_data", "_default")
306
+
307
+ def __getstate__(self):
308
+ return {"_nodes": self._nodes, "_data": self._data, "_default": self._default}
309
+
310
+ def __setstate__(self, state):
311
+ self._nodes = state["_nodes"]
312
+ self._data = state["_data"]
313
+ self._default = state["_default"]
314
+
315
+ def __init__(self, nodedict, data=False, default=None):
316
+ self._nodes = nodedict
317
+ self._data = data
318
+ self._default = default
319
+
320
+ @classmethod
321
+ def _from_iterable(cls, it):
322
+ try:
323
+ return set(it)
324
+ except TypeError as err:
325
+ if "unhashable" in str(err):
326
+ msg = " : Could be b/c data=True or your values are unhashable"
327
+ raise TypeError(str(err) + msg) from err
328
+ raise
329
+
330
+ def __len__(self):
331
+ return len(self._nodes)
332
+
333
+ def __iter__(self):
334
+ data = self._data
335
+ if data is False:
336
+ return iter(self._nodes)
337
+ if data is True:
338
+ return iter(self._nodes.items())
339
+ return (
340
+ (n, dd[data] if data in dd else self._default)
341
+ for n, dd in self._nodes.items()
342
+ )
343
+
344
+ def __contains__(self, n):
345
+ try:
346
+ node_in = n in self._nodes
347
+ except TypeError:
348
+ n, d = n
349
+ return n in self._nodes and self[n] == d
350
+ if node_in is True:
351
+ return node_in
352
+ try:
353
+ n, d = n
354
+ except (TypeError, ValueError):
355
+ return False
356
+ return n in self._nodes and self[n] == d
357
+
358
+ def __getitem__(self, n):
359
+ if isinstance(n, slice):
360
+ raise nx.NetworkXError(
361
+ f"{type(self).__name__} does not support slicing, "
362
+ f"try list(G.nodes.data())[{n.start}:{n.stop}:{n.step}]"
363
+ )
364
+ ddict = self._nodes[n]
365
+ data = self._data
366
+ if data is False or data is True:
367
+ return ddict
368
+ return ddict[data] if data in ddict else self._default
369
+
370
+ def __str__(self):
371
+ return str(list(self))
372
+
373
+ def __repr__(self):
374
+ name = self.__class__.__name__
375
+ if self._data is False:
376
+ return f"{name}({tuple(self)})"
377
+ if self._data is True:
378
+ return f"{name}({dict(self)})"
379
+ return f"{name}({dict(self)}, data={self._data!r})"
380
+
381
+
382
+ # DegreeViews
383
+ class DiDegreeView:
384
+ """A View class for degree of nodes in a NetworkX Graph
385
+
386
+ The functionality is like dict.items() with (node, degree) pairs.
387
+ Additional functionality includes read-only lookup of node degree,
388
+ and calling with optional features nbunch (for only a subset of nodes)
389
+ and weight (use edge weights to compute degree).
390
+
391
+ Parameters
392
+ ==========
393
+ graph : NetworkX graph-like class
394
+ nbunch : node, container of nodes, or None meaning all nodes (default=None)
395
+ weight : bool or string (default=None)
396
+
397
+ Notes
398
+ -----
399
+ DegreeView can still lookup any node even if nbunch is specified.
400
+
401
+ Examples
402
+ --------
403
+ >>> G = nx.path_graph(3)
404
+ >>> DV = G.degree()
405
+ >>> assert DV[2] == 1
406
+ >>> assert sum(deg for n, deg in DV) == 4
407
+
408
+ >>> DVweight = G.degree(weight="span")
409
+ >>> G.add_edge(1, 2, span=34)
410
+ >>> DVweight[2]
411
+ 34
412
+ >>> DVweight[0] # default edge weight is 1
413
+ 1
414
+ >>> sum(span for n, span in DVweight) # sum weighted degrees
415
+ 70
416
+
417
+ >>> DVnbunch = G.degree(nbunch=(1, 2))
418
+ >>> assert len(list(DVnbunch)) == 2 # iteration over nbunch only
419
+ """
420
+
421
+ def __init__(self, G, nbunch=None, weight=None):
422
+ self._graph = G
423
+ self._succ = G._succ if hasattr(G, "_succ") else G._adj
424
+ self._pred = G._pred if hasattr(G, "_pred") else G._adj
425
+ self._nodes = self._succ if nbunch is None else list(G.nbunch_iter(nbunch))
426
+ self._weight = weight
427
+
428
+ def __call__(self, nbunch=None, weight=None):
429
+ if nbunch is None:
430
+ if weight == self._weight:
431
+ return self
432
+ return self.__class__(self._graph, None, weight)
433
+ try:
434
+ if nbunch in self._nodes:
435
+ if weight == self._weight:
436
+ return self[nbunch]
437
+ return self.__class__(self._graph, None, weight)[nbunch]
438
+ except TypeError:
439
+ pass
440
+ return self.__class__(self._graph, nbunch, weight)
441
+
442
+ def __getitem__(self, n):
443
+ weight = self._weight
444
+ succs = self._succ[n]
445
+ preds = self._pred[n]
446
+ if weight is None:
447
+ return len(succs) + len(preds)
448
+ return sum(dd.get(weight, 1) for dd in succs.values()) + sum(
449
+ dd.get(weight, 1) for dd in preds.values()
450
+ )
451
+
452
+ def __iter__(self):
453
+ weight = self._weight
454
+ if weight is None:
455
+ for n in self._nodes:
456
+ succs = self._succ[n]
457
+ preds = self._pred[n]
458
+ yield (n, len(succs) + len(preds))
459
+ else:
460
+ for n in self._nodes:
461
+ succs = self._succ[n]
462
+ preds = self._pred[n]
463
+ deg = sum(dd.get(weight, 1) for dd in succs.values()) + sum(
464
+ dd.get(weight, 1) for dd in preds.values()
465
+ )
466
+ yield (n, deg)
467
+
468
+ def __len__(self):
469
+ return len(self._nodes)
470
+
471
+ def __str__(self):
472
+ return str(list(self))
473
+
474
+ def __repr__(self):
475
+ return f"{self.__class__.__name__}({dict(self)})"
476
+
477
+
478
+ class DegreeView(DiDegreeView):
479
+ """A DegreeView class to act as G.degree for a NetworkX Graph
480
+
481
+ Typical usage focuses on iteration over `(node, degree)` pairs.
482
+ The degree is by default the number of edges incident to the node.
483
+ Optional argument `weight` enables weighted degree using the edge
484
+ attribute named in the `weight` argument. Reporting and iteration
485
+ can also be restricted to a subset of nodes using `nbunch`.
486
+
487
+ Additional functionality include node lookup so that `G.degree[n]`
488
+ reported the (possibly weighted) degree of node `n`. Calling the
489
+ view creates a view with different arguments `nbunch` or `weight`.
490
+
491
+ Parameters
492
+ ==========
493
+ graph : NetworkX graph-like class
494
+ nbunch : node, container of nodes, or None meaning all nodes (default=None)
495
+ weight : string or None (default=None)
496
+
497
+ Notes
498
+ -----
499
+ DegreeView can still lookup any node even if nbunch is specified.
500
+
501
+ Examples
502
+ --------
503
+ >>> G = nx.path_graph(3)
504
+ >>> DV = G.degree()
505
+ >>> assert DV[2] == 1
506
+ >>> assert G.degree[2] == 1
507
+ >>> assert sum(deg for n, deg in DV) == 4
508
+
509
+ >>> DVweight = G.degree(weight="span")
510
+ >>> G.add_edge(1, 2, span=34)
511
+ >>> DVweight[2]
512
+ 34
513
+ >>> DVweight[0] # default edge weight is 1
514
+ 1
515
+ >>> sum(span for n, span in DVweight) # sum weighted degrees
516
+ 70
517
+
518
+ >>> DVnbunch = G.degree(nbunch=(1, 2))
519
+ >>> assert len(list(DVnbunch)) == 2 # iteration over nbunch only
520
+ """
521
+
522
+ def __getitem__(self, n):
523
+ weight = self._weight
524
+ nbrs = self._succ[n]
525
+ if weight is None:
526
+ return len(nbrs) + (n in nbrs)
527
+ return sum(dd.get(weight, 1) for dd in nbrs.values()) + (
528
+ n in nbrs and nbrs[n].get(weight, 1)
529
+ )
530
+
531
+ def __iter__(self):
532
+ weight = self._weight
533
+ if weight is None:
534
+ for n in self._nodes:
535
+ nbrs = self._succ[n]
536
+ yield (n, len(nbrs) + (n in nbrs))
537
+ else:
538
+ for n in self._nodes:
539
+ nbrs = self._succ[n]
540
+ deg = sum(dd.get(weight, 1) for dd in nbrs.values()) + (
541
+ n in nbrs and nbrs[n].get(weight, 1)
542
+ )
543
+ yield (n, deg)
544
+
545
+
546
+ class OutDegreeView(DiDegreeView):
547
+ """A DegreeView class to report out_degree for a DiGraph; See DegreeView"""
548
+
549
+ def __getitem__(self, n):
550
+ weight = self._weight
551
+ nbrs = self._succ[n]
552
+ if self._weight is None:
553
+ return len(nbrs)
554
+ return sum(dd.get(self._weight, 1) for dd in nbrs.values())
555
+
556
+ def __iter__(self):
557
+ weight = self._weight
558
+ if weight is None:
559
+ for n in self._nodes:
560
+ succs = self._succ[n]
561
+ yield (n, len(succs))
562
+ else:
563
+ for n in self._nodes:
564
+ succs = self._succ[n]
565
+ deg = sum(dd.get(weight, 1) for dd in succs.values())
566
+ yield (n, deg)
567
+
568
+
569
+ class InDegreeView(DiDegreeView):
570
+ """A DegreeView class to report in_degree for a DiGraph; See DegreeView"""
571
+
572
+ def __getitem__(self, n):
573
+ weight = self._weight
574
+ nbrs = self._pred[n]
575
+ if weight is None:
576
+ return len(nbrs)
577
+ return sum(dd.get(weight, 1) for dd in nbrs.values())
578
+
579
+ def __iter__(self):
580
+ weight = self._weight
581
+ if weight is None:
582
+ for n in self._nodes:
583
+ preds = self._pred[n]
584
+ yield (n, len(preds))
585
+ else:
586
+ for n in self._nodes:
587
+ preds = self._pred[n]
588
+ deg = sum(dd.get(weight, 1) for dd in preds.values())
589
+ yield (n, deg)
590
+
591
+
592
+ class MultiDegreeView(DiDegreeView):
593
+ """A DegreeView class for undirected multigraphs; See DegreeView"""
594
+
595
+ def __getitem__(self, n):
596
+ weight = self._weight
597
+ nbrs = self._succ[n]
598
+ if weight is None:
599
+ return sum(len(keys) for keys in nbrs.values()) + (
600
+ n in nbrs and len(nbrs[n])
601
+ )
602
+ # edge weighted graph - degree is sum of nbr edge weights
603
+ deg = sum(
604
+ d.get(weight, 1) for key_dict in nbrs.values() for d in key_dict.values()
605
+ )
606
+ if n in nbrs:
607
+ deg += sum(d.get(weight, 1) for d in nbrs[n].values())
608
+ return deg
609
+
610
+ def __iter__(self):
611
+ weight = self._weight
612
+ if weight is None:
613
+ for n in self._nodes:
614
+ nbrs = self._succ[n]
615
+ deg = sum(len(keys) for keys in nbrs.values()) + (
616
+ n in nbrs and len(nbrs[n])
617
+ )
618
+ yield (n, deg)
619
+ else:
620
+ for n in self._nodes:
621
+ nbrs = self._succ[n]
622
+ deg = sum(
623
+ d.get(weight, 1)
624
+ for key_dict in nbrs.values()
625
+ for d in key_dict.values()
626
+ )
627
+ if n in nbrs:
628
+ deg += sum(d.get(weight, 1) for d in nbrs[n].values())
629
+ yield (n, deg)
630
+
631
+
632
+ class DiMultiDegreeView(DiDegreeView):
633
+ """A DegreeView class for MultiDiGraph; See DegreeView"""
634
+
635
+ def __getitem__(self, n):
636
+ weight = self._weight
637
+ succs = self._succ[n]
638
+ preds = self._pred[n]
639
+ if weight is None:
640
+ return sum(len(keys) for keys in succs.values()) + sum(
641
+ len(keys) for keys in preds.values()
642
+ )
643
+ # edge weighted graph - degree is sum of nbr edge weights
644
+ deg = sum(
645
+ d.get(weight, 1) for key_dict in succs.values() for d in key_dict.values()
646
+ ) + sum(
647
+ d.get(weight, 1) for key_dict in preds.values() for d in key_dict.values()
648
+ )
649
+ return deg
650
+
651
+ def __iter__(self):
652
+ weight = self._weight
653
+ if weight is None:
654
+ for n in self._nodes:
655
+ succs = self._succ[n]
656
+ preds = self._pred[n]
657
+ deg = sum(len(keys) for keys in succs.values()) + sum(
658
+ len(keys) for keys in preds.values()
659
+ )
660
+ yield (n, deg)
661
+ else:
662
+ for n in self._nodes:
663
+ succs = self._succ[n]
664
+ preds = self._pred[n]
665
+ deg = sum(
666
+ d.get(weight, 1)
667
+ for key_dict in succs.values()
668
+ for d in key_dict.values()
669
+ ) + sum(
670
+ d.get(weight, 1)
671
+ for key_dict in preds.values()
672
+ for d in key_dict.values()
673
+ )
674
+ yield (n, deg)
675
+
676
+
677
+ class InMultiDegreeView(DiDegreeView):
678
+ """A DegreeView class for inward degree of MultiDiGraph; See DegreeView"""
679
+
680
+ def __getitem__(self, n):
681
+ weight = self._weight
682
+ nbrs = self._pred[n]
683
+ if weight is None:
684
+ return sum(len(data) for data in nbrs.values())
685
+ # edge weighted graph - degree is sum of nbr edge weights
686
+ return sum(
687
+ d.get(weight, 1) for key_dict in nbrs.values() for d in key_dict.values()
688
+ )
689
+
690
+ def __iter__(self):
691
+ weight = self._weight
692
+ if weight is None:
693
+ for n in self._nodes:
694
+ nbrs = self._pred[n]
695
+ deg = sum(len(data) for data in nbrs.values())
696
+ yield (n, deg)
697
+ else:
698
+ for n in self._nodes:
699
+ nbrs = self._pred[n]
700
+ deg = sum(
701
+ d.get(weight, 1)
702
+ for key_dict in nbrs.values()
703
+ for d in key_dict.values()
704
+ )
705
+ yield (n, deg)
706
+
707
+
708
+ class OutMultiDegreeView(DiDegreeView):
709
+ """A DegreeView class for outward degree of MultiDiGraph; See DegreeView"""
710
+
711
+ def __getitem__(self, n):
712
+ weight = self._weight
713
+ nbrs = self._succ[n]
714
+ if weight is None:
715
+ return sum(len(data) for data in nbrs.values())
716
+ # edge weighted graph - degree is sum of nbr edge weights
717
+ return sum(
718
+ d.get(weight, 1) for key_dict in nbrs.values() for d in key_dict.values()
719
+ )
720
+
721
+ def __iter__(self):
722
+ weight = self._weight
723
+ if weight is None:
724
+ for n in self._nodes:
725
+ nbrs = self._succ[n]
726
+ deg = sum(len(data) for data in nbrs.values())
727
+ yield (n, deg)
728
+ else:
729
+ for n in self._nodes:
730
+ nbrs = self._succ[n]
731
+ deg = sum(
732
+ d.get(weight, 1)
733
+ for key_dict in nbrs.values()
734
+ for d in key_dict.values()
735
+ )
736
+ yield (n, deg)
737
+
738
+
739
+ # A base class for all edge views. Ensures all edge view and edge data view
740
+ # objects/classes are captured by `isinstance(obj, EdgeViewABC)` and
741
+ # `issubclass(cls, EdgeViewABC)` respectively
742
+ class EdgeViewABC(ABC):
743
+ pass
744
+
745
+
746
+ # EdgeDataViews
747
+ class OutEdgeDataView(EdgeViewABC):
748
+ """EdgeDataView for outward edges of DiGraph; See EdgeDataView"""
749
+
750
+ __slots__ = (
751
+ "_viewer",
752
+ "_nbunch",
753
+ "_data",
754
+ "_default",
755
+ "_adjdict",
756
+ "_nodes_nbrs",
757
+ "_report",
758
+ )
759
+
760
+ def __getstate__(self):
761
+ return {
762
+ "viewer": self._viewer,
763
+ "nbunch": self._nbunch,
764
+ "data": self._data,
765
+ "default": self._default,
766
+ }
767
+
768
+ def __setstate__(self, state):
769
+ self.__init__(**state)
770
+
771
+ def __init__(self, viewer, nbunch=None, data=False, *, default=None):
772
+ self._viewer = viewer
773
+ adjdict = self._adjdict = viewer._adjdict
774
+ if nbunch is None:
775
+ self._nodes_nbrs = adjdict.items
776
+ else:
777
+ # dict retains order of nodes but acts like a set
778
+ nbunch = dict.fromkeys(viewer._graph.nbunch_iter(nbunch))
779
+ self._nodes_nbrs = lambda: [(n, adjdict[n]) for n in nbunch]
780
+ self._nbunch = nbunch
781
+ self._data = data
782
+ self._default = default
783
+ # Set _report based on data and default
784
+ if data is True:
785
+ self._report = lambda n, nbr, dd: (n, nbr, dd)
786
+ elif data is False:
787
+ self._report = lambda n, nbr, dd: (n, nbr)
788
+ else: # data is attribute name
789
+ self._report = (
790
+ lambda n, nbr, dd: (n, nbr, dd[data])
791
+ if data in dd
792
+ else (n, nbr, default)
793
+ )
794
+
795
+ def __len__(self):
796
+ return sum(len(nbrs) for n, nbrs in self._nodes_nbrs())
797
+
798
+ def __iter__(self):
799
+ return (
800
+ self._report(n, nbr, dd)
801
+ for n, nbrs in self._nodes_nbrs()
802
+ for nbr, dd in nbrs.items()
803
+ )
804
+
805
+ def __contains__(self, e):
806
+ u, v = e[:2]
807
+ if self._nbunch is not None and u not in self._nbunch:
808
+ return False # this edge doesn't start in nbunch
809
+ try:
810
+ ddict = self._adjdict[u][v]
811
+ except KeyError:
812
+ return False
813
+ return e == self._report(u, v, ddict)
814
+
815
+ def __str__(self):
816
+ return str(list(self))
817
+
818
+ def __repr__(self):
819
+ return f"{self.__class__.__name__}({list(self)})"
820
+
821
+
822
+ class EdgeDataView(OutEdgeDataView):
823
+ """A EdgeDataView class for edges of Graph
824
+
825
+ This view is primarily used to iterate over the edges reporting
826
+ edges as node-tuples with edge data optionally reported. The
827
+ argument `nbunch` allows restriction to edges incident to nodes
828
+ in that container/singleton. The default (nbunch=None)
829
+ reports all edges. The arguments `data` and `default` control
830
+ what edge data is reported. The default `data is False` reports
831
+ only node-tuples for each edge. If `data is True` the entire edge
832
+ data dict is returned. Otherwise `data` is assumed to hold the name
833
+ of the edge attribute to report with default `default` if that
834
+ edge attribute is not present.
835
+
836
+ Parameters
837
+ ----------
838
+ nbunch : container of nodes, node or None (default None)
839
+ data : False, True or string (default False)
840
+ default : default value (default None)
841
+
842
+ Examples
843
+ --------
844
+ >>> G = nx.path_graph(3)
845
+ >>> G.add_edge(1, 2, foo="bar")
846
+ >>> list(G.edges(data="foo", default="biz"))
847
+ [(0, 1, 'biz'), (1, 2, 'bar')]
848
+ >>> assert (0, 1, "biz") in G.edges(data="foo", default="biz")
849
+ """
850
+
851
+ __slots__ = ()
852
+
853
+ def __len__(self):
854
+ return sum(1 for e in self)
855
+
856
+ def __iter__(self):
857
+ seen = {}
858
+ for n, nbrs in self._nodes_nbrs():
859
+ for nbr, dd in nbrs.items():
860
+ if nbr not in seen:
861
+ yield self._report(n, nbr, dd)
862
+ seen[n] = 1
863
+ del seen
864
+
865
+ def __contains__(self, e):
866
+ u, v = e[:2]
867
+ if self._nbunch is not None and u not in self._nbunch and v not in self._nbunch:
868
+ return False # this edge doesn't start and it doesn't end in nbunch
869
+ try:
870
+ ddict = self._adjdict[u][v]
871
+ except KeyError:
872
+ return False
873
+ return e == self._report(u, v, ddict)
874
+
875
+
876
+ class InEdgeDataView(OutEdgeDataView):
877
+ """An EdgeDataView class for outward edges of DiGraph; See EdgeDataView"""
878
+
879
+ __slots__ = ()
880
+
881
+ def __iter__(self):
882
+ return (
883
+ self._report(nbr, n, dd)
884
+ for n, nbrs in self._nodes_nbrs()
885
+ for nbr, dd in nbrs.items()
886
+ )
887
+
888
+ def __contains__(self, e):
889
+ u, v = e[:2]
890
+ if self._nbunch is not None and v not in self._nbunch:
891
+ return False # this edge doesn't end in nbunch
892
+ try:
893
+ ddict = self._adjdict[v][u]
894
+ except KeyError:
895
+ return False
896
+ return e == self._report(u, v, ddict)
897
+
898
+
899
+ class OutMultiEdgeDataView(OutEdgeDataView):
900
+ """An EdgeDataView for outward edges of MultiDiGraph; See EdgeDataView"""
901
+
902
+ __slots__ = ("keys",)
903
+
904
+ def __getstate__(self):
905
+ return {
906
+ "viewer": self._viewer,
907
+ "nbunch": self._nbunch,
908
+ "keys": self.keys,
909
+ "data": self._data,
910
+ "default": self._default,
911
+ }
912
+
913
+ def __setstate__(self, state):
914
+ self.__init__(**state)
915
+
916
+ def __init__(self, viewer, nbunch=None, data=False, *, default=None, keys=False):
917
+ self._viewer = viewer
918
+ adjdict = self._adjdict = viewer._adjdict
919
+ self.keys = keys
920
+ if nbunch is None:
921
+ self._nodes_nbrs = adjdict.items
922
+ else:
923
+ # dict retains order of nodes but acts like a set
924
+ nbunch = dict.fromkeys(viewer._graph.nbunch_iter(nbunch))
925
+ self._nodes_nbrs = lambda: [(n, adjdict[n]) for n in nbunch]
926
+ self._nbunch = nbunch
927
+ self._data = data
928
+ self._default = default
929
+ # Set _report based on data and default
930
+ if data is True:
931
+ if keys is True:
932
+ self._report = lambda n, nbr, k, dd: (n, nbr, k, dd)
933
+ else:
934
+ self._report = lambda n, nbr, k, dd: (n, nbr, dd)
935
+ elif data is False:
936
+ if keys is True:
937
+ self._report = lambda n, nbr, k, dd: (n, nbr, k)
938
+ else:
939
+ self._report = lambda n, nbr, k, dd: (n, nbr)
940
+ else: # data is attribute name
941
+ if keys is True:
942
+ self._report = (
943
+ lambda n, nbr, k, dd: (n, nbr, k, dd[data])
944
+ if data in dd
945
+ else (n, nbr, k, default)
946
+ )
947
+ else:
948
+ self._report = (
949
+ lambda n, nbr, k, dd: (n, nbr, dd[data])
950
+ if data in dd
951
+ else (n, nbr, default)
952
+ )
953
+
954
+ def __len__(self):
955
+ return sum(1 for e in self)
956
+
957
+ def __iter__(self):
958
+ return (
959
+ self._report(n, nbr, k, dd)
960
+ for n, nbrs in self._nodes_nbrs()
961
+ for nbr, kd in nbrs.items()
962
+ for k, dd in kd.items()
963
+ )
964
+
965
+ def __contains__(self, e):
966
+ u, v = e[:2]
967
+ if self._nbunch is not None and u not in self._nbunch:
968
+ return False # this edge doesn't start in nbunch
969
+ try:
970
+ kdict = self._adjdict[u][v]
971
+ except KeyError:
972
+ return False
973
+ if self.keys is True:
974
+ k = e[2]
975
+ try:
976
+ dd = kdict[k]
977
+ except KeyError:
978
+ return False
979
+ return e == self._report(u, v, k, dd)
980
+ return any(e == self._report(u, v, k, dd) for k, dd in kdict.items())
981
+
982
+
983
+ class MultiEdgeDataView(OutMultiEdgeDataView):
984
+ """An EdgeDataView class for edges of MultiGraph; See EdgeDataView"""
985
+
986
+ __slots__ = ()
987
+
988
+ def __iter__(self):
989
+ seen = {}
990
+ for n, nbrs in self._nodes_nbrs():
991
+ for nbr, kd in nbrs.items():
992
+ if nbr not in seen:
993
+ for k, dd in kd.items():
994
+ yield self._report(n, nbr, k, dd)
995
+ seen[n] = 1
996
+ del seen
997
+
998
+ def __contains__(self, e):
999
+ u, v = e[:2]
1000
+ if self._nbunch is not None and u not in self._nbunch and v not in self._nbunch:
1001
+ return False # this edge doesn't start and doesn't end in nbunch
1002
+ try:
1003
+ kdict = self._adjdict[u][v]
1004
+ except KeyError:
1005
+ try:
1006
+ kdict = self._adjdict[v][u]
1007
+ except KeyError:
1008
+ return False
1009
+ if self.keys is True:
1010
+ k = e[2]
1011
+ try:
1012
+ dd = kdict[k]
1013
+ except KeyError:
1014
+ return False
1015
+ return e == self._report(u, v, k, dd)
1016
+ return any(e == self._report(u, v, k, dd) for k, dd in kdict.items())
1017
+
1018
+
1019
+ class InMultiEdgeDataView(OutMultiEdgeDataView):
1020
+ """An EdgeDataView for inward edges of MultiDiGraph; See EdgeDataView"""
1021
+
1022
+ __slots__ = ()
1023
+
1024
+ def __iter__(self):
1025
+ return (
1026
+ self._report(nbr, n, k, dd)
1027
+ for n, nbrs in self._nodes_nbrs()
1028
+ for nbr, kd in nbrs.items()
1029
+ for k, dd in kd.items()
1030
+ )
1031
+
1032
+ def __contains__(self, e):
1033
+ u, v = e[:2]
1034
+ if self._nbunch is not None and v not in self._nbunch:
1035
+ return False # this edge doesn't end in nbunch
1036
+ try:
1037
+ kdict = self._adjdict[v][u]
1038
+ except KeyError:
1039
+ return False
1040
+ if self.keys is True:
1041
+ k = e[2]
1042
+ dd = kdict[k]
1043
+ return e == self._report(u, v, k, dd)
1044
+ return any(e == self._report(u, v, k, dd) for k, dd in kdict.items())
1045
+
1046
+
1047
+ # EdgeViews have set operations and no data reported
1048
+ class OutEdgeView(Set, Mapping, EdgeViewABC):
1049
+ """A EdgeView class for outward edges of a DiGraph"""
1050
+
1051
+ __slots__ = ("_adjdict", "_graph", "_nodes_nbrs")
1052
+
1053
+ def __getstate__(self):
1054
+ return {"_graph": self._graph, "_adjdict": self._adjdict}
1055
+
1056
+ def __setstate__(self, state):
1057
+ self._graph = state["_graph"]
1058
+ self._adjdict = state["_adjdict"]
1059
+ self._nodes_nbrs = self._adjdict.items
1060
+
1061
+ @classmethod
1062
+ def _from_iterable(cls, it):
1063
+ return set(it)
1064
+
1065
+ dataview = OutEdgeDataView
1066
+
1067
+ def __init__(self, G):
1068
+ self._graph = G
1069
+ self._adjdict = G._succ if hasattr(G, "succ") else G._adj
1070
+ self._nodes_nbrs = self._adjdict.items
1071
+
1072
+ # Set methods
1073
+ def __len__(self):
1074
+ return sum(len(nbrs) for n, nbrs in self._nodes_nbrs())
1075
+
1076
+ def __iter__(self):
1077
+ for n, nbrs in self._nodes_nbrs():
1078
+ for nbr in nbrs:
1079
+ yield (n, nbr)
1080
+
1081
+ def __contains__(self, e):
1082
+ try:
1083
+ u, v = e
1084
+ return v in self._adjdict[u]
1085
+ except KeyError:
1086
+ return False
1087
+
1088
+ # Mapping Methods
1089
+ def __getitem__(self, e):
1090
+ if isinstance(e, slice):
1091
+ raise nx.NetworkXError(
1092
+ f"{type(self).__name__} does not support slicing, "
1093
+ f"try list(G.edges)[{e.start}:{e.stop}:{e.step}]"
1094
+ )
1095
+ u, v = e
1096
+ try:
1097
+ return self._adjdict[u][v]
1098
+ except KeyError as ex: # Customize msg to indicate exception origin
1099
+ raise KeyError(f"The edge {e} is not in the graph.")
1100
+
1101
+ # EdgeDataView methods
1102
+ def __call__(self, nbunch=None, data=False, *, default=None):
1103
+ if nbunch is None and data is False:
1104
+ return self
1105
+ return self.dataview(self, nbunch, data, default=default)
1106
+
1107
+ def data(self, data=True, default=None, nbunch=None):
1108
+ """
1109
+ Return a read-only view of edge data.
1110
+
1111
+ Parameters
1112
+ ----------
1113
+ data : bool or edge attribute key
1114
+ If ``data=True``, then the data view maps each edge to a dictionary
1115
+ containing all of its attributes. If `data` is a key in the edge
1116
+ dictionary, then the data view maps each edge to its value for
1117
+ the keyed attribute. In this case, if the edge doesn't have the
1118
+ attribute, the `default` value is returned.
1119
+ default : object, default=None
1120
+ The value used when an edge does not have a specific attribute
1121
+ nbunch : container of nodes, optional (default=None)
1122
+ Allows restriction to edges only involving certain nodes. All edges
1123
+ are considered by default.
1124
+
1125
+ Returns
1126
+ -------
1127
+ dataview
1128
+ Returns an `EdgeDataView` for undirected Graphs, `OutEdgeDataView`
1129
+ for DiGraphs, `MultiEdgeDataView` for MultiGraphs and
1130
+ `OutMultiEdgeDataView` for MultiDiGraphs.
1131
+
1132
+ Notes
1133
+ -----
1134
+ If ``data=False``, returns an `EdgeView` without any edge data.
1135
+
1136
+ See Also
1137
+ --------
1138
+ EdgeDataView
1139
+ OutEdgeDataView
1140
+ MultiEdgeDataView
1141
+ OutMultiEdgeDataView
1142
+
1143
+ Examples
1144
+ --------
1145
+ >>> G = nx.Graph()
1146
+ >>> G.add_edges_from(
1147
+ ... [
1148
+ ... (0, 1, {"dist": 3, "capacity": 20}),
1149
+ ... (1, 2, {"dist": 4}),
1150
+ ... (2, 0, {"dist": 5}),
1151
+ ... ]
1152
+ ... )
1153
+
1154
+ Accessing edge data with ``data=True`` (the default) returns an
1155
+ edge data view object listing each edge with all of its attributes:
1156
+
1157
+ >>> G.edges.data()
1158
+ EdgeDataView([(0, 1, {'dist': 3, 'capacity': 20}), (0, 2, {'dist': 5}), (1, 2, {'dist': 4})])
1159
+
1160
+ If `data` represents a key in the edge attribute dict, a dataview listing
1161
+ each edge with its value for that specific key is returned:
1162
+
1163
+ >>> G.edges.data("dist")
1164
+ EdgeDataView([(0, 1, 3), (0, 2, 5), (1, 2, 4)])
1165
+
1166
+ `nbunch` can be used to limit the edges:
1167
+
1168
+ >>> G.edges.data("dist", nbunch=[0])
1169
+ EdgeDataView([(0, 1, 3), (0, 2, 5)])
1170
+
1171
+ If a specific key is not found in an edge attribute dict, the value
1172
+ specified by `default` is used:
1173
+
1174
+ >>> G.edges.data("capacity")
1175
+ EdgeDataView([(0, 1, 20), (0, 2, None), (1, 2, None)])
1176
+
1177
+ Note that there is no check that the `data` key is present in any of
1178
+ the edge attribute dictionaries:
1179
+
1180
+ >>> G.edges.data("speed")
1181
+ EdgeDataView([(0, 1, None), (0, 2, None), (1, 2, None)])
1182
+ """
1183
+ if nbunch is None and data is False:
1184
+ return self
1185
+ return self.dataview(self, nbunch, data, default=default)
1186
+
1187
+ # String Methods
1188
+ def __str__(self):
1189
+ return str(list(self))
1190
+
1191
+ def __repr__(self):
1192
+ return f"{self.__class__.__name__}({list(self)})"
1193
+
1194
+
1195
+ class EdgeView(OutEdgeView):
1196
+ """A EdgeView class for edges of a Graph
1197
+
1198
+ This densely packed View allows iteration over edges, data lookup
1199
+ like a dict and set operations on edges represented by node-tuples.
1200
+ In addition, edge data can be controlled by calling this object
1201
+ possibly creating an EdgeDataView. Typically edges are iterated over
1202
+ and reported as `(u, v)` node tuples or `(u, v, key)` node/key tuples
1203
+ for multigraphs. Those edge representations can also be using to
1204
+ lookup the data dict for any edge. Set operations also are available
1205
+ where those tuples are the elements of the set.
1206
+ Calling this object with optional arguments `data`, `default` and `keys`
1207
+ controls the form of the tuple (see EdgeDataView). Optional argument
1208
+ `nbunch` allows restriction to edges only involving certain nodes.
1209
+
1210
+ If `data is False` (the default) then iterate over 2-tuples `(u, v)`.
1211
+ If `data is True` iterate over 3-tuples `(u, v, datadict)`.
1212
+ Otherwise iterate over `(u, v, datadict.get(data, default))`.
1213
+ For Multigraphs, if `keys is True`, replace `u, v` with `u, v, key` above.
1214
+
1215
+ Parameters
1216
+ ==========
1217
+ graph : NetworkX graph-like class
1218
+ nbunch : (default= all nodes in graph) only report edges with these nodes
1219
+ keys : (only for MultiGraph. default=False) report edge key in tuple
1220
+ data : bool or string (default=False) see above
1221
+ default : object (default=None)
1222
+
1223
+ Examples
1224
+ ========
1225
+ >>> G = nx.path_graph(4)
1226
+ >>> EV = G.edges()
1227
+ >>> (2, 3) in EV
1228
+ True
1229
+ >>> for u, v in EV:
1230
+ ... print((u, v))
1231
+ (0, 1)
1232
+ (1, 2)
1233
+ (2, 3)
1234
+ >>> assert EV & {(1, 2), (3, 4)} == {(1, 2)}
1235
+
1236
+ >>> EVdata = G.edges(data="color", default="aqua")
1237
+ >>> G.add_edge(2, 3, color="blue")
1238
+ >>> assert (2, 3, "blue") in EVdata
1239
+ >>> for u, v, c in EVdata:
1240
+ ... print(f"({u}, {v}) has color: {c}")
1241
+ (0, 1) has color: aqua
1242
+ (1, 2) has color: aqua
1243
+ (2, 3) has color: blue
1244
+
1245
+ >>> EVnbunch = G.edges(nbunch=2)
1246
+ >>> assert (2, 3) in EVnbunch
1247
+ >>> assert (0, 1) not in EVnbunch
1248
+ >>> for u, v in EVnbunch:
1249
+ ... assert u == 2 or v == 2
1250
+
1251
+ >>> MG = nx.path_graph(4, create_using=nx.MultiGraph)
1252
+ >>> EVmulti = MG.edges(keys=True)
1253
+ >>> (2, 3, 0) in EVmulti
1254
+ True
1255
+ >>> (2, 3) in EVmulti # 2-tuples work even when keys is True
1256
+ True
1257
+ >>> key = MG.add_edge(2, 3)
1258
+ >>> for u, v, k in EVmulti:
1259
+ ... print((u, v, k))
1260
+ (0, 1, 0)
1261
+ (1, 2, 0)
1262
+ (2, 3, 0)
1263
+ (2, 3, 1)
1264
+ """
1265
+
1266
+ __slots__ = ()
1267
+
1268
+ dataview = EdgeDataView
1269
+
1270
+ def __len__(self):
1271
+ num_nbrs = (len(nbrs) + (n in nbrs) for n, nbrs in self._nodes_nbrs())
1272
+ return sum(num_nbrs) // 2
1273
+
1274
+ def __iter__(self):
1275
+ seen = {}
1276
+ for n, nbrs in self._nodes_nbrs():
1277
+ for nbr in list(nbrs):
1278
+ if nbr not in seen:
1279
+ yield (n, nbr)
1280
+ seen[n] = 1
1281
+ del seen
1282
+
1283
+ def __contains__(self, e):
1284
+ try:
1285
+ u, v = e[:2]
1286
+ return v in self._adjdict[u] or u in self._adjdict[v]
1287
+ except (KeyError, ValueError):
1288
+ return False
1289
+
1290
+
1291
+ class InEdgeView(OutEdgeView):
1292
+ """A EdgeView class for inward edges of a DiGraph"""
1293
+
1294
+ __slots__ = ()
1295
+
1296
+ def __setstate__(self, state):
1297
+ self._graph = state["_graph"]
1298
+ self._adjdict = state["_adjdict"]
1299
+ self._nodes_nbrs = self._adjdict.items
1300
+
1301
+ dataview = InEdgeDataView
1302
+
1303
+ def __init__(self, G):
1304
+ self._graph = G
1305
+ self._adjdict = G._pred if hasattr(G, "pred") else G._adj
1306
+ self._nodes_nbrs = self._adjdict.items
1307
+
1308
+ def __iter__(self):
1309
+ for n, nbrs in self._nodes_nbrs():
1310
+ for nbr in nbrs:
1311
+ yield (nbr, n)
1312
+
1313
+ def __contains__(self, e):
1314
+ try:
1315
+ u, v = e
1316
+ return u in self._adjdict[v]
1317
+ except KeyError:
1318
+ return False
1319
+
1320
+ def __getitem__(self, e):
1321
+ if isinstance(e, slice):
1322
+ raise nx.NetworkXError(
1323
+ f"{type(self).__name__} does not support slicing, "
1324
+ f"try list(G.in_edges)[{e.start}:{e.stop}:{e.step}]"
1325
+ )
1326
+ u, v = e
1327
+ return self._adjdict[v][u]
1328
+
1329
+
1330
+ class OutMultiEdgeView(OutEdgeView):
1331
+ """A EdgeView class for outward edges of a MultiDiGraph"""
1332
+
1333
+ __slots__ = ()
1334
+
1335
+ dataview = OutMultiEdgeDataView
1336
+
1337
+ def __len__(self):
1338
+ return sum(
1339
+ len(kdict) for n, nbrs in self._nodes_nbrs() for nbr, kdict in nbrs.items()
1340
+ )
1341
+
1342
+ def __iter__(self):
1343
+ for n, nbrs in self._nodes_nbrs():
1344
+ for nbr, kdict in nbrs.items():
1345
+ for key in kdict:
1346
+ yield (n, nbr, key)
1347
+
1348
+ def __contains__(self, e):
1349
+ N = len(e)
1350
+ if N == 3:
1351
+ u, v, k = e
1352
+ elif N == 2:
1353
+ u, v = e
1354
+ k = 0
1355
+ else:
1356
+ raise ValueError("MultiEdge must have length 2 or 3")
1357
+ try:
1358
+ return k in self._adjdict[u][v]
1359
+ except KeyError:
1360
+ return False
1361
+
1362
+ def __getitem__(self, e):
1363
+ if isinstance(e, slice):
1364
+ raise nx.NetworkXError(
1365
+ f"{type(self).__name__} does not support slicing, "
1366
+ f"try list(G.edges)[{e.start}:{e.stop}:{e.step}]"
1367
+ )
1368
+ u, v, k = e
1369
+ return self._adjdict[u][v][k]
1370
+
1371
+ def __call__(self, nbunch=None, data=False, *, default=None, keys=False):
1372
+ if nbunch is None and data is False and keys is True:
1373
+ return self
1374
+ return self.dataview(self, nbunch, data, default=default, keys=keys)
1375
+
1376
+ def data(self, data=True, default=None, nbunch=None, keys=False):
1377
+ if nbunch is None and data is False and keys is True:
1378
+ return self
1379
+ return self.dataview(self, nbunch, data, default=default, keys=keys)
1380
+
1381
+
1382
+ class MultiEdgeView(OutMultiEdgeView):
1383
+ """A EdgeView class for edges of a MultiGraph"""
1384
+
1385
+ __slots__ = ()
1386
+
1387
+ dataview = MultiEdgeDataView
1388
+
1389
+ def __len__(self):
1390
+ return sum(1 for e in self)
1391
+
1392
+ def __iter__(self):
1393
+ seen = {}
1394
+ for n, nbrs in self._nodes_nbrs():
1395
+ for nbr, kd in nbrs.items():
1396
+ if nbr not in seen:
1397
+ for k, dd in kd.items():
1398
+ yield (n, nbr, k)
1399
+ seen[n] = 1
1400
+ del seen
1401
+
1402
+
1403
+ class InMultiEdgeView(OutMultiEdgeView):
1404
+ """A EdgeView class for inward edges of a MultiDiGraph"""
1405
+
1406
+ __slots__ = ()
1407
+
1408
+ def __setstate__(self, state):
1409
+ self._graph = state["_graph"]
1410
+ self._adjdict = state["_adjdict"]
1411
+ self._nodes_nbrs = self._adjdict.items
1412
+
1413
+ dataview = InMultiEdgeDataView
1414
+
1415
+ def __init__(self, G):
1416
+ self._graph = G
1417
+ self._adjdict = G._pred if hasattr(G, "pred") else G._adj
1418
+ self._nodes_nbrs = self._adjdict.items
1419
+
1420
+ def __iter__(self):
1421
+ for n, nbrs in self._nodes_nbrs():
1422
+ for nbr, kdict in nbrs.items():
1423
+ for key in kdict:
1424
+ yield (nbr, n, key)
1425
+
1426
+ def __contains__(self, e):
1427
+ N = len(e)
1428
+ if N == 3:
1429
+ u, v, k = e
1430
+ elif N == 2:
1431
+ u, v = e
1432
+ k = 0
1433
+ else:
1434
+ raise ValueError("MultiEdge must have length 2 or 3")
1435
+ try:
1436
+ return k in self._adjdict[v][u]
1437
+ except KeyError:
1438
+ return False
1439
+
1440
+ def __getitem__(self, e):
1441
+ if isinstance(e, slice):
1442
+ raise nx.NetworkXError(
1443
+ f"{type(self).__name__} does not support slicing, "
1444
+ f"try list(G.in_edges)[{e.start}:{e.stop}:{e.step}]"
1445
+ )
1446
+ u, v, k = e
1447
+ return self._adjdict[v][u][k]
lib/python3.12/site-packages/networkx/classes/tests/__init__.py ADDED
File without changes
lib/python3.12/site-packages/networkx/classes/tests/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (201 Bytes). View file
 
lib/python3.12/site-packages/networkx/classes/tests/__pycache__/dispatch_interface.cpython-312.pyc ADDED
Binary file (9.42 kB). View file
 
lib/python3.12/site-packages/networkx/classes/tests/__pycache__/historical_tests.cpython-312.pyc ADDED
Binary file (27.6 kB). View file
 
lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_coreviews.cpython-312.pyc ADDED
Binary file (27.6 kB). View file
 
lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_digraph.cpython-312.pyc ADDED
Binary file (25.9 kB). View file
 
lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_digraph_historical.cpython-312.pyc ADDED
Binary file (8.71 kB). View file
 
lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_filters.cpython-312.pyc ADDED
Binary file (10.8 kB). View file
 
lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_function.cpython-312.pyc ADDED
Binary file (62 kB). View file
 
lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_graph.cpython-312.pyc ADDED
Binary file (62.3 kB). View file
 
lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_graph_historical.cpython-312.pyc ADDED
Binary file (832 Bytes). View file