Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- lib/python3.12/site-packages/blinker-1.9.0.dist-info/INSTALLER +1 -0
- lib/python3.12/site-packages/blinker-1.9.0.dist-info/LICENSE.txt +20 -0
- lib/python3.12/site-packages/blinker-1.9.0.dist-info/METADATA +60 -0
- lib/python3.12/site-packages/blinker-1.9.0.dist-info/RECORD +12 -0
- lib/python3.12/site-packages/blinker-1.9.0.dist-info/WHEEL +4 -0
- lib/python3.12/site-packages/executing-2.2.1.dist-info/INSTALLER +1 -0
- lib/python3.12/site-packages/executing-2.2.1.dist-info/LICENSE.txt +21 -0
- lib/python3.12/site-packages/executing-2.2.1.dist-info/METADATA +171 -0
- lib/python3.12/site-packages/executing-2.2.1.dist-info/RECORD +21 -0
- lib/python3.12/site-packages/executing-2.2.1.dist-info/WHEEL +6 -0
- lib/python3.12/site-packages/executing-2.2.1.dist-info/top_level.txt +1 -0
- lib/python3.12/site-packages/networkx/__init__.py +62 -0
- lib/python3.12/site-packages/networkx/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/__pycache__/conftest.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/__pycache__/convert.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/__pycache__/convert_matrix.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/__pycache__/exception.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/__pycache__/lazy_imports.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/__pycache__/relabel.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/__init__.py +13 -0
- lib/python3.12/site-packages/networkx/classes/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/__pycache__/coreviews.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/__pycache__/digraph.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/__pycache__/filters.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/__pycache__/function.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/__pycache__/graph.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/__pycache__/graphviews.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/__pycache__/multidigraph.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/__pycache__/multigraph.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/__pycache__/reportviews.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/coreviews.py +435 -0
- lib/python3.12/site-packages/networkx/classes/digraph.py +1363 -0
- lib/python3.12/site-packages/networkx/classes/filters.py +95 -0
- lib/python3.12/site-packages/networkx/classes/function.py +1549 -0
- lib/python3.12/site-packages/networkx/classes/graph.py +2082 -0
- lib/python3.12/site-packages/networkx/classes/graphviews.py +269 -0
- lib/python3.12/site-packages/networkx/classes/multidigraph.py +977 -0
- lib/python3.12/site-packages/networkx/classes/multigraph.py +1294 -0
- lib/python3.12/site-packages/networkx/classes/reportviews.py +1447 -0
- lib/python3.12/site-packages/networkx/classes/tests/__init__.py +0 -0
- lib/python3.12/site-packages/networkx/classes/tests/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/tests/__pycache__/dispatch_interface.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/tests/__pycache__/historical_tests.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_coreviews.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_digraph.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_digraph_historical.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_filters.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_function.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/networkx/classes/tests/__pycache__/test_graph.cpython-312.pyc +0 -0
- 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 |
+
[](https://github.com/alexmojaki/executing/actions) [](https://coveralls.io/github/alexmojaki/executing?branch=master) [](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
|
|
|