From b164b5b94f431ad710fb08949ae5c28e015acc94 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 11:24:06 -0400 Subject: [PATCH 01/38] Bump priority-queue from 2.1.0 to 2.1.1 (#1285) Bumps [priority-queue](https://github.com/garro95/priority-queue) from 2.1.0 to 2.1.1. - [Release notes](https://github.com/garro95/priority-queue/releases) - [Commits](https://github.com/garro95/priority-queue/compare/2.1.0...2.1.1) --- updated-dependencies: - dependency-name: priority-queue dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa6dfba7ba..757bb73f8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -413,9 +413,9 @@ dependencies = [ [[package]] name = "priority-queue" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560bcab673ff7f6ca9e270c17bf3affd8a05e3bd9207f123b0d45076fd8197e8" +checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d" dependencies = [ "autocfg", "equivalent", From e9e49f0c0390f5d6697dd5e544c8816b47b892e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 13:03:53 -0400 Subject: [PATCH 02/38] Bump quick-xml from 0.36.1 to 0.36.2 (#1286) Bumps [quick-xml](https://github.com/tafia/quick-xml) from 0.36.1 to 0.36.2. - [Release notes](https://github.com/tafia/quick-xml/releases) - [Changelog](https://github.com/tafia/quick-xml/blob/master/Changelog.md) - [Commits](https://github.com/tafia/quick-xml/compare/v0.36.1...v0.36.2) --- updated-dependencies: - dependency-name: quick-xml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 757bb73f8b..5e300a30e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -500,9 +500,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.36.1" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a05e2e8efddfa51a84ca47cec303fac86c8541b686d37cac5efc0e094417bc" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" dependencies = [ "memchr", ] From 6096c8f4c8900a58cbf9fdfb4e6ec8d0be8b76fa Mon Sep 17 00:00:00 2001 From: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com> Date: Mon, 30 Sep 2024 18:28:50 -0400 Subject: [PATCH 03/38] Fix `node_link_json` type annotation (#1247) * Fix node_link_json type annotations * Add release notes * Try to fix link * Try to fix merge queue hanging --- .github/workflows/main.yml | 1 + .../notes/fix-node-link-json-stubs-eb745078ff1b9b8a.yaml | 6 ++++++ rustworkx/__init__.pyi | 4 ++-- rustworkx/rustworkx.pyi | 8 ++++---- 4 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/fix-node-link-json-stubs-eb745078ff1b9b8a.yaml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 54408a314c..d1797fe743 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,6 +5,7 @@ on: branches: [ main, 'stable/*' ] pull_request: branches: [ main, 'stable/*' ] + merge_group: concurrency: group: ${{ github.repository }}-${{ github.ref }}-${{ github.head_ref }} cancel-in-progress: true diff --git a/releasenotes/notes/fix-node-link-json-stubs-eb745078ff1b9b8a.yaml b/releasenotes/notes/fix-node-link-json-stubs-eb745078ff1b9b8a.yaml new file mode 100644 index 0000000000..75eafe603d --- /dev/null +++ b/releasenotes/notes/fix-node-link-json-stubs-eb745078ff1b9b8a.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixed a bug in the type hint for :func:`~rustworkx.node_link_json`. + Refer to `issue 1243 `__ for + more information. \ No newline at end of file diff --git a/rustworkx/__init__.pyi b/rustworkx/__init__.pyi index 390eef8cc7..754c02f60c 100644 --- a/rustworkx/__init__.pyi +++ b/rustworkx/__init__.pyi @@ -602,8 +602,8 @@ def node_link_json( graph: PyGraph[_S, _T] | PyDiGraph[_S, _T], path: str | None = ..., graph_attrs: Callable[[Any], dict[str, str]] | None = ..., - node_attrs: Callable[[_S], str] | None = ..., - edge_attrs: Callable[[_T], str] | None = ..., + node_attrs: Callable[[_S], dict[str, str]] | None = ..., + edge_attrs: Callable[[_T], dict[str, str]] | None = ..., ) -> str | None: ... def longest_simple_path(graph: PyGraph[_S, _T] | PyDiGraph[_S, _T]) -> NodeIndices | None: ... def isolates(graph: PyGraph[_S, _T] | PyDiGraph[_S, _T]) -> NodeIndices: ... diff --git a/rustworkx/rustworkx.pyi b/rustworkx/rustworkx.pyi index 37b3f0c5eb..474472cf0e 100644 --- a/rustworkx/rustworkx.pyi +++ b/rustworkx/rustworkx.pyi @@ -634,16 +634,16 @@ def digraph_node_link_json( /, path: str | None = ..., graph_attrs: Callable[[Any], dict[str, str]] | None = ..., - node_attrs: Callable[[_S], str] | None = ..., - edge_attrs: Callable[[_T], str] | None = ..., + node_attrs: Callable[[_S], dict[str, str]] | None = ..., + edge_attrs: Callable[[_T], dict[str, str]] | None = ..., ) -> str | None: ... def graph_node_link_json( graph: PyGraph[_S, _T], /, path: str | None = ..., graph_attrs: Callable[[Any], dict[str, str]] | None = ..., - node_attrs: Callable[[_S], str] | None = ..., - edge_attrs: Callable[[_T], str] | None = ..., + node_attrs: Callable[[_S], dict[str, str]] | None = ..., + edge_attrs: Callable[[_T], dict[str, str]] | None = ..., ) -> str | None: ... def parse_node_link_json( data: str, From 327b089841f0e8c67f17c4ac4549d4439e3e6019 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 23:21:42 +0000 Subject: [PATCH 04/38] Bump indexmap from 2.5.0 to 2.6.0 (#1288) * Bump indexmap from 2.5.0 to 2.6.0 Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.5.0 to 2.6.0. - [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md) - [Commits](https://github.com/indexmap-rs/indexmap/compare/2.5.0...2.6.0) --- updated-dependencies: - dependency-name: indexmap dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Fix indexmap bump --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ivan Carvalho --- Cargo.lock | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5e300a30e8..7de789d507 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,6 +136,12 @@ dependencies = [ "rayon", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "heck" version = "0.4.1" @@ -160,12 +166,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.0", "rayon", ] @@ -393,7 +399,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.5.0", + "indexmap 2.6.0", ] [[package]] @@ -419,7 +425,7 @@ checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d" dependencies = [ "autocfg", "equivalent", - "indexmap 2.5.0", + "indexmap 2.6.0", ] [[package]] @@ -439,7 +445,7 @@ checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8" dependencies = [ "cfg-if", "hashbrown 0.14.5", - "indexmap 2.5.0", + "indexmap 2.6.0", "indoc", "libc", "memoffset", @@ -614,7 +620,7 @@ dependencies = [ "ahash", "fixedbitset", "hashbrown 0.14.5", - "indexmap 2.5.0", + "indexmap 2.6.0", "ndarray", "ndarray-stats", "num-bigint", @@ -641,7 +647,7 @@ dependencies = [ "ahash", "fixedbitset", "hashbrown 0.14.5", - "indexmap 2.5.0", + "indexmap 2.6.0", "ndarray", "num-traits", "petgraph", From c223d21edb1643d89257c2d8a5450b952554db75 Mon Sep 17 00:00:00 2001 From: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com> Date: Thu, 3 Oct 2024 21:54:37 -0400 Subject: [PATCH 05/38] Remove support for Python 3.8 (#1290) * Remove support for Python 3.8 (EOL) * Add release note * Update black to support newer Python syntaxes * Update mypy as well * More fixes * Bump ruff as well * Fix lint finds --- .github/workflows/docs_dev.yml | 2 +- .github/workflows/docs_release.yml | 2 +- .github/workflows/main.yml | 16 +++++++--------- .github/workflows/wheels.yml | 4 ++-- .mergify.yml | 3 --- Cargo.toml | 2 +- constraints.txt | 1 - noxfile.py | 10 +++++----- pyproject.toml | 8 ++++---- .../notes/py38-eol-6443a548b6c727cc.yaml | 6 ++++++ rustworkx/__init__.pyi | 3 ++- rustworkx/generators/__init__.pyi | 3 ++- rustworkx/rustworkx.pyi | 10 ++++++---- rustworkx/visualization/matplotlib.pyi | 18 ++++++++++++++---- setup.py | 5 ++--- tests/graph/test_max_weight_matching.py | 8 ++++---- tests/graph/test_planar.py | 4 ++-- tests/test_graphml.py | 2 +- tests/test_token_swapper.py | 2 +- tests/visualization/test_mpl.py | 2 +- tox.ini | 6 +++--- 21 files changed, 65 insertions(+), 52 deletions(-) create mode 100644 releasenotes/notes/py38-eol-6443a548b6c727cc.yaml diff --git a/.github/workflows/docs_dev.yml b/.github/workflows/docs_dev.yml index a253cc46b0..1d69e2e374 100644 --- a/.github/workflows/docs_dev.yml +++ b/.github/workflows/docs_dev.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.8' + python-version: '3.10' - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/docs_release.yml b/.github/workflows/docs_release.yml index 843ed6743d..013e4a6c77 100644 --- a/.github/workflows/docs_release.yml +++ b/.github/workflows/docs_release.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.8' + python-version: '3.10' - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d1797fe743..907c964e17 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,8 +25,8 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: 3.8 - - run: pip install -U ruff==0.4.1 black~=22.0 + python-version: "3.10" + - run: pip install -U ruff==0.6.8 black~=24.8 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt @@ -58,7 +58,7 @@ jobs: strategy: matrix: rust: [stable] - python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] + python-version: [3.9, "3.10", "3.11", "3.12"] platform: [ { os: "macOS-13", python-architecture: "x64", rust-target: "x86_64-apple-darwin" }, { os: "macOS-14", python-architecture: "arm64", rust-target: "aarch64-apple-darwin" }, @@ -68,7 +68,7 @@ jobs: include: # Test minimal supported Rust version - rust: 1.70.0 - python-version: 3.8 + python-version: "3.10" platform: { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu" } msrv: "MSRV" # Test future versions of Rust and Python @@ -76,10 +76,8 @@ jobs: python-version: "3.13-dev" platform: { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu" } msrv: "Beta" - # Exclude python 3.8 and 3.9 on arm64 until actions/setup-python#808 is resolved + # Exclude python 3.9 on arm64 until actions/setup-python#808 is resolved exclude: - - platform: {os: "macOS-14", python-architecture: "arm64", rust-target: "aarch64-apple-darwin" } - python-version: 3.8 - platform: {os: "macOS-14", python-architecture: "arm64", rust-target: "aarch64-apple-darwin" } python-version: 3.9 steps: @@ -109,7 +107,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.8, 3.9, "3.10", "3.11"] + python-version: [3.9, "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} @@ -179,7 +177,7 @@ jobs: - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: "3.10" - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Install binary deps diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index b14256497c..5b1a9e4636 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -149,8 +149,8 @@ jobs: python -m cibuildwheel --output-dir wheelhouse env: CIBW_ARCHS_LINUX: aarch64 - CIBW_SKIP: cp36-* cp37-* *many* - CIBW_TEST_SKIP: cp37-* cp38-* cp39-* cp310-* cp311-* cp312-* *many* + CIBW_SKIP: cp36-* cp37-* cp38-* *many* + CIBW_TEST_SKIP: cp39-* cp310-* cp311-* cp312-* *many* - uses: actions/upload-artifact@v4 with: path: ./wheelhouse/*.whl diff --git a/.mergify.yml b/.mergify.yml index 254ce2477b..3438adc735 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -1,9 +1,6 @@ queue_rules: - name: automerge conditions: - - check-success=python3.8-x64 windows-latest - - check-success=python3.8-x64 ubuntu-latest - - check-success=python3.8-x64 macOS-latest - check-success=python3.9-x64 windows-latest - check-success=python3.9-x64 ubuntu-latest - check-success=python3.9-x64 macOS-latest diff --git a/Cargo.toml b/Cargo.toml index 6c75c894d2..667d17de7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ rustworkx-core = { path = "rustworkx-core", version = "=0.16.0" } [dependencies.pyo3] version = "0.21.2" -features = ["abi3-py38", "extension-module", "hashbrown", "num-bigint", "num-complex", "indexmap"] +features = ["abi3-py39", "extension-module", "hashbrown", "num-bigint", "num-complex", "indexmap"] [dependencies.sprs] version = "^0.11" diff --git a/constraints.txt b/constraints.txt index 623a9cc4f0..4927e21b09 100644 --- a/constraints.txt +++ b/constraints.txt @@ -1,4 +1,3 @@ decorator==4.4.2 -importlib-metadata==4.13.0;python_version<'3.8' pillow<10.0.0;python_version<'3.13' lxml==5.1.1 diff --git a/noxfile.py b/noxfile.py index 4a2b757139..1b62212542 100644 --- a/noxfile.py +++ b/noxfile.py @@ -12,19 +12,19 @@ ] lint_deps = [ - "black~=22.0", - "ruff~=0.1", + "black~=24.8", + "ruff~=0.6", "setuptools-rust", ] stubs_deps = [ - "mypy==1.8.0", + "mypy==1.11.2", "typing-extensions", ] def install_rustworkx(session): session.install(*deps) - session.install(".[all]", "-c", "constraints.txt") + session.install(".", "-c", "constraints.txt") # We define a common base such that -e test triggers a test with the current # Python version of the interpreter and -e test_with_version launches @@ -38,7 +38,7 @@ def base_test(session): def test(session): base_test(session) -@nox.session(python=["3.8", "3.9", "3.10", "3.11", "3.12"]) +@nox.session(python=["3.9", "3.10", "3.11", "3.12"]) def test_with_version(session): base_test(session) diff --git a/pyproject.toml b/pyproject.toml index a7da3f16cc..2a752ec1be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [tool.black] line-length = 100 -target-version = ['py38', 'py39', 'py310', 'py311'] +target-version = ['py39', 'py310', 'py311', 'py312'] [tool.ruff] line-length = 105 # more lenient than black due to long function signatures @@ -16,7 +16,7 @@ lint.select = [ "PYI", # flake8-pyi "Q", # flake8-quotes ] -target-version = "py38" +target-version = "py39" extend-exclude = ["doc"] [tool.ruff.lint.per-file-ignores] @@ -26,11 +26,11 @@ extend-exclude = ["doc"] [tool.cibuildwheel] manylinux-x86_64-image = "manylinux2014" manylinux-i686-image = "manylinux2014" -skip = "pp* cp36-* cp37-* *win32 *musllinux*i686" +skip = "pp* cp36-* cp37-* cp38-* *win32 *musllinux*i686" test-requires = "networkx" test-command = "python -m unittest discover {project}/tests" before-build = "pip install -U setuptools-rust" -test-skip = "cp38-*musllinux* *linux_s390x *ppc64le" +test-skip = "*linux_s390x *ppc64le" [tool.cibuildwheel.linux] before-all = "yum install -y wget && {package}/tools/install_rust.sh" diff --git a/releasenotes/notes/py38-eol-6443a548b6c727cc.yaml b/releasenotes/notes/py38-eol-6443a548b6c727cc.yaml new file mode 100644 index 0000000000..d4e736808f --- /dev/null +++ b/releasenotes/notes/py38-eol-6443a548b6c727cc.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + The minimum supported Python version for using rustworkx has been raised to Python 3.9. + Python 3.8 has reached it's end-of-life and will no longer be supported. To use rustworkx + you will need to ensure you are using Python >=3.9. \ No newline at end of file diff --git a/rustworkx/__init__.pyi b/rustworkx/__init__.pyi index 754c02f60c..63f5f4be4b 100644 --- a/rustworkx/__init__.pyi +++ b/rustworkx/__init__.pyi @@ -11,7 +11,8 @@ import numpy as np -from typing import Generic, TypeVar, Any, Callable, Iterator, overload, Sequence +from typing import Generic, TypeVar, Any, Callable, overload +from collections.abc import Iterator, Sequence # Re-Exports of rust native functions in rustworkx.rustworkx # To workaround limitations in mypy around re-exporting objects from the inner diff --git a/rustworkx/generators/__init__.pyi b/rustworkx/generators/__init__.pyi index 99946bed87..440db39407 100644 --- a/rustworkx/generators/__init__.pyi +++ b/rustworkx/generators/__init__.pyi @@ -12,7 +12,8 @@ from rustworkx import PyGraph from rustworkx import PyDiGraph -from typing import Sequence, Any +from typing import Any +from collections.abc import Sequence def cycle_graph( num_nodes: int | None = ..., weights: Sequence[Any] | None = ..., multigraph: bool = ... diff --git a/rustworkx/rustworkx.pyi b/rustworkx/rustworkx.pyi index 474472cf0e..d0be41c5a1 100644 --- a/rustworkx/rustworkx.pyi +++ b/rustworkx/rustworkx.pyi @@ -13,17 +13,19 @@ from .visit import BFSVisitor, DFSVisitor, DijkstraVisitor from typing import ( TypeVar, Callable, - Iterable, - Iterator, final, - Sequence, Any, Generic, + overload, +) +from collections.abc import ( + Iterable, + Iterator, + Sequence, ItemsView, KeysView, ValuesView, Mapping, - overload, Hashable, ) from abc import ABC diff --git a/rustworkx/visualization/matplotlib.pyi b/rustworkx/visualization/matplotlib.pyi index 3c5625d7f8..3f8da1fd17 100644 --- a/rustworkx/visualization/matplotlib.pyi +++ b/rustworkx/visualization/matplotlib.pyi @@ -26,18 +26,28 @@ class _DrawKwargs(typing.TypedDict, total=False): node_list: list[int] edge_list: list[int] node_size: int | list[int] - node_color: str | tuple[float, float, float] | tuple[float, float, float, float] | list[ + node_color: ( str - ] | list[tuple[float, float, float]] | list[tuple[float, float, float, float]] + | tuple[float, float, float] + | tuple[float, float, float, float] + | list[str] + | list[tuple[float, float, float]] + | list[tuple[float, float, float, float]] + ) node_shape: str alpha: float cmap: Colormap vmin: float vmax: float linewidths: float | list[float] - edge_color: str | tuple[float, float, float] | tuple[float, float, float, float] | list[ + edge_color: ( str - ] | list[tuple[float, float, float]] | list[tuple[float, float, float, float]] + | tuple[float, float, float] + | tuple[float, float, float, float] + | list[str] + | list[tuple[float, float, float]] + | list[tuple[float, float, float, float]] + ) edge_cmap: Colormap edge_vmin: float edge_vmax: float diff --git a/setup.py b/setup.py index 12485f05af..16f9dcdec1 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ def readme(): PKG_INSTALL_REQUIRES = ["numpy>=1.16.0,<3"] RUST_EXTENSIONS = [RustExtension("rustworkx.rustworkx", "Cargo.toml", binding=Binding.PyO3, debug=rustworkx_debug)] -RUST_OPTS ={"bdist_wheel": {"py_limited_api": "cp38"}} +RUST_OPTS ={"bdist_wheel": {"py_limited_api": "cp39"}} retworkx_readme_compat = """# retworkx @@ -66,7 +66,6 @@ def readme(): "Intended Audience :: Science/Research", "Programming Language :: Rust", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -86,7 +85,7 @@ def readme(): include_package_data=True, packages=PKG_PACKAGES, zip_safe=False, - python_requires=">=3.8", + python_requires=">=3.9", install_requires=PKG_INSTALL_REQUIRES, extras_require={ "mpl": mpl_extras, diff --git a/tests/graph/test_max_weight_matching.py b/tests/graph/test_max_weight_matching.py index 1b07a2eeb1..06fbbde5eb 100644 --- a/tests/graph/test_max_weight_matching.py +++ b/tests/graph/test_max_weight_matching.py @@ -27,7 +27,7 @@ def match_dict_to_set(match): class TestMaxWeightMatching(unittest.TestCase): def compare_match_sets(self, rx_match, expected_match): - for (u, v) in rx_match: + for u, v in rx_match: if (u, v) not in expected_match and (v, u) not in expected_match: self.fail( f"Element {(u, v)} and it's reverse {(v, u)} not found in " @@ -49,7 +49,7 @@ def get_nx_weight(edge): return weight["weight"] not_match = False - for (u, v) in rx_matches: + for u, v in rx_matches: if (u, v) not in nx_matches: if (v, u) not in nx_matches: not_match = True @@ -57,11 +57,11 @@ def get_nx_weight(edge): if not_match: self.assertTrue( rustworkx.is_matching(rx_graph, rx_matches), - "%s is not a valid matching" % rx_matches, + f"{rx_matches} is not a valid matching", ) self.assertTrue( rustworkx.is_maximal_matching(rx_graph, rx_matches), - "%s is not a maximal matching" % rx_matches, + f"{rx_matches} is not a maximal matching", ) self.assertEqual( sum(map(get_rx_weight, rx_matches)), diff --git a/tests/graph/test_planar.py b/tests/graph/test_planar.py index 92750ad0f8..2cf68b7fae 100644 --- a/tests/graph/test_planar.py +++ b/tests/graph/test_planar.py @@ -266,7 +266,7 @@ def test_generalized_petersen_graph_planar_instances(self): iter((n, 1) for n in range(3, 17)), iter((n, 2) for n in range(6, 17, 2)), ) - for (n, k) in planars: + for n, k in planars: with self.subTest(n=n, k=k): graph = rx.generators.generalized_petersen_graph(n=n, k=k) self.assertTrue(rx.is_planar(graph)) @@ -277,7 +277,7 @@ def test_generalized_petersen_graph_non_planar_instances(self): iter((n, 2) for n in range(5, 17, 2)), iter((n, k) for k in range(3, 9) for n in range(2 * k + 1, 17)), ) - for (n, k) in no_planars: + for n, k in no_planars: with self.subTest(n=n, k=k): graph = rx.generators.generalized_petersen_graph(n=n, k=k) self.assertFalse(rx.is_planar(graph)) diff --git a/tests/test_graphml.py b/tests/test_graphml.py index 5556e12bf6..fee85da4ad 100644 --- a/tests/test_graphml.py +++ b/tests/test_graphml.py @@ -44,7 +44,7 @@ def assertGraphEqual(self, graph, nodes, edges, directed=True, attrs={}): for node_a, node_b in zip(graph.nodes(), nodes): self.assertDictPayloadEqual(node_a, node_b) - for ((s, t, data), edge) in zip(graph.weighted_edge_list(), edges): + for (s, t, data), edge in zip(graph.weighted_edge_list(), edges): self.assertEqual((graph[s]["id"], graph[t]["id"]), (edge[0], edge[1])) self.assertDictPayloadEqual(data, edge[2]) diff --git a/tests/test_token_swapper.py b/tests/test_token_swapper.py index aafc6e6ff0..2fa9b22de5 100644 --- a/tests/test_token_swapper.py +++ b/tests/test_token_swapper.py @@ -21,7 +21,7 @@ def swap_permutation( mapping, swaps, ) -> None: - for (sw1, sw2) in list(swaps): + for sw1, sw2 in list(swaps): val1 = mapping.pop(sw1, None) val2 = mapping.pop(sw2, None) diff --git a/tests/visualization/test_mpl.py b/tests/visualization/test_mpl.py index 2208b4f3fa..705a5b80f3 100644 --- a/tests/visualization/test_mpl.py +++ b/tests/visualization/test_mpl.py @@ -121,7 +121,7 @@ def test_draw_edges_min_source_target_margins(self): min_source_margin=100, min_target_margin=100, ) - _save_images(fig, "test_node_shape_%s.png" % node_shape) + _save_images(fig, f"test_node_shape_{node_shape}.png") def test_alpha_iter(self): graph = rustworkx.generators.grid_graph(4, 6) diff --git a/tox.ini b/tox.ini index 63e661d99a..712139c017 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 4.4.0 -envlist = py37, py38, py39, py310, py311, lint +envlist = py39, py310, py311, lint isolated_build = true [testenv] @@ -33,7 +33,7 @@ commands = basepython = python3 skip_install = true deps = - black~=22.0 + black~=24.8 ruff allowlist_externals=cargo commands = @@ -75,7 +75,7 @@ commands = basepython = python3 skip_install = true deps = - black~=22.0 + black~=24.8 commands = black {posargs} '../rustworkx' '../tests' '../retworkx' python -c "print('\nrustworkx no longer supports tox. Please run the equivalent comand with nox:\n\tnox -e black\n')" From a38ed218266b287bcc42870d2b25fae3805f0d22 Mon Sep 17 00:00:00 2001 From: James Webber Date: Thu, 10 Oct 2024 20:01:32 -0400 Subject: [PATCH 06/38] Allow some graph and digraph methods to take iterables/generators (#1292) * graph and digraph methods can use iterables * ran cargo fmt * linting * remove_edges_from and remove_nodes_from * cleaned up docs * added tests --- rustworkx/rustworkx.pyi | 28 ++++++------- src/digraph.rs | 70 +++++++++++++++++---------------- src/graph.rs | 78 ++++++++++++++++++++----------------- tests/digraph/test_edges.py | 66 +++++++++++++++++++++++++++++++ tests/digraph/test_nodes.py | 18 +++++++++ tests/graph/test_edges.py | 57 +++++++++++++++++++++++++++ tests/graph/test_nodes.py | 20 ++++++++++ 7 files changed, 254 insertions(+), 83 deletions(-) diff --git a/rustworkx/rustworkx.pyi b/rustworkx/rustworkx.pyi index d0be41c5a1..6e690a2c2c 100644 --- a/rustworkx/rustworkx.pyi +++ b/rustworkx/rustworkx.pyi @@ -1153,14 +1153,14 @@ class PyGraph(Generic[_S, _T]): def add_edge(self, node_a: int, node_b: int, edge: _T, /) -> int: ... def add_edges_from( self, - obj_list: Sequence[tuple[int, int, _T]], + obj_list: Iterable[tuple[int, int, _T]], /, ) -> list[int]: ... def add_edges_from_no_data( - self: PyGraph[_S, _T | None], obj_list: Sequence[tuple[int, int]], / + self: PyGraph[_S, _T | None], obj_list: Iterable[tuple[int, int]], / ) -> list[int]: ... def add_node(self, obj: _S, /) -> int: ... - def add_nodes_from(self, obj_list: Sequence[_S], /) -> NodeIndices: ... + def add_nodes_from(self, obj_list: Iterable[_S], /) -> NodeIndices: ... def adj(self, node: int, /) -> dict[int, _T]: ... def clear(self) -> None: ... def clear_edges(self) -> None: ... @@ -1188,11 +1188,11 @@ class PyGraph(Generic[_S, _T]): def edges(self) -> list[_T]: ... def edge_subgraph(self, edge_list: Sequence[tuple[int, int]], /) -> PyGraph[_S, _T]: ... def extend_from_edge_list( - self: PyGraph[_S | None, _T | None], edge_list: Sequence[tuple[int, int]], / + self: PyGraph[_S | None, _T | None], edge_list: Iterable[tuple[int, int]], / ) -> None: ... def extend_from_weighted_edge_list( self: PyGraph[_S | None, _T], - edge_list: Sequence[tuple[int, int, _T]], + edge_list: Iterable[tuple[int, int, _T]], /, ) -> None: ... def filter_edges(self, filter_function: Callable[[_T], bool]) -> EdgeIndices: ... @@ -1238,9 +1238,9 @@ class PyGraph(Generic[_S, _T]): ) -> PyGraph: ... def remove_edge(self, node_a: int, node_b: int, /) -> None: ... def remove_edge_from_index(self, edge: int, /) -> None: ... - def remove_edges_from(self, index_list: Sequence[tuple[int, int]], /) -> None: ... + def remove_edges_from(self, index_list: Iterable[tuple[int, int]], /) -> None: ... def remove_node(self, node: int, /) -> None: ... - def remove_nodes_from(self, index_list: Sequence[int], /) -> None: ... + def remove_nodes_from(self, index_list: Iterable[int], /) -> None: ... def subgraph(self, nodes: Sequence[int], /, preserve_attrs: bool = ...) -> PyGraph[_S, _T]: ... def substitute_node_with_subgraph( self, @@ -1304,14 +1304,14 @@ class PyDiGraph(Generic[_S, _T]): def add_edge(self, parent: int, child: int, edge: _T, /) -> int: ... def add_edges_from( self, - obj_list: Sequence[tuple[int, int, _T]], + obj_list: Iterable[tuple[int, int, _T]], /, ) -> list[int]: ... def add_edges_from_no_data( - self: PyDiGraph[_S, _T | None], obj_list: Sequence[tuple[int, int]], / + self: PyDiGraph[_S, _T | None], obj_list: Iterable[tuple[int, int]], / ) -> list[int]: ... def add_node(self, obj: _S, /) -> int: ... - def add_nodes_from(self, obj_list: Sequence[_S], /) -> NodeIndices: ... + def add_nodes_from(self, obj_list: Iterable[_S], /) -> NodeIndices: ... def add_parent(self, child: int, obj: _S, edge: _T, /) -> int: ... def adj(self, node: int, /) -> dict[int, _T]: ... def adj_direction(self, node: int, direction: bool, /) -> dict[int, _T]: ... @@ -1341,11 +1341,11 @@ class PyDiGraph(Generic[_S, _T]): def edges(self) -> list[_T]: ... def edge_subgraph(self, edge_list: Sequence[tuple[int, int]], /) -> PyDiGraph[_S, _T]: ... def extend_from_edge_list( - self: PyDiGraph[_S | None, _T | None], edge_list: Sequence[tuple[int, int]], / + self: PyDiGraph[_S | None, _T | None], edge_list: Iterable[tuple[int, int]], / ) -> None: ... def extend_from_weighted_edge_list( self: PyDiGraph[_S | None, _T], - edge_list: Sequence[tuple[int, int, _T]], + edge_list: Iterable[tuple[int, int, _T]], /, ) -> None: ... def filter_edges(self, filter_function: Callable[[_T], bool]) -> EdgeIndices: ... @@ -1413,7 +1413,7 @@ class PyDiGraph(Generic[_S, _T]): ) -> PyDiGraph: ... def remove_edge(self, parent: int, child: int, /) -> None: ... def remove_edge_from_index(self, edge: int, /) -> None: ... - def remove_edges_from(self, index_list: Sequence[tuple[int, int]], /) -> None: ... + def remove_edges_from(self, index_list: Iterable[tuple[int, int]], /) -> None: ... def remove_node(self, node: int, /) -> None: ... def remove_node_retain_edges( self, @@ -1431,7 +1431,7 @@ class PyDiGraph(Generic[_S, _T]): *, use_outgoing: bool = ..., ) -> None: ... - def remove_nodes_from(self, index_list: Sequence[int], /) -> None: ... + def remove_nodes_from(self, index_list: Iterable[int], /) -> None: ... def subgraph( self, nodes: Sequence[int], /, preserve_attrs: bool = ... ) -> PyDiGraph[_S, _T]: ... diff --git a/src/digraph.rs b/src/digraph.rs index b15b3dea06..dd50250802 100644 --- a/src/digraph.rs +++ b/src/digraph.rs @@ -1273,7 +1273,7 @@ impl PyDiGraph { /// Add new edges to the dag. /// - /// :param list obj_list: A list of tuples of the form + /// :param iterable obj_list: An iterable of tuples of the form /// ``(parent, child, obj)`` to attach to the graph. ``parent`` and /// ``child`` are integer indices describing where an edge should be /// added, and obj is the python object for the edge data. @@ -1281,12 +1281,10 @@ impl PyDiGraph { /// :returns: A list of int indices of the newly created edges /// :rtype: list #[pyo3(text_signature = "(self, obj_list, /)")] - pub fn add_edges_from( - &mut self, - obj_list: Vec<(usize, usize, PyObject)>, - ) -> PyResult> { - let mut out_list: Vec = Vec::with_capacity(obj_list.len()); - for obj in obj_list { + pub fn add_edges_from(&mut self, obj_list: Bound<'_, PyAny>) -> PyResult> { + let mut out_list = Vec::new(); + for py_obj in obj_list.iter()? { + let obj = py_obj?.extract::<(usize, usize, PyObject)>()?; let edge = self.add_edge(obj.0, obj.1, obj.2)?; out_list.push(edge); } @@ -1295,7 +1293,7 @@ impl PyDiGraph { /// Add new edges to the dag without python data. /// - /// :param list obj_list: A list of tuples of the form + /// :param iterable obj_list: An iterable of tuples of the form /// ``(parent, child)`` to attach to the graph. ``parent`` and /// ``child`` are integer indices describing where an edge should be /// added. Unlike :meth:`add_edges_from` there is no data payload and @@ -1307,10 +1305,11 @@ impl PyDiGraph { pub fn add_edges_from_no_data( &mut self, py: Python, - obj_list: Vec<(usize, usize)>, + obj_list: Bound<'_, PyAny>, ) -> PyResult> { - let mut out_list: Vec = Vec::with_capacity(obj_list.len()); - for obj in obj_list { + let mut out_list = Vec::new(); + for py_obj in obj_list.iter()? { + let obj = py_obj?.extract::<(usize, usize)>()?; let edge = self.add_edge(obj.0, obj.1, py.None())?; out_list.push(edge); } @@ -1322,7 +1321,7 @@ impl PyDiGraph { /// This method differs from :meth:`add_edges_from_no_data` in that it will /// add nodes if a node index is not present in the edge list. /// - /// :param list edge_list: A list of tuples of the form ``(source, target)`` + /// :param iterable edge_list: An iterable of tuples of the form ``(source, target)`` /// where source and target are integer node indices. If the node index /// is not present in the graph, nodes will be added (with a node /// weight of ``None``) to that index. @@ -1330,9 +1329,10 @@ impl PyDiGraph { pub fn extend_from_edge_list( &mut self, py: Python, - edge_list: Vec<(usize, usize)>, + edge_list: Bound<'_, PyAny>, ) -> PyResult<()> { - for (source, target) in edge_list { + for py_obj in edge_list.iter()? { + let (source, target) = py_obj?.extract::<(usize, usize)>()?; let max_index = cmp::max(source, target); while max_index >= self.node_count() { self.graph.add_node(py.None()); @@ -1347,7 +1347,7 @@ impl PyDiGraph { /// This method differs from :meth:`add_edges_from` in that it will /// add nodes if a node index is not present in the edge list. /// - /// :param list edge_list: A list of tuples of the form + /// :param iterable edge_list: An iterable of tuples of the form /// ``(source, target, weight)`` where source and target are integer /// node indices. If the node index is not present in the graph /// nodes will be added (with a node weight of ``None``) to that index. @@ -1355,9 +1355,10 @@ impl PyDiGraph { pub fn extend_from_weighted_edge_list( &mut self, py: Python, - edge_list: Vec<(usize, usize, PyObject)>, + edge_list: Bound<'_, PyAny>, ) -> PyResult<()> { - for (source, target, weight) in edge_list { + for py_obj in edge_list.iter()? { + let (source, target, weight) = py_obj?.extract::<(usize, usize, PyObject)>()?; let max_index = cmp::max(source, target); while max_index >= self.node_count() { self.graph.add_node(py.None()); @@ -1500,17 +1501,16 @@ impl PyDiGraph { /// Note if there are multiple edges between the specified nodes only one /// will be removed. /// - /// :param list index_list: A list of node index pairs to remove from + /// :param iterable index_list: An iterable of node index pairs to remove from /// the graph /// /// :raises NoEdgeBetweenNodes: If there are no edges between a specified /// pair of nodes. #[pyo3(text_signature = "(self, index_list, /)")] - pub fn remove_edges_from(&mut self, index_list: Vec<(usize, usize)>) -> PyResult<()> { - for (p_index, c_index) in index_list - .iter() - .map(|(x, y)| (NodeIndex::new(*x), NodeIndex::new(*y))) - { + pub fn remove_edges_from(&mut self, index_list: Bound<'_, PyAny>) -> PyResult<()> { + for py_obj in index_list.iter()? { + let (x, y) = py_obj?.extract::<(usize, usize)>()?; + let (p_index, c_index) = (NodeIndex::new(x), NodeIndex::new(y)); let edge_index = match self.graph.find_edge(p_index, c_index) { Some(edge_index) => edge_index, None => return Err(NoEdgeBetweenNodes::new_err("No edge found between nodes")), @@ -1949,18 +1949,19 @@ impl PyDiGraph { /// Add new nodes to the graph. /// - /// :param list obj_list: A list of python objects to attach to the graph + /// :param iterable obj_list: An iterable of python objects to attach to the graph /// as new nodes /// /// :returns: A list of int indices of the newly created nodes /// :rtype: NodeIndices #[pyo3(text_signature = "(self, obj_list, /)")] - pub fn add_nodes_from(&mut self, obj_list: Vec) -> NodeIndices { - let out_list: Vec = obj_list - .into_iter() - .map(|obj| self.graph.add_node(obj).index()) - .collect(); - NodeIndices { nodes: out_list } + pub fn add_nodes_from(&mut self, obj_list: Bound<'_, PyAny>) -> PyResult { + let mut out_list = Vec::new(); + for py_obj in obj_list.iter()? { + let obj = py_obj?.extract::()?; + out_list.push(self.graph.add_node(obj).index()); + } + Ok(NodeIndices { nodes: out_list }) } /// Remove nodes from the graph. @@ -1968,11 +1969,12 @@ impl PyDiGraph { /// If a node index in the list is not present in the graph it will be /// ignored. /// - /// :param list index_list: A list of node indicies to remove from the - /// the graph. + /// :param iterable index_list: An iterable of node indices to remove from the + /// graph. #[pyo3(text_signature = "(self, index_list, /)")] - pub fn remove_nodes_from(&mut self, index_list: Vec) -> PyResult<()> { - for node in index_list { + pub fn remove_nodes_from(&mut self, index_list: Bound<'_, PyAny>) -> PyResult<()> { + for py_obj in index_list.iter()? { + let node = py_obj?.extract::()?; self.remove_node(node)?; } Ok(()) diff --git a/src/graph.rs b/src/graph.rs index c268717f56..41cf02bac8 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -862,7 +862,7 @@ impl PyGraph { /// Add new edges to the graph. /// - /// :param list obj_list: A list of tuples of the form + /// :param iterable obj_list: An iterable of tuples of the form /// ``(node_a, node_b, obj)`` to attach to the graph. ``node_a`` and /// ``node_b`` are integer indices describing where an edge should be /// added, and ``obj`` is the python object for the edge data. @@ -876,12 +876,10 @@ impl PyGraph { /// :returns: A list of int indices of the newly created edges /// :rtype: list #[pyo3(text_signature = "(self, obj_list, /)")] - pub fn add_edges_from( - &mut self, - obj_list: Vec<(usize, usize, PyObject)>, - ) -> PyResult { - let mut out_list: Vec = Vec::with_capacity(obj_list.len()); - for obj in obj_list { + pub fn add_edges_from(&mut self, obj_list: Bound<'_, PyAny>) -> PyResult { + let mut out_list = Vec::new(); + for py_obj in obj_list.iter()? { + let obj = py_obj?.extract::<(usize, usize, PyObject)>()?; out_list.push(self.add_edge(obj.0, obj.1, obj.2)?); } Ok(EdgeIndices { edges: out_list }) @@ -889,7 +887,7 @@ impl PyGraph { /// Add new edges to the graph without python data. /// - /// :param list obj_list: A list of tuples of the form + /// :param iterable obj_list: An iterable of tuples of the form /// ``(parent, child)`` to attach to the graph. ``parent`` and /// ``child`` are integer indices describing where an edge should be /// added. Unlike :meth:`add_edges_from` there is no data payload and @@ -905,10 +903,11 @@ impl PyGraph { pub fn add_edges_from_no_data( &mut self, py: Python, - obj_list: Vec<(usize, usize)>, + obj_list: Bound<'_, PyAny>, ) -> PyResult { - let mut out_list: Vec = Vec::with_capacity(obj_list.len()); - for obj in obj_list { + let mut out_list: Vec = Vec::new(); + for py_obj in obj_list.iter()? { + let obj = py_obj?.extract::<(usize, usize)>()?; out_list.push(self.add_edge(obj.0, obj.1, py.None())?); } Ok(EdgeIndices { edges: out_list }) @@ -923,13 +922,18 @@ impl PyGraph { /// exists between ``node_a`` and ``node_b`` the weight/payload of that /// existing edge will be updated to be ``None``. /// - /// :param list edge_list: A list of tuples of the form ``(source, target)`` + /// :param iterable edge_list: An iterable of tuples of the form ``(source, target)`` /// where source and target are integer node indices. If the node index /// is not present in the graph, nodes will be added (with a node /// weight of ``None``) to that index. #[pyo3(text_signature = "(self, edge_list, /)")] - pub fn extend_from_edge_list(&mut self, py: Python, edge_list: Vec<(usize, usize)>) { - for (source, target) in edge_list { + pub fn extend_from_edge_list( + &mut self, + py: Python, + edge_list: Bound<'_, PyAny>, + ) -> PyResult<()> { + for py_obj in edge_list.iter()? { + let (source, target) = py_obj?.extract::<(usize, usize)>()?; let max_index = cmp::max(source, target); while max_index >= self.node_count() { self.graph.add_node(py.None()); @@ -938,6 +942,7 @@ impl PyGraph { let target_index = NodeIndex::new(target); self._add_edge(source_index, target_index, py.None()); } + Ok(()) } /// Extend graph from a weighted edge list @@ -951,7 +956,7 @@ impl PyGraph { /// from ``obj_list`` so if there are multiple parallel edges in ``obj_list`` /// the last entry will be used. /// - /// :param list edge_list: A list of tuples of the form + /// :param iterable edge_list: An iterable of tuples of the form /// ``(source, target, weight)`` where source and target are integer /// node indices. If the node index is not present in the graph, /// nodes will be added (with a node weight of ``None``) to that index. @@ -959,9 +964,10 @@ impl PyGraph { pub fn extend_from_weighted_edge_list( &mut self, py: Python, - edge_list: Vec<(usize, usize, PyObject)>, - ) { - for (source, target, weight) in edge_list { + edge_list: Bound<'_, PyAny>, + ) -> PyResult<()> { + for py_obj in edge_list.iter()? { + let (source, target, weight) = py_obj?.extract::<(usize, usize, PyObject)>()?; let max_index = cmp::max(source, target); while max_index >= self.node_count() { self.graph.add_node(py.None()); @@ -970,6 +976,7 @@ impl PyGraph { let target_index = NodeIndex::new(target); self._add_edge(source_index, target_index, weight); } + Ok(()) } /// Remove an edge between 2 nodes. @@ -1009,17 +1016,16 @@ impl PyGraph { /// Note if there are multiple edges between the specified nodes only one /// will be removed. /// - /// :param list index_list: A list of node index pairs to remove from + /// :param iterable index_list: An iterable of node index pairs to remove from /// the graph /// /// :raises NoEdgeBetweenNodes: If there are no edges between a specified /// pair of nodes. #[pyo3(text_signature = "(self, index_list, /)")] - pub fn remove_edges_from(&mut self, index_list: Vec<(usize, usize)>) -> PyResult<()> { - for (p_index, c_index) in index_list - .iter() - .map(|(x, y)| (NodeIndex::new(*x), NodeIndex::new(*y))) - { + pub fn remove_edges_from(&mut self, index_list: Bound<'_, PyAny>) -> PyResult<()> { + for py_obj in index_list.iter()? { + let (x, y) = py_obj?.extract::<(usize, usize)>()?; + let (p_index, c_index) = (NodeIndex::new(x), NodeIndex::new(y)); let edge_index = match self.graph.find_edge(p_index, c_index) { Some(edge_index) => edge_index, None => return Err(NoEdgeBetweenNodes::new_err("No edge found between nodes")), @@ -1043,17 +1049,18 @@ impl PyGraph { /// Add new nodes to the graph. /// - /// :param list obj_list: A list of python object to attach to the graph. + /// :param iterable obj_list: An iterable of python object to attach to the graph. /// /// :returns indices: A list of int indices of the newly created nodes /// :rtype: NodeIndices #[pyo3(text_signature = "(self, obj_list, /)")] - pub fn add_nodes_from(&mut self, obj_list: Vec) -> NodeIndices { - let out_list: Vec = obj_list - .into_iter() - .map(|obj| self.graph.add_node(obj).index()) - .collect(); - NodeIndices { nodes: out_list } + pub fn add_nodes_from(&mut self, obj_list: Bound<'_, PyAny>) -> PyResult { + let mut out_list = Vec::new(); + for py_obj in obj_list.iter()? { + let obj = py_obj?.extract::()?; + out_list.push(self.graph.add_node(obj).index()); + } + Ok(NodeIndices { nodes: out_list }) } /// Remove nodes from the graph. @@ -1061,11 +1068,12 @@ impl PyGraph { /// If a node index in the list is not present in the graph it will be /// ignored. /// - /// :param list index_list: A list of node indicies to remove from the - /// the graph + /// :param iterable index_list: An iterable of node indices to remove from the + /// graph #[pyo3(text_signature = "(self, index_list, /)")] - pub fn remove_nodes_from(&mut self, index_list: Vec) -> PyResult<()> { - for node in index_list { + pub fn remove_nodes_from(&mut self, index_list: Bound<'_, PyAny>) -> PyResult<()> { + for py_obj in index_list.iter()? { + let node = py_obj?.extract::()?; self.remove_node(node)?; } Ok(()) diff --git a/tests/digraph/test_edges.py b/tests/digraph/test_edges.py index 9ec06d31a3..d2275a6def 100644 --- a/tests/digraph/test_edges.py +++ b/tests/digraph/test_edges.py @@ -164,6 +164,16 @@ def test_remove_edges_from(self): graph.remove_edges_from([(node_a, node_b), (node_a, node_c)]) self.assertEqual([], graph.edges()) + def test_remove_edges_from_gen(self): + graph = rustworkx.PyDiGraph() + node_a = graph.add_node("a") + node_b = graph.add_node("b") + node_c = graph.add_node("c") + graph.add_edge(node_a, node_b, "edgy") + graph.add_edge(node_a, node_c, "super_edgy") + graph.remove_edges_from((node_a, n) for n in (node_b, node_c)) + self.assertEqual([], graph.edges()) + def test_remove_edges_from_invalid(self): graph = rustworkx.PyDiGraph() node_a = graph.add_node("a") @@ -287,6 +297,21 @@ def test_add_edge_from(self): self.assertEqual(1, dag.out_degree(2)) self.assertEqual(2, dag.in_degree(3)) + def test_add_edge_from_gen(self): + graph = rustworkx.PyDiGraph() + nodes = range(4) + graph.add_nodes_from(nodes) + edge_list = [(0, 1), (1, 2), (0, 2), (2, 3), (0, 3)] + edge_gen = ((i, j, c) for (i, j), c in zip(edge_list, ["a", "b", "c", "d", "e"])) + res = graph.add_edges_from(edge_gen) + self.assertEqual(len(res), 5) + self.assertEqual(["a", "b", "c", "d", "e"], graph.edges()) + self.assertEqual(3, graph.out_degree(0)) + self.assertEqual(0, graph.in_degree(0)) + self.assertEqual(1, graph.out_degree(1)) + self.assertEqual(1, graph.out_degree(2)) + self.assertEqual(2, graph.in_degree(3)) + def test_add_edge_from_empty(self): dag = rustworkx.PyDAG() res = dag.add_edges_from([]) @@ -323,6 +348,21 @@ def test_add_edge_from_no_data(self): self.assertEqual(1, dag.out_degree(2)) self.assertEqual(2, dag.in_degree(3)) + def test_add_edge_from_gen_no_data(self): + graph = rustworkx.PyDiGraph() + nodes = range(4) + graph.add_nodes_from(nodes) + edge_list = [(0, 1), (1, 2), (0, 2), (2, 3), (0, 3)] + edge_gen = ((i, j) for i, j in edge_list) + res = graph.add_edges_from_no_data(edge_gen) + self.assertEqual(len(res), 5) + self.assertEqual([None, None, None, None, None], graph.edges()) + self.assertEqual(3, graph.out_degree(0)) + self.assertEqual(0, graph.in_degree(0)) + self.assertEqual(1, graph.out_degree(1)) + self.assertEqual(1, graph.out_degree(2)) + self.assertEqual(2, graph.in_degree(3)) + def test_add_edge_from_empty_no_data(self): dag = rustworkx.PyDAG() res = dag.add_edges_from_no_data([]) @@ -401,6 +441,19 @@ def test_extend_from_edge_list(self): self.assertEqual(1, dag.out_degree(2)) self.assertEqual(2, dag.in_degree(3)) + def test_extend_from_edge_gen(self): + graph = rustworkx.PyDiGraph() + edge_list = [(0, 1), (1, 2), (0, 2), (2, 3), (0, 3)] + edge_gen = ((i, j) for i, j in edge_list) + graph.extend_from_edge_list(edge_gen) + self.assertEqual(len(graph), 4) + self.assertEqual([None] * 5, graph.edges()) + self.assertEqual(3, graph.out_degree(0)) + self.assertEqual(0, graph.in_degree(0)) + self.assertEqual(1, graph.out_degree(1)) + self.assertEqual(1, graph.out_degree(2)) + self.assertEqual(2, graph.in_degree(3)) + def test_extend_from_edge_list_empty(self): dag = rustworkx.PyDAG() dag.extend_from_edge_list([]) @@ -445,6 +498,19 @@ def test_extend_from_weighted_edge_list(self): self.assertEqual(1, dag.out_degree(2)) self.assertEqual(2, dag.in_degree(3)) + def test_extend_from_weighted_edge_gen(self): + graph = rustworkx.PyDiGraph() + edge_list = [(0, 1), (1, 2), (0, 2), (2, 3), (0, 3)] + edge_gen = ((i, j, c) for (i, j), c in zip(edge_list, ["a", "b", "c", "d", "e"])) + graph.extend_from_weighted_edge_list(edge_gen) + self.assertEqual(len(graph), 4) + self.assertEqual(["a", "b", "c", "d", "e"], graph.edges()) + self.assertEqual(3, graph.out_degree(0)) + self.assertEqual(0, graph.in_degree(0)) + self.assertEqual(1, graph.out_degree(1)) + self.assertEqual(1, graph.out_degree(2)) + self.assertEqual(2, graph.in_degree(3)) + def test_extend_from_weighted_edge_list_empty(self): dag = rustworkx.PyDAG() dag.extend_from_weighted_edge_list([]) diff --git a/tests/digraph/test_nodes.py b/tests/digraph/test_nodes.py index eab9926073..773713b227 100644 --- a/tests/digraph/test_nodes.py +++ b/tests/digraph/test_nodes.py @@ -66,6 +66,16 @@ def test_remove_nodes_from(self): self.assertEqual(["a"], res) self.assertEqual([0], dag.node_indexes()) + def test_remove_nodes_from_gen(self): + graph = rustworkx.PyDiGraph() + node_a = graph.add_node("a") + node_b = graph.add_child(node_a, "b", "Edgy") + node_c = graph.add_child(node_b, "c", "Edgy_mk2") + graph.remove_nodes_from(n for n in [node_b, node_c]) + res = graph.nodes() + self.assertEqual(["a"], res) + self.assertEqual([0], graph.node_indexes()) + def test_remove_nodes_from_with_invalid_index(self): dag = rustworkx.PyDAG() node_a = dag.add_node("a") @@ -638,6 +648,14 @@ def test_add_nodes_from(self): self.assertEqual(len(res), 100) self.assertEqual(res, nodes) + def test_add_nodes_from_gen(self): + graph = rustworkx.PyDiGraph() + nodes = list(range(100)) + node_gen = (i**2 for i in nodes) + res = graph.add_nodes_from(node_gen) + self.assertEqual(len(res), 100) + self.assertEqual(res, nodes) + def test_add_node_from_empty(self): dag = rustworkx.PyDAG() res = dag.add_nodes_from([]) diff --git a/tests/graph/test_edges.py b/tests/graph/test_edges.py index 628a9788b9..4834888321 100644 --- a/tests/graph/test_edges.py +++ b/tests/graph/test_edges.py @@ -197,6 +197,16 @@ def test_remove_edges_from(self): graph.remove_edges_from([(node_a, node_b), (node_a, node_c)]) self.assertEqual([], graph.edges()) + def test_remove_edges_from_gen(self): + graph = rustworkx.PyGraph() + node_a = graph.add_node("a") + node_b = graph.add_node("b") + node_c = graph.add_node("c") + graph.add_edge(node_a, node_b, "edgy") + graph.add_edge(node_a, node_c, "super_edgy") + graph.remove_edges_from((node_a, n) for n in (node_b, node_c)) + self.assertEqual([], graph.edges()) + def test_remove_edges_from_invalid(self): graph = rustworkx.PyGraph() node_a = graph.add_node("a") @@ -240,6 +250,20 @@ def test_add_edge_from(self): self.assertEqual(3, graph.degree(2)) self.assertEqual(2, graph.degree(3)) + def test_add_edge_from_gen(self): + graph = rustworkx.PyGraph() + nodes = range(4) + graph.add_nodes_from(nodes) + edge_list = [(0, 1), (1, 2), (0, 2), (2, 3), (0, 3)] + edge_gen = ((i, j, c) for (i, j), c in zip(edge_list, ["a", "b", "c", "d", "e"])) + res = graph.add_edges_from(edge_gen) + self.assertEqual(len(res), 5) + self.assertEqual(["a", "b", "c", "d", "e"], graph.edges()) + self.assertEqual(3, graph.degree(0)) + self.assertEqual(2, graph.degree(1)) + self.assertEqual(3, graph.degree(2)) + self.assertEqual(2, graph.degree(3)) + def test_add_edge_from_empty(self): graph = rustworkx.PyGraph() res = graph.add_edges_from([]) @@ -258,6 +282,20 @@ def test_add_edge_from_no_data(self): self.assertEqual(3, graph.degree(2)) self.assertEqual(2, graph.degree(3)) + def test_add_edge_from_gen_no_data(self): + graph = rustworkx.PyGraph() + nodes = range(4) + graph.add_nodes_from(nodes) + edge_list = [(0, 1), (1, 2), (0, 2), (2, 3), (0, 3)] + edge_gen = ((i, j) for i, j in edge_list) + res = graph.add_edges_from_no_data(edge_gen) + self.assertEqual(len(res), 5) + self.assertEqual([None, None, None, None, None], graph.edges()) + self.assertEqual(3, graph.degree(0)) + self.assertEqual(2, graph.degree(1)) + self.assertEqual(3, graph.degree(2)) + self.assertEqual(2, graph.degree(3)) + def test_add_edge_from_empty_no_data(self): graph = rustworkx.PyGraph() res = graph.add_edges_from_no_data([]) @@ -362,6 +400,18 @@ def test_extend_from_edge_list(self): self.assertEqual(3, graph.degree(2)) self.assertEqual(2, graph.degree(3)) + def test_extend_from_edge_gen(self): + graph = rustworkx.PyGraph() + edge_list = [(0, 1), (1, 2), (0, 2), (2, 3), (0, 3)] + edge_gen = ((i, j) for i, j in edge_list) + graph.extend_from_edge_list(edge_gen) + self.assertEqual(len(graph), 4) + self.assertEqual([None] * 5, graph.edges()) + self.assertEqual(3, graph.degree(0)) + self.assertEqual(2, graph.degree(1)) + self.assertEqual(3, graph.degree(2)) + self.assertEqual(2, graph.degree(3)) + def test_extend_from_edge_list_empty(self): graph = rustworkx.PyGraph() graph.extend_from_edge_list([]) @@ -399,6 +449,13 @@ def test_extend_from_weighted_edge_list(self): graph.extend_from_weighted_edge_list(edge_list) self.assertEqual(len(graph), 4) + def test_extend_from_weighted_edge_gen(self): + graph = rustworkx.PyGraph() + edge_list = [(0, 1), (1, 2), (0, 2), (2, 3), (0, 3)] + edge_gen = ((i, j, c) for (i, j), c in zip(edge_list, ["a", "b", "c", "d", "e"])) + graph.extend_from_weighted_edge_list(edge_gen) + self.assertEqual(len(graph), 4) + def test_add_edges_from_parallel_edges(self): graph = rustworkx.PyGraph() graph.add_nodes_from([0, 1]) diff --git a/tests/graph/test_nodes.py b/tests/graph/test_nodes.py index f81b823739..d704724a3b 100644 --- a/tests/graph/test_nodes.py +++ b/tests/graph/test_nodes.py @@ -68,6 +68,18 @@ def test_remove_nodes_from(self): self.assertEqual(["a"], res) self.assertEqual([0], graph.node_indexes()) + def test_remove_nodes_from_gen(self): + graph = rustworkx.PyGraph() + node_a = graph.add_node("a") + node_b = graph.add_node("b") + graph.add_edge(node_a, node_b, "Edgy") + node_c = graph.add_node("c") + graph.add_edge(node_b, node_c, "Edgy_mk2") + graph.remove_nodes_from(n for n in [node_b, node_c]) + res = graph.nodes() + self.assertEqual(["a"], res) + self.assertEqual([0], graph.node_indexes()) + def test_remove_nodes_from_with_invalid_index(self): graph = rustworkx.PyGraph() node_a = graph.add_node("a") @@ -121,6 +133,14 @@ def test_add_nodes_from(self): self.assertEqual(len(res), 100) self.assertEqual(res, nodes) + def test_add_nodes_from_gen(self): + graph = rustworkx.PyGraph() + nodes = list(range(100)) + node_gen = (i**2 for i in nodes) + res = graph.add_nodes_from(node_gen) + self.assertEqual(len(res), 100) + self.assertEqual(res, nodes) + def test_add_node_from_empty(self): graph = rustworkx.PyGraph() res = graph.add_nodes_from([]) From 6f1d2ecad34f0d66d9344e90742a93636f2bb9c5 Mon Sep 17 00:00:00 2001 From: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com> Date: Mon, 14 Oct 2024 12:33:30 -0400 Subject: [PATCH 07/38] Update PyO3 to 0.22 (#1293) * Prototype of migration (pt1) * Temporarily remove Slicing because I want to handle it later * Cargo fmt * Tackle first batch of warnings * Second batch for warning fixes * Third batch of warning fixes * Final batch of warning fixes * Re-introduce slicing support * Update ndarray as well * Use NumPy from release * Remove stray comment --------- Co-authored-by: Matthew Treinish --- Cargo.lock | 205 ++++++++------------------------------- Cargo.toml | 10 +- src/coloring.rs | 2 +- src/connectivity/mod.rs | 16 +-- src/dag_algo/mod.rs | 4 +- src/digraph.rs | 13 ++- src/graph.rs | 12 ++- src/iterators.rs | 30 ++++-- src/json/mod.rs | 8 +- src/layout/mod.rs | 12 +-- src/random_graph.rs | 20 ++-- src/shortest_path/mod.rs | 12 +-- src/token_swapper.rs | 5 +- src/traversal/mod.rs | 10 +- 14 files changed, 136 insertions(+), 223 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7de789d507..f81e20123e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,12 +47,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - [[package]] name = "byteorder" version = "1.5.0" @@ -119,12 +113,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.5" @@ -144,9 +132,9 @@ checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -154,16 +142,6 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.6.0" @@ -183,18 +161,18 @@ checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "itertools" -version = "0.10.5" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[package]] name = "itertools" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -217,16 +195,6 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "matrixmultiply" version = "0.3.9" @@ -254,26 +222,28 @@ dependencies = [ [[package]] name = "ndarray" -version = "0.15.6" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" dependencies = [ "matrixmultiply", "num-complex 0.4.6", "num-integer", "num-traits", + "portable-atomic", + "portable-atomic-util", "rawpointer", "rayon", ] [[package]] name = "ndarray-stats" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af5a8477ac96877b5bd1fd67e0c28736c12943aba24eda92b127e036b0c8f400" +checksum = "17ebbe97acce52d06aebed4cd4a87c0941f4b2519b59b82b4feb5bd0ce003dfd" dependencies = [ - "indexmap 1.9.3", - "itertools 0.10.5", + "indexmap", + "itertools 0.13.0", "ndarray", "noisy_float", "num-integer", @@ -350,9 +320,9 @@ dependencies = [ [[package]] name = "numpy" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec170733ca37175f5d75a5bea5911d6ff45d2cd52849ce98b685394e4f2f37f4" +checksum = "cf314fca279e6e6ac2126a4ff98f26d88aa4ad06bc68fb6ae5cf4bd706758311" dependencies = [ "libc", "ndarray", @@ -369,29 +339,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] - [[package]] name = "petgraph" version = "0.6.5" @@ -399,7 +346,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.6.0", + "indexmap", ] [[package]] @@ -408,6 +355,15 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +[[package]] +name = "portable-atomic-util" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcdd8420072e66d54a407b3316991fe946ce3ab1083a7f575b2463866624704d" +dependencies = [ + "portable-atomic", +] + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -425,7 +381,7 @@ checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d" dependencies = [ "autocfg", "equivalent", - "indexmap 2.6.0", + "indexmap", ] [[package]] @@ -439,19 +395,19 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8" +checksum = "15ee168e30649f7f234c3d49ef5a7a6cbf5134289bc46c29ff3155fa3221c225" dependencies = [ "cfg-if", "hashbrown 0.14.5", - "indexmap 2.6.0", + "indexmap", "indoc", "libc", "memoffset", "num-bigint", "num-complex 0.4.6", - "parking_lot", + "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", @@ -461,9 +417,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50" +checksum = "e61cef80755fe9e46bb8a0b8f20752ca7676dcc07a5277d8b7768c6172e529b3" dependencies = [ "once_cell", "target-lexicon", @@ -471,9 +427,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403" +checksum = "67ce096073ec5405f5ee2b8b31f03a68e02aa10d5d4f565eca04acc41931fa1c" dependencies = [ "libc", "pyo3-build-config", @@ -481,9 +437,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c" +checksum = "2440c6d12bc8f3ae39f1e775266fa5122fd0c8891ce7520fa6048e683ad3de28" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -493,9 +449,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c" +checksum = "1be962f0e06da8f8465729ea2cb71a416d2257dff56cbe40a70d3e62a93ae5d1" dependencies = [ "heck", "proc-macro2", @@ -598,15 +554,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "redox_syscall" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" -dependencies = [ - "bitflags", -] - [[package]] name = "rustc-hash" version = "1.1.0" @@ -620,7 +567,7 @@ dependencies = [ "ahash", "fixedbitset", "hashbrown 0.14.5", - "indexmap 2.6.0", + "indexmap", "ndarray", "ndarray-stats", "num-bigint", @@ -647,7 +594,7 @@ dependencies = [ "ahash", "fixedbitset", "hashbrown 0.14.5", - "indexmap 2.6.0", + "indexmap", "ndarray", "num-traits", "petgraph", @@ -664,12 +611,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "serde" version = "1.0.210" @@ -764,70 +705,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index 667d17de7a..b9826507a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,9 +28,9 @@ ahash = "0.8.6" fixedbitset = "0.4.2" hashbrown = { version = ">=0.13, <0.15", features = ["rayon"] } indexmap = { version = ">=1.9, <3", features = ["rayon"] } -ndarray = { version = "0.15.6", features = ["rayon"] } +ndarray = { version = "0.16.1", features = ["rayon"] } num-traits = "0.2" -numpy = "0.21.0" +numpy = "0.22" petgraph = "0.6.5" rand = "0.8" rand_pcg = "0.3" @@ -46,7 +46,7 @@ fixedbitset.workspace = true hashbrown.workspace = true indexmap.workspace = true ndarray.workspace = true -ndarray-stats = "0.5.1" +ndarray-stats = "0.6.0" num-bigint = "0.4" num-complex = "0.4" num-traits.workspace = true @@ -62,8 +62,8 @@ smallvec = { version = "1.0", features = ["union"] } rustworkx-core = { path = "rustworkx-core", version = "=0.16.0" } [dependencies.pyo3] -version = "0.21.2" -features = ["abi3-py39", "extension-module", "hashbrown", "num-bigint", "num-complex", "indexmap"] +version = "0.22.3" +features = ["abi3-py39", "extension-module", "hashbrown", "num-bigint", "num-complex", "indexmap", "py-clone"] [dependencies.sprs] version = "^0.11" diff --git a/src/coloring.rs b/src/coloring.rs index cf844b1a22..25b96ebd36 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -42,7 +42,7 @@ pub use rustworkx_core::coloring::ColoringStrategy as ColoringStrategyCore; /// - `GIS` strategy in [1] (section 1.2.2.9) /// /// [1] Adrian Kosowski, and Krzysztof Manuszewski, Classical Coloring of Graphs, Graph Colorings, 2-19, 2004. ISBN 0-8218-3458-4. -#[pyclass(module = "rustworkx")] +#[pyclass(module = "rustworkx", eq, eq_int)] #[derive(Clone, PartialEq)] pub enum ColoringStrategy { Degree, diff --git a/src/connectivity/mod.rs b/src/connectivity/mod.rs index c3023d6d8b..353a57fe25 100644 --- a/src/connectivity/mod.rs +++ b/src/connectivity/mod.rs @@ -71,7 +71,7 @@ use rustworkx_core::dag_algo::longest_path; /// .. [1] Paton, K. An algorithm for finding a fundamental set of /// cycles of a graph. Comm. ACM 12, 9 (Sept 1969), 514-518. #[pyfunction] -#[pyo3(text_signature = "(graph, /, root=None)")] +#[pyo3(text_signature = "(graph, /, root=None)", signature = (graph, root=None))] pub fn cycle_basis(graph: &graph::PyGraph, root: Option) -> Vec> { connectivity::cycle_basis(&graph.graph, root.map(NodeIndex::new)) .into_iter() @@ -125,7 +125,7 @@ pub fn strongly_connected_components(graph: &digraph::PyDiGraph) -> Vec) -> EdgeList { EdgeList { edges: connectivity::find_cycle(&graph.graph, source.map(NodeIndex::new)) @@ -575,7 +575,7 @@ pub fn digraph_complement(py: Python, graph: &digraph::PyDiGraph) -> PyResult, @@ -749,7 +749,7 @@ pub fn connected_subgraphs(graph: &PyGraph, k: usize) -> PyResult /// /// :raises ValueError: If ``min_depth`` or ``cutoff`` are < 2. #[pyfunction] -#[pyo3(text_signature = "(graph, /, min_depth=None, cutoff=None)")] +#[pyo3(text_signature = "(graph, /, min_depth=None, cutoff=None)", signature = (graph, min_depth=None, cutoff=None))] pub fn graph_all_pairs_all_simple_paths( graph: &graph::PyGraph, min_depth: Option, @@ -939,7 +939,7 @@ pub fn digraph_core_number(py: Python, graph: &digraph::PyDiGraph) -> PyResult

BiconnectedComponents { /// and 2-edge-connectivity." *Information Processing Letters*, /// 113, 241–244. Elsevier. #[pyfunction] -#[pyo3(text_signature = "(graph, /, source=None)")] +#[pyo3(text_signature = "(graph, /, source=None)", signature = (graph, source=None))] pub fn chain_decomposition(graph: graph::PyGraph, source: Option) -> Chains { let chains = connectivity::chain_decomposition(&graph.graph, source.map(NodeIndex::new)); Chains { diff --git a/src/dag_algo/mod.rs b/src/dag_algo/mod.rs index 65df6afcc7..769582ce13 100644 --- a/src/dag_algo/mod.rs +++ b/src/dag_algo/mod.rs @@ -111,7 +111,7 @@ pub fn traversal_directions(reverse: bool) -> (petgraph::Direction, petgraph::Di /// :raises Exception: If an unexpected error occurs or a path can't be found /// :raises DAGHasCycle: If the input PyDiGraph has a cycle #[pyfunction] -#[pyo3(text_signature = "(graph, /, weight_fn=None)")] +#[pyo3(text_signature = "(graph, /, weight_fn=None)", signature = (graph, weight_fn=None))] pub fn dag_longest_path( py: Python, graph: &digraph::PyDiGraph, @@ -151,7 +151,7 @@ pub fn dag_longest_path( /// :raises Exception: If an unexpected error occurs or a path can't be found /// :raises DAGHasCycle: If the input PyDiGraph has a cycle #[pyfunction] -#[pyo3(text_signature = "(graph, /, weight_fn=None)")] +#[pyo3(text_signature = "(graph, /, weight_fn=None)", signature = (graph, weight_fn=None))] pub fn dag_longest_path_length( py: Python, graph: &digraph::PyDiGraph, diff --git a/src/digraph.rs b/src/digraph.rs index dd50250802..07aa2be14a 100644 --- a/src/digraph.rs +++ b/src/digraph.rs @@ -2132,7 +2132,8 @@ impl PyDiGraph { /// image /// #[pyo3( - text_signature = "(self, /, node_attr=None, edge_attr=None, graph_attr=None, filename=None)" + text_signature = "(self, /, node_attr=None, edge_attr=None, graph_attr=None, filename=None)", + signature = (node_attr=None, edge_attr=None, graph_attr=None, filename=None) )] pub fn to_dot( &self, @@ -2309,7 +2310,7 @@ impl PyDiGraph { /// with open(path, 'rt') as edge_file: /// print(edge_file.read()) /// - #[pyo3(text_signature = "(self, path, /, deliminator=None, weight_fn=None)")] + #[pyo3(text_signature = "(self, path, /, deliminator=None, weight_fn=None)", signature = (path, deliminator=None, weight_fn=None))] pub fn write_edge_list( &self, py: Python, @@ -2476,7 +2477,7 @@ impl PyDiGraph { /// graph.compose(other_graph, node_map) /// mpl_draw(graph, with_labels=True, labels=str, edge_labels=str) /// - #[pyo3(text_signature = "(self, other, node_map, /, node_map_func=None, edge_map_func=None)")] + #[pyo3(text_signature = "(self, other, node_map, /, node_map_func=None, edge_map_func=None)", signature = (other, node_map, node_map_func=None, edge_map_func=None))] pub fn compose( &mut self, py: Python, @@ -2553,7 +2554,8 @@ impl PyDiGraph { /// order when iterated over multiple times). /// #[pyo3( - text_signature = "(self, node, other, edge_map_fn, /, node_filter=None, edge_weight_map=None)" + text_signature = "(self, node, other, edge_map_fn, /, node_filter=None, edge_weight_map=None)", + signature = (node, other, edge_map_fn, node_filter=None, edge_weight_map=None) )] fn substitute_node_with_subgraph( &mut self, @@ -2695,7 +2697,7 @@ impl PyDiGraph { /// :returns: The index of the newly created node. /// :raises DAGWouldCycle: The cycle check is enabled and the /// contraction would introduce cycle(s). - #[pyo3(text_signature = "(self, nodes, obj, /, check_cycle=None, weight_combo_fn=None)")] + #[pyo3(text_signature = "(self, nodes, obj, /, check_cycle=None, weight_combo_fn=None)", signature = (nodes, obj, check_cycle=None, weight_combo_fn=None))] pub fn contract_nodes( &mut self, py: Python, @@ -2867,6 +2869,7 @@ impl PyDiGraph { /// then by default the data payload will be copied when the reverse edge is added. /// If there are parallel edges, then one of the edges (typically the one with the lower /// index, but this is not a guarantee) will be copied. + #[pyo3(signature = (edge_payload_fn=None))] pub fn make_symmetric( &mut self, py: Python, diff --git a/src/graph.rs b/src/graph.rs index 41cf02bac8..f74933928b 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -1254,7 +1254,8 @@ impl PyGraph { /// image /// #[pyo3( - text_signature = "(self, /, node_attr=None, edge_attr=None, graph_attr=None, filename=None)" + text_signature = "(self, /, node_attr=None, edge_attr=None, graph_attr=None, filename=None)", + signature = (node_attr=None, edge_attr=None, graph_attr=None, filename=None) )] pub fn to_dot( &self, @@ -1428,7 +1429,7 @@ impl PyGraph { /// with open(path, 'rt') as edge_file: /// print(edge_file.read()) /// - #[pyo3(text_signature = "(self, path, /, deliminator=None, weight_fn=None)")] + #[pyo3(text_signature = "(self, path, /, deliminator=None, weight_fn=None)", signature = (path, deliminator=None, weight_fn=None))] pub fn write_edge_list( &self, py: Python, @@ -1602,7 +1603,7 @@ impl PyGraph { /// graph.compose(other_graph, node_map) /// mpl_draw(graph, with_labels=True, labels=str, edge_labels=str) /// - #[pyo3(text_signature = "(self, other, node_map, /, node_map_func=None, edge_map_func=None)")] + #[pyo3(text_signature = "(self, other, node_map, /, node_map_func=None, edge_map_func=None)", signature = (other, node_map, node_map_func=None, edge_map_func=None))] pub fn compose( &mut self, py: Python, @@ -1679,7 +1680,8 @@ impl PyGraph { /// order when iterated over multiple times). /// #[pyo3( - text_signature = "(self, node, other, edge_map_fn, /, node_filter=None, edge_weight_map=None" + text_signature = "(self, node, other, edge_map_fn, /, node_filter=None, edge_weight_map=None", + signature = (node, other, edge_map_fn, node_filter=None, edge_weight_map=None) )] fn substitute_node_with_subgraph( &mut self, @@ -1825,7 +1827,7 @@ impl PyGraph { /// combined by choosing one of the edge's weights arbitrarily based /// on an internal iteration order, subject to change. /// :returns: The index of the newly created node. - #[pyo3(text_signature = "(self, nodes, obj, /, weight_combo_fn=None)")] + #[pyo3(text_signature = "(self, nodes, obj, /, weight_combo_fn=None)", signature = (nodes, obj, weight_combo_fn=None))] pub fn contract_nodes( &mut self, py: Python, diff --git a/src/iterators.rs b/src/iterators.rs index 64d0c644ec..6eecbbaeba 100644 --- a/src/iterators.rs +++ b/src/iterators.rs @@ -39,7 +39,6 @@ #![allow(clippy::float_cmp, clippy::upper_case_acronyms)] use std::collections::hash_map::DefaultHasher; -use std::convert::TryInto; use std::hash::Hasher; use num_bigint::BigUint; @@ -410,10 +409,26 @@ trait PyGCProtocol { fn __clear__(&mut self) {} } -#[derive(FromPyObject)] -enum SliceOrInt<'a> { +/// A Python-space indexer for the standard `PySequence` type; a single integer or a slice. +/// +/// These come in as `isize`s from Python space, since Python typically allows negative indices. +/// Copied from https://github.com/Qiskit/qiskit/pull/12669 +pub enum PySequenceIndex<'py> { Int(isize), - Slice(&'a PySlice), + Slice(Bound<'py, PySlice>), +} + +impl<'py> FromPyObject<'py> for PySequenceIndex<'py> { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + // `slice` can't be subclassed in Python, so it's safe (and faster) to check for it exactly. + // The `downcast_exact` check is just a pointer comparison, so while `slice` is the less + // common input, doing that first has little-to-no impact on the speed of the `isize` path, + // while the reverse makes `slice` inputs significantly slower. + if let Ok(slice) = ob.downcast_exact::() { + return Ok(Self::Slice(slice.clone())); + } + Ok(Self::Int(ob.extract()?)) + } } trait PyConvertToPyArray { @@ -542,9 +557,9 @@ macro_rules! custom_vec_iter_impl { Ok(self.$data.len()) } - fn __getitem__(&self, py: Python, idx: SliceOrInt) -> PyResult { + fn __getitem__(&self, py: Python, idx: PySequenceIndex) -> PyResult { match idx { - SliceOrInt::Slice(slc) => { + PySequenceIndex::Slice(slc) => { let len = self.$data.len().try_into().unwrap(); let indices = slc.indices(len)?; let mut out_vec: Vec<$T> = Vec::new(); @@ -571,7 +586,7 @@ macro_rules! custom_vec_iter_impl { } Ok(out_vec.into_py(py)) } - SliceOrInt::Int(idx) => { + PySequenceIndex::Int(idx) => { let len = self.$data.len() as isize; if idx >= len || idx < -len { Err(PyIndexError::new_err(format!("Invalid index, {}", idx))) @@ -599,6 +614,7 @@ macro_rules! custom_vec_iter_impl { } } + #[pyo3(signature = (dtype=None, copy=None))] fn __array__( &self, py: Python, diff --git a/src/json/mod.rs b/src/json/mod.rs index 4f6ee50e28..2ad2e8b6bb 100644 --- a/src/json/mod.rs +++ b/src/json/mod.rs @@ -43,6 +43,7 @@ use pyo3::Python; /// :returns: The graph represented by the node link JSON /// :rtype: PyGraph | PyDiGraph #[pyfunction] +#[pyo3(signature = (path, graph_attrs=None, node_attrs=None, edge_attrs=None))] pub fn from_node_link_json_file( py: Python, path: &str, @@ -120,6 +121,7 @@ pub fn from_node_link_json_file( /// :returns: The graph represented by the node link JSON /// :rtype: PyGraph | PyDiGraph #[pyfunction] +#[pyo3(signature = (data, graph_attrs=None, node_attrs=None, edge_attrs=None))] pub fn parse_node_link_json( py: Python, data: &str, @@ -196,7 +198,8 @@ pub fn parse_node_link_json( /// :rtype: str #[pyfunction] #[pyo3( - text_signature = "(graph, /, path=None, graph_attrs=None, node_attrs=None, edge_attrs=None)" + text_signature = "(graph, /, path=None, graph_attrs=None, node_attrs=None, edge_attrs=None)", + signature = (graph, path=None, graph_attrs=None, node_attrs=None, edge_attrs=None) )] pub fn digraph_node_link_json( py: Python, @@ -243,7 +246,8 @@ pub fn digraph_node_link_json( /// :rtype: str #[pyfunction] #[pyo3( - text_signature = "(graph, /, path=None, graph_attrs=None, node_attrs=None, edge_attrs=None)" + text_signature = "(graph, /, path=None, graph_attrs=None, node_attrs=None, edge_attrs=None)", + signature = (graph, path=None, graph_attrs=None, node_attrs=None, edge_attrs=None) )] pub fn graph_node_link_json( py: Python, diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 72c4155e8e..802207b1c4 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -205,7 +205,7 @@ pub fn digraph_spring_layout( /// :returns: The random layout of the graph. /// :rtype: Pos2DMapping #[pyfunction] -#[pyo3(text_signature = "(graph, / center=None, seed=None)")] +#[pyo3(text_signature = "(graph, / center=None, seed=None)", signature = (graph, center=None, seed=None))] pub fn graph_random_layout( graph: &graph::PyGraph, center: Option<[f64; 2]>, @@ -224,7 +224,7 @@ pub fn graph_random_layout( /// :returns: The random layout of the graph. /// :rtype: Pos2DMapping #[pyfunction] -#[pyo3(text_signature = "(graph, / center=None, seed=None)")] +#[pyo3(text_signature = "(graph, / center=None, seed=None)", signature = (graph, center=None, seed=None))] pub fn digraph_random_layout( graph: &digraph::PyDiGraph, center: Option<[f64; 2]>, @@ -319,7 +319,7 @@ pub fn digraph_bipartite_layout( /// :returns: The circular layout of the graph. /// :rtype: Pos2DMapping #[pyfunction] -#[pyo3(text_signature = "(graph, /, scale=1, center=None)")] +#[pyo3(text_signature = "(graph, /, scale=1, center=None)", signature = (graph, scale=None, center=None))] pub fn graph_circular_layout( graph: &graph::PyGraph, scale: Option, @@ -338,7 +338,7 @@ pub fn graph_circular_layout( /// :returns: The circular layout of the graph. /// :rtype: Pos2DMapping #[pyfunction] -#[pyo3(text_signature = "(graph, /, scale=1, center=None)")] +#[pyo3(text_signature = "(graph, /, scale=1, center=None)", signature = (graph, scale=None, center=None))] pub fn digraph_circular_layout( graph: &digraph::PyDiGraph, scale: Option, @@ -361,7 +361,7 @@ pub fn digraph_circular_layout( /// :returns: The shell layout of the graph. /// :rtype: Pos2DMapping #[pyfunction] -#[pyo3(text_signature = "(graph, /, nlist=None, rotate=None, scale=1, center=None)")] +#[pyo3(text_signature = "(graph, /, nlist=None, rotate=None, scale=1, center=None)", signature = (graph, nlist=None, rotate=None, scale=None, center=None))] pub fn graph_shell_layout( graph: &graph::PyGraph, nlist: Option>>, @@ -385,7 +385,7 @@ pub fn graph_shell_layout( /// :returns: The shell layout of the graph. /// :rtype: Pos2DMapping #[pyfunction] -#[pyo3(text_signature = "(graph, /, nlist=None, rotate=None, scale=1, center=None)")] +#[pyo3(text_signature = "(graph, /, nlist=None, rotate=None, scale=1, center=None)", signature = (graph, nlist=None, rotate=None, scale=None, center=None))] pub fn digraph_shell_layout( graph: &digraph::PyDiGraph, nlist: Option>>, diff --git a/src/random_graph.rs b/src/random_graph.rs index 7c981c5ee8..66451dc222 100644 --- a/src/random_graph.rs +++ b/src/random_graph.rs @@ -61,7 +61,7 @@ use rustworkx_core::generators as core_generators; /// Phys. Rev. E, 71, 036113, 2005. /// .. [2] https://github.com/networkx/networkx/blob/networkx-2.4/networkx/generators/random_graphs.py#L49-L120 #[pyfunction] -#[pyo3(text_signature = "(num_nodes, probability, /, seed=None)")] +#[pyo3(text_signature = "(num_nodes, probability, /, seed=None)", signature = (num_nodes, probability, seed=None))] pub fn directed_gnp_random_graph( py: Python, num_nodes: usize, @@ -129,7 +129,7 @@ pub fn directed_gnp_random_graph( /// Phys. Rev. E, 71, 036113, 2005. /// .. [2] https://github.com/networkx/networkx/blob/networkx-2.4/networkx/generators/random_graphs.py#L49-L120 #[pyfunction] -#[pyo3(text_signature = "(num_nodes, probability, /, seed=None)")] +#[pyo3(text_signature = "(num_nodes, probability, /, seed=None)", signature = (num_nodes, probability, seed=None))] pub fn undirected_gnp_random_graph( py: Python, num_nodes: usize, @@ -187,7 +187,7 @@ pub fn undirected_gnp_random_graph( /// :rtype: PyDiGraph /// #[pyfunction] -#[pyo3(text_signature = "(num_nodes, num_edges, /, seed=None)")] +#[pyo3(text_signature = "(num_nodes, num_edges, /, seed=None)", signature = (num_nodes, num_edges, seed=None))] pub fn directed_gnm_random_graph( py: Python, num_nodes: usize, @@ -243,7 +243,7 @@ pub fn directed_gnm_random_graph( /// :rtype: PyGraph #[pyfunction] -#[pyo3(text_signature = "(num_nodes, num_edges, /, seed=None)")] +#[pyo3(text_signature = "(num_nodes, num_edges, /, seed=None)", signature = (num_nodes, num_edges, seed=None))] pub fn undirected_gnm_random_graph( py: Python, num_nodes: usize, @@ -297,7 +297,7 @@ pub fn undirected_gnm_random_graph( /// :return: A PyDiGraph object /// :rtype: PyDiGraph #[pyfunction] -#[pyo3(text_signature = "(sizes, probabilities, loops, /, seed=None)")] +#[pyo3(text_signature = "(sizes, probabilities, loops, /, seed=None)", signature = (sizes, probabilities, loops, seed=None))] pub fn directed_sbm_random_graph<'p>( py: Python<'p>, sizes: Vec, @@ -353,7 +353,7 @@ pub fn directed_sbm_random_graph<'p>( /// :return: A PyGraph object /// :rtype: PyGraph #[pyfunction] -#[pyo3(text_signature = "(sizes, probabilities, loops, /, seed=None)")] +#[pyo3(text_signature = "(sizes, probabilities, loops, /, seed=None)", signature = (sizes, probabilities, loops, seed=None))] pub fn undirected_sbm_random_graph<'p>( py: Python<'p>, sizes: Vec, @@ -527,7 +527,7 @@ pub fn random_geometric_graph( /// :return: A PyGraph object /// :rtype: PyGraph #[pyfunction] -#[pyo3(text_signature = "(pos, beta, r, /, seed=None)")] +#[pyo3(text_signature = "(pos, beta, r, /, seed=None)", signature = (pos, r, beta=None, seed=None))] pub fn hyperbolic_random_graph( py: Python, pos: Vec>, @@ -572,6 +572,7 @@ pub fn hyperbolic_random_graph( /// :return: A PyGraph object /// :rtype: PyGraph #[pyfunction] +#[pyo3(signature = (n, m, seed=None, initial_graph=None))] pub fn barabasi_albert_graph( py: Python, n: usize, @@ -633,6 +634,7 @@ pub fn barabasi_albert_graph( /// :return: A PyDiGraph object /// :rtype: PyDiGraph #[pyfunction] +#[pyo3(signature = (n, m, seed=None, initial_graph=None))] pub fn directed_barabasi_albert_graph( py: Python, n: usize, @@ -691,7 +693,7 @@ pub fn directed_barabasi_albert_graph( /// :return: A PyDiGraph object /// :rtype: PyDiGraph #[pyfunction] -#[pyo3(text_signature = "(num_l_nodes, num_r_nodes, probability, /, seed=None)")] +#[pyo3(text_signature = "(num_l_nodes, num_r_nodes, probability, /, seed=None)", signature = (num_l_nodes, num_r_nodes, probability, seed=None))] pub fn directed_random_bipartite_graph( py: Python, num_l_nodes: usize, @@ -744,7 +746,7 @@ pub fn directed_random_bipartite_graph( /// :return: A PyGraph object /// :rtype: PyGraph #[pyfunction] -#[pyo3(text_signature = "(num_l_nodes, num_r_nodes, probability, /, seed=None)")] +#[pyo3(text_signature = "(num_l_nodes, num_r_nodes, probability, /, seed=None)", signature = (num_l_nodes, num_r_nodes, probability, seed=None))] pub fn undirected_random_bipartite_graph( py: Python, num_l_nodes: usize, diff --git a/src/shortest_path/mod.rs b/src/shortest_path/mod.rs index fb9aec4e13..1b9970327d 100644 --- a/src/shortest_path/mod.rs +++ b/src/shortest_path/mod.rs @@ -375,7 +375,7 @@ pub fn digraph_has_path( /// :raises ValueError: when an edge weight with NaN or negative value /// is provided. #[pyfunction] -#[pyo3(text_signature = "(graph, node, edge_cost_fn, /, goal=None)")] +#[pyo3(text_signature = "(graph, node, edge_cost_fn, /, goal=None)", signature = (graph, node, edge_cost_fn, goal=None))] pub fn graph_dijkstra_shortest_path_lengths( py: Python, graph: &graph::PyGraph, @@ -450,7 +450,7 @@ pub fn graph_dijkstra_shortest_path_lengths( /// :raises ValueError: when an edge weight with NaN or negative value /// is provided. #[pyfunction] -#[pyo3(text_signature = "(graph, node, edge_cost_fn, /, goal=None)")] +#[pyo3(text_signature = "(graph, node, edge_cost_fn, /, goal=None)", signature = (graph, node, edge_cost_fn, goal=None))] pub fn digraph_dijkstra_shortest_path_lengths( py: Python, graph: &digraph::PyDiGraph, @@ -802,7 +802,7 @@ pub fn graph_astar_shortest_path( /// :raises ValueError: when an edge weight with NaN or negative value /// is provided. #[pyfunction] -#[pyo3(text_signature = "(graph, start, k, edge_cost, /, goal=None)")] +#[pyo3(text_signature = "(graph, start, k, edge_cost, /, goal=None)", signature = (graph, start, k, edge_cost, goal=None))] pub fn digraph_k_shortest_path_lengths( py: Python, graph: &digraph::PyDiGraph, @@ -862,7 +862,7 @@ pub fn digraph_k_shortest_path_lengths( /// :raises ValueError: when an edge weight with NaN or negative value /// is provided. #[pyfunction] -#[pyo3(text_signature = "(graph, start, k, edge_cost, /, goal=None)")] +#[pyo3(text_signature = "(graph, start, k, edge_cost, /, goal=None)", signature = (graph, start, k, edge_cost, goal=None))] pub fn graph_k_shortest_path_lengths( py: Python, graph: &graph::PyGraph, @@ -1578,7 +1578,7 @@ pub fn graph_unweighted_average_shortest_path_length( /// :raises: :class:`~rustworkx.NegativeCycle`: when there is a negative cycle and the shortest /// path is not defined. #[pyfunction] -#[pyo3(text_signature = "(graph, node, edge_cost_fn, /, goal=None)")] +#[pyo3(text_signature = "(graph, node, edge_cost_fn, /, goal=None)", signature = (graph, node, edge_cost_fn, goal=None))] pub fn digraph_bellman_ford_shortest_path_lengths( py: Python, graph: &digraph::PyDiGraph, @@ -1663,7 +1663,7 @@ pub fn digraph_bellman_ford_shortest_path_lengths( /// :raises: :class:`~rustworkx.NegativeCycle`: when there is a negative cycle and the shortest /// path is not defined. #[pyfunction] -#[pyo3(text_signature = "(graph, node, edge_cost_fn, /, goal=None)")] +#[pyo3(text_signature = "(graph, node, edge_cost_fn, /, goal=None)", signature = (graph, node, edge_cost_fn, goal=None))] pub fn graph_bellman_ford_shortest_path_lengths( py: Python, graph: &graph::PyGraph, diff --git a/src/token_swapper.rs b/src/token_swapper.rs index a4b460a868..a2ee68e21d 100644 --- a/src/token_swapper.rs +++ b/src/token_swapper.rs @@ -48,7 +48,10 @@ use rustworkx_core::token_swapper; /// the tokens. /// :rtype: EdgeList #[pyfunction] -#[pyo3(text_signature = "(graph, mapping, /, trials=None, seed=None, parallel_threshold=50)")] +#[pyo3( + text_signature = "(graph, mapping, /, trials=None, seed=None, parallel_threshold=50)", + signature = (graph, mapping, trials=None, seed=None, parallel_threshold=None) +)] pub fn graph_token_swapper( graph: &graph::PyGraph, mapping: HashMap, diff --git a/src/traversal/mod.rs b/src/traversal/mod.rs index f6ce66a767..2d8531b48b 100644 --- a/src/traversal/mod.rs +++ b/src/traversal/mod.rs @@ -71,7 +71,7 @@ use crate::iterators::EdgeList; /// depth-first order /// :rtype: EdgeList #[pyfunction] -#[pyo3(text_signature = "(graph, /, source=None)")] +#[pyo3(text_signature = "(graph, /, source=None)", signature = (graph, source=None))] pub fn digraph_dfs_edges(graph: &digraph::PyDiGraph, source: Option) -> EdgeList { EdgeList { edges: dfs_edges(&graph.graph, source.map(NodeIndex::new)), @@ -116,7 +116,7 @@ pub fn digraph_dfs_edges(graph: &digraph::PyDiGraph, source: Option) -> E /// depth-first order /// :rtype: EdgeList #[pyfunction] -#[pyo3(text_signature = "(graph, /, source=None)")] +#[pyo3(text_signature = "(graph, /, source=None)", signature = (graph, source=None))] pub fn graph_dfs_edges(graph: &graph::PyGraph, source: Option) -> EdgeList { EdgeList { edges: dfs_edges(&graph.graph, source.map(NodeIndex::new)), @@ -318,6 +318,7 @@ pub fn descendants(graph: &digraph::PyDiGraph, node: usize) -> HashSet { /// preserve argument ordering from an earlier version) but it is a required argument /// and will raise a ``TypeError`` if not specified. #[pyfunction] +#[pyo3(signature = (graph, source=None, visitor=None))] pub fn digraph_bfs_search( py: Python, graph: &digraph::PyDiGraph, @@ -410,6 +411,7 @@ pub fn digraph_bfs_search( /// preserve argument ordering from an earlier version) but it is a required argument /// and will raise a ``TypeError`` if not specified. #[pyfunction] +#[pyo3(signature = (graph, source=None, visitor=None))] pub fn graph_bfs_search( py: Python, graph: &graph::PyGraph, @@ -500,6 +502,7 @@ pub fn graph_bfs_search( /// preserve argument ordering from an earlier version) but it is a required argument /// and will raise a ``TypeError`` if not specified. #[pyfunction] +#[pyo3(signature = (graph, source=None, visitor=None))] pub fn digraph_dfs_search( py: Python, graph: &digraph::PyDiGraph, @@ -590,6 +593,7 @@ pub fn digraph_dfs_search( /// preserve argument ordering from an earlier version) but it is a required argument /// and will raise a ``TypeError`` if not specified. #[pyfunction] +#[pyo3(signature = (graph, source=None, visitor=None))] pub fn graph_dfs_search( py: Python, graph: &graph::PyGraph, @@ -666,6 +670,7 @@ pub fn graph_dfs_search( /// preserve argument ordering from an earlier version) but it is a required argument /// and will raise a ``TypeError`` if not specified. #[pyfunction] +#[pyo3(signature = (graph, source=None, weight_fn=None, visitor=None))] pub fn digraph_dijkstra_search( py: Python, graph: &digraph::PyDiGraph, @@ -747,6 +752,7 @@ pub fn digraph_dijkstra_search( /// preserve argument ordering from an earlier version) but it is a required argument /// and will raise a ``TypeError`` if not specified. #[pyfunction] +#[pyo3(signature = (graph, source=None, weight_fn=None, visitor=None))] pub fn graph_dijkstra_search( py: Python, graph: &graph::PyGraph, From 8cc1286f9e55e78f9ee21d1b8c3aff4155c85379 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:43:18 +0000 Subject: [PATCH 08/38] Bump pyo3 from 0.22.3 to 0.22.5 (#1296) * Bump pyo3 from 0.22.3 to 0.22.4 Bumps [pyo3](https://github.com/pyo3/pyo3) from 0.22.3 to 0.22.4. - [Release notes](https://github.com/pyo3/pyo3/releases) - [Changelog](https://github.com/PyO3/pyo3/blob/main/CHANGELOG.md) - [Commits](https://github.com/pyo3/pyo3/compare/v0.22.3...v0.22.4) --- updated-dependencies: - dependency-name: pyo3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Bump PyO3 to 0.22.5 * Bump other dependencies as well * List 0.22.5 in Cargo.toml --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ivan Carvalho --- Cargo.lock | 48 ++++++++++++++++++++++++------------------------ Cargo.toml | 2 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f81e20123e..b85898829e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,9 +43,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "byteorder" @@ -185,9 +185,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libm" @@ -335,9 +335,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "petgraph" @@ -351,9 +351,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "portable-atomic-util" @@ -386,18 +386,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" -version = "0.22.3" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15ee168e30649f7f234c3d49ef5a7a6cbf5134289bc46c29ff3155fa3221c225" +checksum = "3d922163ba1f79c04bc49073ba7b32fd5a8d3b76a87c955921234b8e77333c51" dependencies = [ "cfg-if", "hashbrown 0.14.5", @@ -417,9 +417,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.22.3" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e61cef80755fe9e46bb8a0b8f20752ca7676dcc07a5277d8b7768c6172e529b3" +checksum = "bc38c5feeb496c8321091edf3d63e9a6829eab4b863b4a6a65f26f3e9cc6b179" dependencies = [ "once_cell", "target-lexicon", @@ -427,9 +427,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.22.3" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ce096073ec5405f5ee2b8b31f03a68e02aa10d5d4f565eca04acc41931fa1c" +checksum = "94845622d88ae274d2729fcefc850e63d7a3ddff5e3ce11bd88486db9f1d357d" dependencies = [ "libc", "pyo3-build-config", @@ -437,9 +437,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.22.3" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2440c6d12bc8f3ae39f1e775266fa5122fd0c8891ce7520fa6048e683ad3de28" +checksum = "e655aad15e09b94ffdb3ce3d217acf652e26bbc37697ef012f5e5e348c716e5e" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -449,9 +449,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.22.3" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1be962f0e06da8f8465729ea2cb71a416d2257dff56cbe40a70d3e62a93ae5d1" +checksum = "ae1e3f09eecd94618f60a455a23def79f79eba4dc561a97324bf9ac8c6df30ce" dependencies = [ "heck", "proc-macro2", @@ -666,9 +666,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -683,9 +683,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unindent" diff --git a/Cargo.toml b/Cargo.toml index b9826507a1..cb2a23ecea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ smallvec = { version = "1.0", features = ["union"] } rustworkx-core = { path = "rustworkx-core", version = "=0.16.0" } [dependencies.pyo3] -version = "0.22.3" +version = "0.22.5" features = ["abi3-py39", "extension-module", "hashbrown", "num-bigint", "num-complex", "indexmap", "py-clone"] [dependencies.sprs] From 644853d87a31b0b1a92fa97375e04c6df551e2fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Oct 2024 11:09:46 -0400 Subject: [PATCH 09/38] Bump serde_json from 1.0.128 to 1.0.129 (#1297) Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.128 to 1.0.129. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/1.0.128...1.0.129) --- updated-dependencies: - dependency-name: serde_json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b85898829e..b9784bd11d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -633,9 +633,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "6dbcf9b78a125ee667ae19388837dd12294b858d101fdd393cb9d5501ef09eb2" dependencies = [ "itoa", "memchr", From 9e9fcdea2bd17fa77a541c52d30672752b711c38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Oct 2024 15:02:32 +0000 Subject: [PATCH 10/38] Bump serde_json from 1.0.129 to 1.0.132 (#1300) Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.129 to 1.0.132. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/1.0.129...1.0.132) --- updated-dependencies: - dependency-name: serde_json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b9784bd11d..f002234963 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -633,9 +633,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.129" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbcf9b78a125ee667ae19388837dd12294b858d101fdd393cb9d5501ef09eb2" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", From c7af0732a1273623a0c851744bfe05864f4a080a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:14:46 -0400 Subject: [PATCH 11/38] Bump serde from 1.0.210 to 1.0.211 (#1301) Bumps [serde](https://github.com/serde-rs/serde) from 1.0.210 to 1.0.211. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.210...v1.0.211) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f002234963..c5c876630e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -613,18 +613,18 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.211" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "1ac55e59090389fb9f0dd9e0f3c09615afed1d19094284d0b200441f13550793" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.211" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "54be4f245ce16bc58d57ef2716271d0d4519e0f6defa147f6e081005bcb278ff" dependencies = [ "proc-macro2", "quote", @@ -666,9 +666,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" dependencies = [ "proc-macro2", "quote", From 4f0228d171595a607076148ac112cf82b6624a05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Oct 2024 14:48:45 +0000 Subject: [PATCH 12/38] Bump serde from 1.0.211 to 1.0.213 (#1302) Bumps [serde](https://github.com/serde-rs/serde) from 1.0.211 to 1.0.213. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.211...v1.0.213) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5c876630e..4e731b7955 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -613,18 +613,18 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" -version = "1.0.211" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac55e59090389fb9f0dd9e0f3c09615afed1d19094284d0b200441f13550793" +checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.211" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be4f245ce16bc58d57ef2716271d0d4519e0f6defa147f6e081005bcb278ff" +checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" dependencies = [ "proc-macro2", "quote", From 8b5106e66a786de84ca944b7b5158c1e30cad2f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:52:11 -0400 Subject: [PATCH 13/38] Bump quick-xml from 0.36.2 to 0.37.0 (#1303) Bumps [quick-xml](https://github.com/tafia/quick-xml) from 0.36.2 to 0.37.0. - [Release notes](https://github.com/tafia/quick-xml/releases) - [Changelog](https://github.com/tafia/quick-xml/blob/master/Changelog.md) - [Commits](https://github.com/tafia/quick-xml/compare/v0.36.2...v0.37.0) --- updated-dependencies: - dependency-name: quick-xml dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e731b7955..ca67a8c8a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -462,9 +462,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.36.2" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +checksum = "ffbfb3ddf5364c9cfcd65549a1e7b801d0e8d1b14c1a1590a6408aa93cfbfa84" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index cb2a23ecea..e7b5bbd14a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ num-complex = "0.4" num-traits.workspace = true numpy.workspace = true petgraph.workspace = true -quick-xml = "0.36" +quick-xml = "0.37" rand.workspace = true rand_pcg.workspace = true rayon.workspace = true From a895988e66bbfc96bad480f813a2a22195043055 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:15:02 -0400 Subject: [PATCH 14/38] Bump serde from 1.0.213 to 1.0.214 (#1304) Bumps [serde](https://github.com/serde-rs/serde) from 1.0.213 to 1.0.214. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.213...v1.0.214) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca67a8c8a2..bfbf74ef3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -613,18 +613,18 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" -version = "1.0.213" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.213" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", From 8774ccde2014e3a7e145e7bb14b4381b013f2d66 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:03:51 +0000 Subject: [PATCH 15/38] Bump numpy from 0.22.0 to 0.22.1 (#1305) Bumps [numpy](https://github.com/PyO3/rust-numpy) from 0.22.0 to 0.22.1. - [Release notes](https://github.com/PyO3/rust-numpy/releases) - [Changelog](https://github.com/PyO3/rust-numpy/blob/main/CHANGELOG.md) - [Commits](https://github.com/PyO3/rust-numpy/compare/v0.22.0...v0.22.1) --- updated-dependencies: - dependency-name: numpy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bfbf74ef3c..c2f4216af5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -320,13 +320,13 @@ dependencies = [ [[package]] name = "numpy" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf314fca279e6e6ac2126a4ff98f26d88aa4ad06bc68fb6ae5cf4bd706758311" +checksum = "edb929bc0da91a4d85ed6c0a84deaa53d411abfb387fc271124f91bf6b89f14e" dependencies = [ "libc", "ndarray", - "num-complex 0.4.6", + "num-complex 0.2.4", "num-integer", "num-traits", "pyo3", @@ -406,7 +406,7 @@ dependencies = [ "libc", "memoffset", "num-bigint", - "num-complex 0.4.6", + "num-complex 0.2.4", "once_cell", "portable-atomic", "pyo3-build-config", From badf425d4113e55ec4a269b110aa6c2bef8eac15 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2024 12:38:35 -0500 Subject: [PATCH 16/38] Bump pyo3 from 0.22.5 to 0.22.6 (#1310) Bumps [pyo3](https://github.com/pyo3/pyo3) from 0.22.5 to 0.22.6. - [Release notes](https://github.com/pyo3/pyo3/releases) - [Changelog](https://github.com/PyO3/pyo3/blob/v0.22.6/CHANGELOG.md) - [Commits](https://github.com/pyo3/pyo3/compare/v0.22.5...v0.22.6) --- updated-dependencies: - dependency-name: pyo3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c2f4216af5..703e37172a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -326,7 +326,7 @@ checksum = "edb929bc0da91a4d85ed6c0a84deaa53d411abfb387fc271124f91bf6b89f14e" dependencies = [ "libc", "ndarray", - "num-complex 0.2.4", + "num-complex 0.4.6", "num-integer", "num-traits", "pyo3", @@ -395,9 +395,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d922163ba1f79c04bc49073ba7b32fd5a8d3b76a87c955921234b8e77333c51" +checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884" dependencies = [ "cfg-if", "hashbrown 0.14.5", @@ -406,7 +406,7 @@ dependencies = [ "libc", "memoffset", "num-bigint", - "num-complex 0.2.4", + "num-complex 0.4.6", "once_cell", "portable-atomic", "pyo3-build-config", @@ -417,9 +417,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc38c5feeb496c8321091edf3d63e9a6829eab4b863b4a6a65f26f3e9cc6b179" +checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38" dependencies = [ "once_cell", "target-lexicon", @@ -427,9 +427,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94845622d88ae274d2729fcefc850e63d7a3ddff5e3ce11bd88486db9f1d357d" +checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636" dependencies = [ "libc", "pyo3-build-config", @@ -437,9 +437,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e655aad15e09b94ffdb3ce3d217acf652e26bbc37697ef012f5e5e348c716e5e" +checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -449,9 +449,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1e3f09eecd94618f60a455a23def79f79eba4dc561a97324bf9ac8c6df30ce" +checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe" dependencies = [ "heck", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index e7b5bbd14a..bf4b4adda5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ smallvec = { version = "1.0", features = ["union"] } rustworkx-core = { path = "rustworkx-core", version = "=0.16.0" } [dependencies.pyo3] -version = "0.22.5" +version = "0.22.6" features = ["abi3-py39", "extension-module", "hashbrown", "num-bigint", "num-complex", "indexmap", "py-clone"] [dependencies.sprs] From aea56cf23b8a45716c1ae5af15a63b14222c8cf9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2024 09:40:56 -0500 Subject: [PATCH 17/38] Bump serde from 1.0.214 to 1.0.215 (#1313) Bumps [serde](https://github.com/serde-rs/serde) from 1.0.214 to 1.0.215. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.214...v1.0.215) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 703e37172a..acd36b0b74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -613,18 +613,18 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", From 4ed5d9df7b72f7475def99359644a29409d7e52d Mon Sep 17 00:00:00 2001 From: "ZhengYu, Xu" Date: Thu, 14 Nov 2024 23:48:09 +0800 Subject: [PATCH 18/38] Impl __repr__ for custom vector (#1314) --- src/iterators.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/iterators.rs b/src/iterators.rs index 6eecbbaeba..390aa39257 100644 --- a/src/iterators.rs +++ b/src/iterators.rs @@ -546,6 +546,10 @@ macro_rules! custom_vec_iter_impl { Python::with_gil(|py| Ok(format!("{}{}", stringify!($name), self.$data.str(py)?))) } + fn __repr__(&self) -> PyResult { + self.__str__() + } + fn __hash__(&self) -> PyResult { let mut hasher = DefaultHasher::new(); Python::with_gil(|py| PyHash::hash(&self.$data, py, &mut hasher))?; From 78efc368410fe5e65e76b1bd22f702038b1af6e5 Mon Sep 17 00:00:00 2001 From: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:28:55 -0500 Subject: [PATCH 19/38] Remove unused feature from `sprs` dependency (#1299) * Remove unused feature from sprs * Simplify with new Cargo.lock * Fix Cargo.lock --- Cargo.lock | 48 +++++------------------------------------------- Cargo.toml | 1 + 2 files changed, 6 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index acd36b0b74..3684204627 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,32 +15,12 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "alga" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f823d037a7ec6ea2197046bafd4ae150e6bc36f9ca347404f46a46823fa84f2" -dependencies = [ - "approx", - "num-complex 0.2.4", - "num-traits", -] - [[package]] name = "allocator-api2" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" -[[package]] -name = "approx" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" -dependencies = [ - "num-traits", -] - [[package]] name = "autocfg" version = "1.4.0" @@ -189,12 +169,6 @@ version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - [[package]] name = "matrixmultiply" version = "0.3.9" @@ -227,7 +201,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" dependencies = [ "matrixmultiply", - "num-complex 0.4.6", + "num-complex", "num-integer", "num-traits", "portable-atomic", @@ -270,16 +244,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-complex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" -dependencies = [ - "autocfg", - "num-traits", -] - [[package]] name = "num-complex" version = "0.4.6" @@ -305,7 +269,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -326,7 +289,7 @@ checksum = "edb929bc0da91a4d85ed6c0a84deaa53d411abfb387fc271124f91bf6b89f14e" dependencies = [ "libc", "ndarray", - "num-complex 0.4.6", + "num-complex", "num-integer", "num-traits", "pyo3", @@ -406,7 +369,7 @@ dependencies = [ "libc", "memoffset", "num-bigint", - "num-complex 0.4.6", + "num-complex", "once_cell", "portable-atomic", "pyo3-build-config", @@ -571,7 +534,7 @@ dependencies = [ "ndarray", "ndarray-stats", "num-bigint", - "num-complex 0.4.6", + "num-complex", "num-traits", "numpy", "petgraph", @@ -655,9 +618,8 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "704ef26d974e8a452313ed629828cd9d4e4fa34667ca1ad9d6b1fffa43c6e166" dependencies = [ - "alga", "ndarray", - "num-complex 0.4.6", + "num-complex", "num-traits", "num_cpus", "rayon", diff --git a/Cargo.toml b/Cargo.toml index bf4b4adda5..3bffc9f379 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ features = ["abi3-py39", "extension-module", "hashbrown", "num-bigint", "num-com [dependencies.sprs] version = "^0.11" +default-features = false features = ["multi_thread"] [profile.release] From cb897b9cb31fe91c34b3abd40a8378a58f145324 Mon Sep 17 00:00:00 2001 From: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:36:07 -0500 Subject: [PATCH 20/38] Add Python 3.13 to CI and to release (#1291) * Add 3.13 support * Enable pillow for 3.13 tests * Fix stub tests * Bump cibuildwheel to build with candidate and not rc * Remove win32 builds * Add 3.13 to supported list * Re-introduce win32 --- .github/workflows/main.yml | 6 +++--- .github/workflows/wheels.yml | 12 ++++++------ constraints.txt | 3 +-- noxfile.py | 2 +- rustworkx/visualization/graphviz.pyi | 2 +- setup.py | 1 + 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 907c964e17..5579fb21a6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -58,7 +58,7 @@ jobs: strategy: matrix: rust: [stable] - python-version: [3.9, "3.10", "3.11", "3.12"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] platform: [ { os: "macOS-13", python-architecture: "x64", rust-target: "x86_64-apple-darwin" }, { os: "macOS-14", python-architecture: "arm64", rust-target: "aarch64-apple-darwin" }, @@ -73,7 +73,7 @@ jobs: msrv: "MSRV" # Test future versions of Rust and Python - rust: beta - python-version: "3.13-dev" + python-version: "3.13" # upgrade to 3.14-dev when the release candidate is available platform: { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu" } msrv: "Beta" # Exclude python 3.9 on arm64 until actions/setup-python#808 is resolved @@ -107,7 +107,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9, "3.10", "3.11", "3.12"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 5b1a9e4636..4fa5cc7f7c 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -57,7 +57,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable - name: Install cibuildwheel run: | - python -m pip install cibuildwheel==2.17.0 + python -m pip install cibuildwheel==2.21.3 - name: Build wheels run: | python -m cibuildwheel --output-dir wheelhouse @@ -105,7 +105,7 @@ jobs: platforms: all - name: Install cibuildwheel run: | - python -m pip install cibuildwheel==2.17.0 + python -m pip install cibuildwheel==2.21.3 - name: Build wheels run: | python -m cibuildwheel --output-dir wheelhouse @@ -143,7 +143,7 @@ jobs: platforms: all - name: Install cibuildwheel run: | - python -m pip install cibuildwheel==2.17.0 + python -m pip install cibuildwheel==2.21.3 - name: Build wheels run: | python -m cibuildwheel --output-dir wheelhouse @@ -182,7 +182,7 @@ jobs: platforms: all - name: Install cibuildwheel run: | - python -m pip install cibuildwheel==2.17.0 + python -m pip install cibuildwheel==2.21.3 - name: Build wheels run: | python -m cibuildwheel --output-dir wheelhouse @@ -220,7 +220,7 @@ jobs: platforms: all - name: Install cibuildwheel run: | - python -m pip install cibuildwheel==2.17.0 + python -m pip install cibuildwheel==2.21.3 - name: Build wheels run: | python -m cibuildwheel --output-dir wheelhouse @@ -253,7 +253,7 @@ jobs: run: rustup default stable-i686-pc-windows-msvc - name: Install cibuildwheel run: | - python -m pip install cibuildwheel==2.17.0 + python -m pip install cibuildwheel==2.21.3 - name: Build wheels run: | python -m cibuildwheel --output-dir wheelhouse diff --git a/constraints.txt b/constraints.txt index 4927e21b09..b445ab540f 100644 --- a/constraints.txt +++ b/constraints.txt @@ -1,3 +1,2 @@ -decorator==4.4.2 -pillow<10.0.0;python_version<'3.13' +pillow>=10.1 lxml==5.1.1 diff --git a/noxfile.py b/noxfile.py index 1b62212542..d7d5229cb2 100644 --- a/noxfile.py +++ b/noxfile.py @@ -24,7 +24,7 @@ def install_rustworkx(session): session.install(*deps) - session.install(".", "-c", "constraints.txt") + session.install(".[all]", "-c", "constraints.txt") # We define a common base such that -e test triggers a test with the current # Python version of the interpreter and -e test_with_version launches diff --git a/rustworkx/visualization/graphviz.pyi b/rustworkx/visualization/graphviz.pyi index f37bd5bcf4..f1a9a41e3f 100644 --- a/rustworkx/visualization/graphviz.pyi +++ b/rustworkx/visualization/graphviz.pyi @@ -10,7 +10,7 @@ import typing from rustworkx.rustworkx import PyGraph, PyDiGraph if typing.TYPE_CHECKING: - from PIL import Image # type: ignore + from PIL.Image import Image # type: ignore _S = typing.TypeVar("_S") _T = typing.TypeVar("_T") diff --git a/setup.py b/setup.py index 16f9dcdec1..a3d7040859 100644 --- a/setup.py +++ b/setup.py @@ -70,6 +70,7 @@ def readme(): "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", From d9fbe83c4abb8fc3b228be79a3ca8b7d9d1a9c64 Mon Sep 17 00:00:00 2001 From: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:50:30 -0500 Subject: [PATCH 21/38] Add Zachary's Karate Club (#1280) * Karate club draft * Add Zachary's data * Consider weight for Karate Club * Add tests * Add karate_club_graph signature * Fix clippy * Update rustworkx-core/src/generators/karate_club.rs Co-authored-by: Alexander Ivrii * Add labels and documentation * Test node labels * Add simple Rust docstring * Add release notes * Black changes * Update test to be independent * Apply suggestions from code review Co-authored-by: Matthew Treinish * Format and u8 * Ignore ruff for that file * Use adjacency list * Add some documentation * Update tests/graph/test_karate.py --------- Co-authored-by: Alexander Ivrii Co-authored-by: Matthew Treinish --- .../notes/karate-club-35708b3838689a0b.yaml | 14 + rustworkx-core/src/generators/karate_club.rs | 126 ++++++ rustworkx-core/src/generators/mod.rs | 2 + rustworkx/generators/__init__.pyi | 1 + src/generators.rs | 54 +++ tests/graph/test_karate.py | 402 ++++++++++++++++++ 6 files changed, 599 insertions(+) create mode 100644 releasenotes/notes/karate-club-35708b3838689a0b.yaml create mode 100644 rustworkx-core/src/generators/karate_club.rs create mode 100644 tests/graph/test_karate.py diff --git a/releasenotes/notes/karate-club-35708b3838689a0b.yaml b/releasenotes/notes/karate-club-35708b3838689a0b.yaml new file mode 100644 index 0000000000..5d47a385a6 --- /dev/null +++ b/releasenotes/notes/karate-club-35708b3838689a0b.yaml @@ -0,0 +1,14 @@ +--- +features: + - | + Added a new function, :func:`~rustworkx.generators.karate_club_graph` that + returns Zachary's Karate Club graph, commonly found in social network examples. + + .. jupyter-execute:: + + import rustworkx.generators + from rustworkx.visualization import mpl_draw + + graph = rustworkx.generators.karate_club_graph() + layout = rustworkx.circular_layout(graph) + mpl_draw(graph, pos=layout) diff --git a/rustworkx-core/src/generators/karate_club.rs b/rustworkx-core/src/generators/karate_club.rs new file mode 100644 index 0000000000..d0e0ac495e --- /dev/null +++ b/rustworkx-core/src/generators/karate_club.rs @@ -0,0 +1,126 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +use std::hash::Hash; + +use petgraph::data::{Build, Create}; +use petgraph::visit::{Data, NodeIndexable}; + +/// Generates Zachary's Karate Club graph. +/// +/// Zachary's Karate Club graph is a well-known social network that represents +/// the relations between 34 members of a karate club. +/// Arguments: +/// +/// * `default_node_weight` - A callable that will receive a boolean, indicating +/// if a node is part of Mr Hi's faction (True) or the Officer's faction (false). +/// It shoudl return the node weight according to the desired type. +/// * `default_edge_weight` - A callable that will receive the integer representing +/// the strenght of the relation between two nodes. It should return the edge +/// weight according to the desired type. +/// +pub fn karate_club_graph(mut default_node_weight: F, mut default_edge_weight: H) -> G +where + G: Build + Create + Data + NodeIndexable, + F: FnMut(bool) -> T, + H: FnMut(usize) -> M, + G::NodeId: Eq + Hash, +{ + const N: usize = 34; + const M: usize = 78; + let mr_hi_members: [u8; 17] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 16, 17, 19, 21]; + let membership: std::collections::HashSet = mr_hi_members.into_iter().collect(); + + let adjacency_list: Vec> = vec![ + vec![], + vec![(0, 4)], + vec![(0, 5), (1, 6)], + vec![(0, 3), (1, 3), (2, 3)], + vec![(0, 3)], + vec![(0, 3)], + vec![(0, 3), (4, 2), (5, 5)], + vec![(0, 2), (1, 4), (2, 4), (3, 3)], + vec![(0, 2), (2, 5)], + vec![(2, 1)], + vec![(0, 2), (4, 3), (5, 3)], + vec![(0, 3)], + vec![(0, 1), (3, 3)], + vec![(0, 3), (1, 5), (2, 3), (3, 3)], + vec![], + vec![], + vec![(5, 3), (6, 3)], + vec![(0, 2), (1, 1)], + vec![], + vec![(0, 2), (1, 2)], + vec![], + vec![(0, 2), (1, 2)], + vec![], + vec![], + vec![], + vec![(23, 5), (24, 2)], + vec![], + vec![(2, 2), (23, 4), (24, 3)], + vec![(2, 2)], + vec![(23, 3), (26, 4)], + vec![(1, 2), (8, 3)], + vec![(0, 2), (24, 2), (25, 7), (28, 2)], + vec![ + (2, 2), + (8, 3), + (14, 3), + (15, 3), + (18, 1), + (20, 3), + (22, 2), + (23, 5), + (29, 4), + (30, 3), + (31, 4), + ], + vec![ + (8, 4), + (9, 2), + (13, 3), + (14, 2), + (15, 4), + (18, 2), + (19, 1), + (20, 1), + (23, 4), + (26, 2), + (27, 4), + (28, 2), + (29, 2), + (30, 3), + (31, 4), + (32, 5), + (22, 3), + ], + ]; + + let mut graph = G::with_capacity(N, M); + + let mut node_indices = Vec::with_capacity(N); + for (row, neighbors) in adjacency_list.into_iter().enumerate() { + let node_id = graph.add_node(default_node_weight(membership.contains(&(row as u8)))); + node_indices.push(node_id); + + for (neighbor, weight) in neighbors.into_iter() { + graph.add_edge( + node_indices[neighbor], + node_indices[row], + default_edge_weight(weight), + ); + } + } + graph +} diff --git a/rustworkx-core/src/generators/mod.rs b/rustworkx-core/src/generators/mod.rs index 6c0af1ace3..d7a5ddd837 100644 --- a/rustworkx-core/src/generators/mod.rs +++ b/rustworkx-core/src/generators/mod.rs @@ -22,6 +22,7 @@ mod grid_graph; mod heavy_hex_graph; mod heavy_square_graph; mod hexagonal_lattice_graph; +mod karate_club; mod lollipop_graph; mod path_graph; mod petersen_graph; @@ -55,6 +56,7 @@ pub use grid_graph::grid_graph; pub use heavy_hex_graph::heavy_hex_graph; pub use heavy_square_graph::heavy_square_graph; pub use hexagonal_lattice_graph::{hexagonal_lattice_graph, hexagonal_lattice_graph_weighted}; +pub use karate_club::karate_club_graph; pub use lollipop_graph::lollipop_graph; pub use path_graph::path_graph; pub use petersen_graph::petersen_graph; diff --git a/rustworkx/generators/__init__.pyi b/rustworkx/generators/__init__.pyi index 440db39407..6136cb3de1 100644 --- a/rustworkx/generators/__init__.pyi +++ b/rustworkx/generators/__init__.pyi @@ -132,3 +132,4 @@ def directed_complete_graph( multigraph: bool = ..., ) -> PyDiGraph: ... def dorogovtsev_goltsev_mendes_graph(n: int) -> PyGraph: ... +def karate_club_graph(multigraph: bool = ...) -> PyGraph: ... diff --git a/src/generators.rs b/src/generators.rs index ac8088d12b..82b75b8a4d 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -1715,6 +1715,59 @@ pub fn dorogovtsev_goltsev_mendes_graph(py: Python, n: usize) -> PyResult PyResult { + let default_node_fn = |w: bool| match w { + true => "Mr. Hi".to_object(py), + false => "Officer".to_object(py), + }; + let default_edge_fn = |w: usize| (w as f64).to_object(py); + let graph: StablePyGraph = + core_generators::karate_club_graph(default_node_fn, default_edge_fn); + Ok(graph::PyGraph { + graph, + node_removed: false, + multigraph, + attrs: py.None(), + }) +} + #[pymodule] pub fn generators(_py: Python, m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(cycle_graph))?; @@ -1744,5 +1797,6 @@ pub fn generators(_py: Python, m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(complete_graph))?; m.add_wrapped(wrap_pyfunction!(directed_complete_graph))?; m.add_wrapped(wrap_pyfunction!(dorogovtsev_goltsev_mendes_graph))?; + m.add_wrapped(wrap_pyfunction!(karate_club_graph))?; Ok(()) } diff --git a/tests/graph/test_karate.py b/tests/graph/test_karate.py new file mode 100644 index 0000000000..7bc7e4e3ee --- /dev/null +++ b/tests/graph/test_karate.py @@ -0,0 +1,402 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import unittest +import tempfile + +import rustworkx as rx + + +class TestKarate(unittest.TestCase): + def test_isomorphic_to_networkx(self): + def node_matcher(a, b): + if isinstance(a, dict): + ( + a, + b, + ) = ( + b, + a, + ) + return a == b["club"] + + def edge_matcher(a, b): + if isinstance(a, dict): + ( + a, + b, + ) = ( + b, + a, + ) + return a == b["weight"] + + with tempfile.NamedTemporaryFile("wt") as fd: + fd.write(karate_xml) + fd.flush() + expected = rx.read_graphml(fd.name)[0] + + graph = rx.generators.karate_club_graph() + + self.assertTrue( + rx.is_isomorphic(graph, expected, node_matcher=node_matcher, edge_matcher=edge_matcher) + ) + + +# ruff: noqa: E501 +# Output of +# import networkx as nx +# nx.write_graphml_lxml(nx.karate_club_graph(), open("karate.xml", "w")) +karate_xml = """ + + + + +Zachary's Karate Club + + Mr. Hi + + + Mr. Hi + + + Mr. Hi + + + Mr. Hi + + + Mr. Hi + + + Mr. Hi + + + Mr. Hi + + + Mr. Hi + + + Mr. Hi + + + Officer + + + Mr. Hi + + + Mr. Hi + + + Mr. Hi + + + Mr. Hi + + + Officer + + + Officer + + + Mr. Hi + + + Mr. Hi + + + Officer + + + Mr. Hi + + + Officer + + + Mr. Hi + + + Officer + + + Officer + + + Officer + + + Officer + + + Officer + + + Officer + + + Officer + + + Officer + + + Officer + + + Officer + + + Officer + + + Officer + + + 4 + + + 5 + + + 3 + + + 3 + + + 3 + + + 3 + + + 2 + + + 2 + + + 2 + + + 3 + + + 1 + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + 6 + + + 3 + + + 4 + + + 5 + + + 1 + + + 2 + + + 2 + + + 2 + + + 3 + + + 4 + + + 5 + + + 1 + + + 3 + + + 2 + + + 2 + + + 2 + + + 3 + + + 3 + + + 3 + + + 2 + + + 3 + + + 5 + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + 4 + + + 2 + + + 3 + + + 3 + + + 2 + + + 3 + + + 4 + + + 1 + + + 2 + + + 1 + + + 3 + + + 1 + + + 2 + + + 3 + + + 5 + + + 4 + + + 3 + + + 5 + + + 4 + + + 2 + + + 3 + + + 2 + + + 7 + + + 4 + + + 2 + + + 4 + + + 2 + + + 2 + + + 4 + + + 2 + + + 3 + + + 3 + + + 4 + + + 4 + + + 5 + + +""" From 44d9fb068cb7090ec4bc76296cbef7dcb51f99a2 Mon Sep 17 00:00:00 2001 From: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com> Date: Fri, 15 Nov 2024 22:15:46 -0500 Subject: [PATCH 22/38] Fix bug where edge colors are randomly shuffled in `mpl_draw` (#1312) * Try to make edge_pos iteration deterministic * Fix minor bug * Add release note --- .../notes/correct-edge-colors-e082e1761e1c060b.yaml | 8 ++++++++ rustworkx/visualization/matplotlib.py | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/correct-edge-colors-e082e1761e1c060b.yaml diff --git a/releasenotes/notes/correct-edge-colors-e082e1761e1c060b.yaml b/releasenotes/notes/correct-edge-colors-e082e1761e1c060b.yaml new file mode 100644 index 0000000000..b0df1801ee --- /dev/null +++ b/releasenotes/notes/correct-edge-colors-e082e1761e1c060b.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Fixed a bug introduced in version 0.15 where the edge colors specified as + a list in calls to :func:`~rustworkx.visualization.mpl_draw` were not + having their order respected. Instead, the order of the colors was + being shuffled. This has been restored and now the behavior should + match that of 0.14. \ No newline at end of file diff --git a/rustworkx/visualization/matplotlib.py b/rustworkx/visualization/matplotlib.py index 559756a13b..bd06c243b8 100644 --- a/rustworkx/visualization/matplotlib.py +++ b/rustworkx/visualization/matplotlib.py @@ -636,9 +636,10 @@ def draw_edges( edge_color = "k" # set edge positions - edge_pos = set() + edge_pos_keys = dict() for e in edge_list: - edge_pos.add((tuple(pos[e[0]]), tuple(pos[e[1]]))) + edge_pos_keys[(tuple(pos[e[0]]), tuple(pos[e[1]]))] = None + edge_pos = edge_pos_keys.keys() # Check if edge_color is an array of floats and map to edge_cmap. # This is the only case handled differently from matplotlib From 2cf35c0eb44ce380a676456fbe8ca3277ed272a2 Mon Sep 17 00:00:00 2001 From: Gohlub <62673775+Gohlub@users.noreply.github.com> Date: Sun, 17 Nov 2024 22:37:14 -0500 Subject: [PATCH 23/38] Degree centrality implementation (#1306) * Add degree centrality Co-authored-by: Gohlub 62673775+Gohlub@users.noreply.github.com Co-authored-by: onsali 64367557+onsali@users.noreply.github.com * Rename method in rustworkx-core * Add universal functions * Fix typo * Fix method names in tests * Add type stubs * Remove unnecessary diff * Handle graphs with removed edges * Fix rustdoc tests * Fix type stubs --------- Co-authored-by: Ivan Carvalho --- .gitignore | 1 + rustworkx-core/src/centrality.rs | 68 ++++++++++++++++++++++ rustworkx/__init__.py | 14 +++++ rustworkx/__init__.pyi | 7 +++ rustworkx/rustworkx.pyi | 16 ++++++ src/centrality.rs | 96 ++++++++++++++++++++++++++++++++ src/lib.rs | 4 ++ tests/digraph/test_centrality.py | 95 +++++++++++++++++++++++++++++++ tests/graph/test_centrality.py | 63 +++++++++++++++++++++ 9 files changed, 364 insertions(+) diff --git a/.gitignore b/.gitignore index 6afef31f50..6e09a4a58b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ retworkx/*pyd *.jpg **/*.so retworkx-core/Cargo.lock +**/.DS_Store diff --git a/rustworkx-core/src/centrality.rs b/rustworkx-core/src/centrality.rs index 09e21affac..47dfd21367 100644 --- a/rustworkx-core/src/centrality.rs +++ b/rustworkx-core/src/centrality.rs @@ -335,6 +335,74 @@ fn accumulate_edges( } } } +/// Compute the degree centrality of all nodes in a graph. +/// +/// For undirected graphs, this calculates the normalized degree for each node. +/// For directed graphs, this calculates the normalized out-degree for each node. +/// +/// Arguments: +/// +/// * `graph` - The graph object to calculate degree centrality for +/// +/// # Example +/// ```rust +/// use rustworkx_core::petgraph::graph::{UnGraph, DiGraph}; +/// use rustworkx_core::centrality::degree_centrality; +/// +/// // Undirected graph example +/// let graph = UnGraph::::from_edges(&[ +/// (0, 1), (1, 2), (2, 3), (3, 0) +/// ]); +/// let centrality = degree_centrality(&graph, None); +/// +/// // Directed graph example +/// let digraph = DiGraph::::from_edges(&[ +/// (0, 1), (1, 2), (2, 3), (3, 0), (0, 2), (1, 3) +/// ]); +/// let centrality = degree_centrality(&digraph, None); +/// ``` +pub fn degree_centrality(graph: G, direction: Option) -> Vec +where + G: NodeIndexable + + IntoNodeIdentifiers + + IntoNeighbors + + IntoNeighborsDirected + + NodeCount + + GraphProp, + G::NodeId: Eq + Hash, +{ + let node_count = graph.node_count() as f64; + let mut centrality = vec![0.0; graph.node_bound()]; + + for node in graph.node_identifiers() { + let (degree, normalization) = match (graph.is_directed(), direction) { + (true, None) => { + let out_degree = graph + .neighbors_directed(node, petgraph::Direction::Outgoing) + .count() as f64; + let in_degree = graph + .neighbors_directed(node, petgraph::Direction::Incoming) + .count() as f64; + let total = in_degree + out_degree; + // Use 2(n-1) normalization only if this is a complete graph + let norm = if total == 2.0 * (node_count - 1.0) { + 2.0 * (node_count - 1.0) + } else { + node_count - 1.0 + }; + (total, norm) + } + (true, Some(dir)) => ( + graph.neighbors_directed(node, dir).count() as f64, + node_count - 1.0, + ), + (false, _) => (graph.neighbors(node).count() as f64, node_count - 1.0), + }; + centrality[graph.to_index(node)] = degree / normalization; + } + + centrality +} struct ShortestPathData where diff --git a/rustworkx/__init__.py b/rustworkx/__init__.py index 2943017fcc..a470c71f07 100644 --- a/rustworkx/__init__.py +++ b/rustworkx/__init__.py @@ -1184,6 +1184,20 @@ def closeness_centrality(graph, wf_improved=True): raise TypeError("Invalid input type %s for graph" % type(graph)) +@_rustworkx_dispatch +def degree_centrality(graph): + r"""Compute the degree centrality of each node in a graph object. + + :param graph: The input graph. Can either be a + :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`. + + :returns: a read-only dict-like object whose keys are edges and values are the + degree centrality score for each node. + :rtype: CentralityMapping + """ + raise TypeError("Invalid input type %s for graph" % type(graph)) + + @_rustworkx_dispatch def edge_betweenness_centrality(graph, normalized=True, parallel_threshold=50): r"""Compute the edge betweenness centrality of all edges in a graph. diff --git a/rustworkx/__init__.pyi b/rustworkx/__init__.pyi index 63f5f4be4b..2152b4f60b 100644 --- a/rustworkx/__init__.pyi +++ b/rustworkx/__init__.pyi @@ -50,6 +50,10 @@ from .rustworkx import digraph_closeness_centrality as digraph_closeness_central from .rustworkx import graph_closeness_centrality as graph_closeness_centrality from .rustworkx import digraph_katz_centrality as digraph_katz_centrality from .rustworkx import graph_katz_centrality as graph_katz_centrality +from .rustworkx import digraph_degree_centrality as digraph_degree_centrality +from .rustworkx import graph_degree_centrality as graph_degree_centrality +from .rustworkx import in_degree_centrality as in_degree_centrality +from .rustworkx import out_degree_centrality as out_degree_centrality from .rustworkx import graph_greedy_color as graph_greedy_color from .rustworkx import graph_greedy_edge_color as graph_greedy_edge_color from .rustworkx import graph_is_bipartite as graph_is_bipartite @@ -484,6 +488,9 @@ def betweenness_centrality( def closeness_centrality( graph: PyGraph[_S, _T] | PyDiGraph[_S, _T], wf_improved: bool = ... ) -> CentralityMapping: ... +def degree_centrality( + graph: PyGraph[_S, _T] | PyDiGraph[_S, _T], +) -> CentralityMapping: ... def edge_betweenness_centrality( graph: PyGraph[_S, _T] | PyDiGraph[_S, _T], normalized: bool = ..., diff --git a/rustworkx/rustworkx.pyi b/rustworkx/rustworkx.pyi index 6e690a2c2c..69dcac8dd1 100644 --- a/rustworkx/rustworkx.pyi +++ b/rustworkx/rustworkx.pyi @@ -124,6 +124,22 @@ def graph_closeness_centrality( graph: PyGraph[_S, _T], wf_improved: bool = ..., ) -> CentralityMapping: ... +def digraph_degree_centrality( + graph: PyDiGraph[_S, _T], + /, +) -> CentralityMapping: ... +def in_degree_centrality( + graph: PyDiGraph[_S, _T], + /, +) -> CentralityMapping: ... +def out_degree_centrality( + graph: PyDiGraph[_S, _T], + /, +) -> CentralityMapping: ... +def graph_degree_centrality( + graph: PyGraph[_S, _T], + /, +) -> CentralityMapping: ... def digraph_katz_centrality( graph: PyDiGraph[_S, _T], /, diff --git a/src/centrality.rs b/src/centrality.rs index ca055cec37..3db8bfd7ca 100644 --- a/src/centrality.rs +++ b/src/centrality.rs @@ -165,6 +165,102 @@ pub fn digraph_betweenness_centrality( } } +/// Compute the degree centrality for nodes in a PyGraph. +/// +/// Degree centrality assigns an importance score based simply on the number of edges held by each node. +/// +/// :param PyGraph graph: The input graph +/// +/// :returns: a read-only dict-like object whose keys are the node indices and values are the +/// centrality score for each node. +/// :rtype: CentralityMapping +#[pyfunction(signature = (graph,))] +#[pyo3(text_signature = "(graph, /,)")] +pub fn graph_degree_centrality(graph: &graph::PyGraph) -> PyResult { + let centrality = centrality::degree_centrality(&graph.graph, None); + + Ok(CentralityMapping { + centralities: graph + .graph + .node_indices() + .map(|i| (i.index(), centrality[i.index()])) + .collect(), + }) +} + +/// Compute the degree centrality for nodes in a PyDiGraph. +/// +/// Degree centrality assigns an importance score based simply on the number of edges held by each node. +/// This function computes the TOTAL (in + out) degree centrality. +/// +/// :param PyDiGraph graph: The input graph +/// +/// :returns: a read-only dict-like object whose keys are the node indices and values are the +/// centrality score for each node. +/// :rtype: CentralityMapping +#[pyfunction(signature = (graph,))] +#[pyo3(text_signature = "(graph, /,)")] +pub fn digraph_degree_centrality(graph: &digraph::PyDiGraph) -> PyResult { + let centrality = centrality::degree_centrality(&graph.graph, None); + + Ok(CentralityMapping { + centralities: graph + .graph + .node_indices() + .map(|i| (i.index(), centrality[i.index()])) + .collect(), + }) +} +/// Compute the in-degree centrality for nodes in a PyDiGraph. +/// +/// In-degree centrality assigns an importance score based on the number of incoming edges +/// to each node. +/// +/// :param PyDiGraph graph: The input graph +/// +/// :returns: a read-only dict-like object whose keys are the node indices and values are the +/// centrality score for each node. +/// :rtype: CentralityMapping +#[pyfunction(signature = (graph,))] +#[pyo3(text_signature = "(graph, /)")] +pub fn in_degree_centrality(graph: &digraph::PyDiGraph) -> PyResult { + let centrality = + centrality::degree_centrality(&graph.graph, Some(petgraph::Direction::Incoming)); + + Ok(CentralityMapping { + centralities: graph + .graph + .node_indices() + .map(|i| (i.index(), centrality[i.index()])) + .collect(), + }) +} + +/// Compute the out-degree centrality for nodes in a PyDiGraph. +/// +/// Out-degree centrality assigns an importance score based on the number of outgoing edges +/// from each node. +/// +/// :param PyDiGraph graph: The input graph +/// +/// :returns: a read-only dict-like object whose keys are the node indices and values are the +/// centrality score for each node. +/// :rtype: CentralityMapping +#[pyfunction(signature = (graph,))] +#[pyo3(text_signature = "(graph, /,)")] +pub fn out_degree_centrality(graph: &digraph::PyDiGraph) -> PyResult { + let centrality = + centrality::degree_centrality(&graph.graph, Some(petgraph::Direction::Outgoing)); + + Ok(CentralityMapping { + centralities: graph + .graph + .node_indices() + .map(|i| (i.index(), centrality[i.index()])) + .collect(), + }) +} + /// Compute the closeness centrality of each node in a :class:`~.PyGraph` object. /// /// The closeness centrality of a node :math:`u` is defined as the diff --git a/src/lib.rs b/src/lib.rs index 79f183462f..4d83e41587 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -533,6 +533,10 @@ fn rustworkx(py: Python<'_>, m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(digraph_eigenvector_centrality))?; m.add_wrapped(wrap_pyfunction!(graph_katz_centrality))?; m.add_wrapped(wrap_pyfunction!(digraph_katz_centrality))?; + m.add_wrapped(wrap_pyfunction!(graph_degree_centrality))?; + m.add_wrapped(wrap_pyfunction!(digraph_degree_centrality))?; + m.add_wrapped(wrap_pyfunction!(in_degree_centrality))?; + m.add_wrapped(wrap_pyfunction!(out_degree_centrality))?; m.add_wrapped(wrap_pyfunction!(graph_astar_shortest_path))?; m.add_wrapped(wrap_pyfunction!(digraph_astar_shortest_path))?; m.add_wrapped(wrap_pyfunction!(graph_greedy_color))?; diff --git a/tests/digraph/test_centrality.py b/tests/digraph/test_centrality.py index 3ab12e465b..03de54fb58 100644 --- a/tests/digraph/test_centrality.py +++ b/tests/digraph/test_centrality.py @@ -241,3 +241,98 @@ def test_path_graph_unnormalized(self): expected = {0: 4.0, 1: 6.0, 2: 6.0, 3: 4.0} for k, v in centrality.items(): self.assertAlmostEqual(v, expected[k]) + + +class TestDiGraphDegreeCentrality(unittest.TestCase): + def setUp(self): + self.graph = rustworkx.PyDiGraph() + self.a = self.graph.add_node("A") + self.b = self.graph.add_node("B") + self.c = self.graph.add_node("C") + self.d = self.graph.add_node("D") + edge_list = [ + (self.a, self.b, 1), + (self.b, self.c, 1), + (self.c, self.d, 1), + (self.a, self.c, 1), # Additional edge + ] + self.graph.add_edges_from(edge_list) + + def test_degree_centrality(self): + centrality = rustworkx.degree_centrality(self.graph) + expected = { + 0: 2 / 3, # 2 total edges / 3 + 1: 2 / 3, # 2 total edges / 3 + 2: 1.0, # 3 total edges / 3 + 3: 1 / 3, # 1 total edge / 3 + } + for k, v in centrality.items(): + self.assertAlmostEqual(v, expected[k]) + + def test_in_degree_centrality(self): + centrality = rustworkx.in_degree_centrality(self.graph) + expected = { + 0: 0.0, # 0 incoming edges + 1: 1 / 3, # 1 incoming edge + 2: 2 / 3, # 2 incoming edges + 3: 1 / 3, # 1 incoming edge + } + for k, v in centrality.items(): + self.assertAlmostEqual(v, expected[k]) + + def test_out_degree_centrality(self): + centrality = rustworkx.out_degree_centrality(self.graph) + expected = { + 0: 2 / 3, # 2 outgoing edges + 1: 1 / 3, # 1 outgoing edge + 2: 1 / 3, # 1 outgoing edge + 3: 0.0, # 0 outgoing edges + } + for k, v in centrality.items(): + self.assertAlmostEqual(v, expected[k]) + + def test_degree_centrality_complete_digraph(self): + graph = rustworkx.generators.directed_complete_graph(5) + centrality = rustworkx.degree_centrality(graph) + expected = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0} + for k, v in centrality.items(): + self.assertAlmostEqual(v, expected[k]) + + def test_degree_centrality_directed_path(self): + graph = rustworkx.generators.directed_path_graph(5) + centrality = rustworkx.degree_centrality(graph) + expected = { + 0: 1 / 4, # 1 total edge (out only) / 4 + 1: 2 / 4, # 2 total edges (1 in + 1 out) / 4 + 2: 2 / 4, # 2 total edges (1 in + 1 out) / 4 + 3: 2 / 4, # 2 total edges (1 in + 1 out) / 4 + 4: 1 / 4, # 1 total edge (in only) / 4 + } + for k, v in centrality.items(): + self.assertAlmostEqual(v, expected[k]) + + def test_in_degree_centrality_directed_path(self): + graph = rustworkx.generators.directed_path_graph(5) + centrality = rustworkx.in_degree_centrality(graph) + expected = { + 0: 0.0, # 0 incoming edges + 1: 1 / 4, # 1 incoming edge + 2: 1 / 4, # 1 incoming edge + 3: 1 / 4, # 1 incoming edge + 4: 1 / 4, # 1 incoming edge + } + for k, v in centrality.items(): + self.assertAlmostEqual(v, expected[k]) + + def test_out_degree_centrality_directed_path(self): + graph = rustworkx.generators.directed_path_graph(5) + centrality = rustworkx.out_degree_centrality(graph) + expected = { + 0: 1 / 4, # 1 outgoing edge + 1: 1 / 4, # 1 outgoing edge + 2: 1 / 4, # 1 outgoing edge + 3: 1 / 4, # 1 outgoing edge + 4: 0.0, # 0 outgoing edges + } + for k, v in centrality.items(): + self.assertAlmostEqual(v, expected[k]) diff --git a/tests/graph/test_centrality.py b/tests/graph/test_centrality.py index 12ed67457b..3fe3db5f30 100644 --- a/tests/graph/test_centrality.py +++ b/tests/graph/test_centrality.py @@ -230,3 +230,66 @@ def test_custom_graph_unnormalized(self): expected = {0: 9, 1: 9, 2: 12, 3: 15, 4: 11, 5: 14, 6: 10, 7: 13, 8: 9, 9: 9} for k, v in centrality.items(): self.assertAlmostEqual(v, expected[k]) + + +class TestGraphDegreeCentrality(unittest.TestCase): + def setUp(self): + self.graph = rustworkx.PyGraph() + self.a = self.graph.add_node("A") + self.b = self.graph.add_node("B") + self.c = self.graph.add_node("C") + self.d = self.graph.add_node("D") + edge_list = [ + (self.a, self.b, 1), + (self.b, self.c, 1), + (self.c, self.d, 1), + ] + self.graph.add_edges_from(edge_list) + + def test_degree_centrality(self): + centrality = rustworkx.degree_centrality(self.graph) + expected = { + 0: 1 / 3, # Node A has 1 edge, normalized by (n-1) = 3 + 1: 2 / 3, # Node B has 2 edges + 2: 2 / 3, # Node C has 2 edges + 3: 1 / 3, # Node D has 1 edge + } + self.assertEqual(expected, centrality) + + def test_degree_centrality_complete_graph(self): + graph = rustworkx.generators.complete_graph(5) + centrality = rustworkx.degree_centrality(graph) + expected = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0} + self.assertEqual(expected, centrality) + + def test_degree_centrality_star_graph(self): + graph = rustworkx.generators.star_graph(5) + centrality = rustworkx.degree_centrality(graph) + expected = {0: 1.0, 1: 0.25, 2: 0.25, 3: 0.25, 4: 0.25} + self.assertEqual(expected, centrality) + + def test_degree_centrality_empty_graph(self): + graph = rustworkx.PyGraph() + centrality = rustworkx.degree_centrality(graph) + expected = {} + self.assertEqual(expected, centrality) + + def test_degree_centrality_multigraph(self): + graph = rustworkx.PyGraph() + a = graph.add_node("A") + b = graph.add_node("B") + c = graph.add_node("C") + edge_list = [ + (a, b, 1), # First edge between A-B + (a, b, 2), # Second edge between A-B (parallel edge) + (b, c, 1), # Edge between B-C + ] + graph.add_edges_from(edge_list) + + centrality = rustworkx.degree_centrality(graph) + expected = { + 0: 1.0, # Node A has 2 edges (counting parallel edges), normalized by (n-1) = 2 + 1: 1.5, # Node B has 3 edges total (2 to A, 1 to C) + 2: 0.5, # Node C has 1 edge + } + self.assertEqual(expected, dict(centrality)) From 45885b8bac05fb85623d1919fecc69a40aeb350d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:08:55 +0000 Subject: [PATCH 24/38] Bump serde_json from 1.0.132 to 1.0.133 (#1321) Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.132 to 1.0.133. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.132...v1.0.133) --- updated-dependencies: - dependency-name: serde_json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3684204627..053eba660c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -596,9 +596,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", From ff611f22bf488d1b6dd06edb21ef46afc7c1b65b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:55:25 +0000 Subject: [PATCH 25/38] Bump quick-xml from 0.37.0 to 0.37.1 (#1320) Bumps [quick-xml](https://github.com/tafia/quick-xml) from 0.37.0 to 0.37.1. - [Release notes](https://github.com/tafia/quick-xml/releases) - [Changelog](https://github.com/tafia/quick-xml/blob/master/Changelog.md) - [Commits](https://github.com/tafia/quick-xml/compare/v0.37.0...v0.37.1) --- updated-dependencies: - dependency-name: quick-xml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 053eba660c..45fd8248a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -425,9 +425,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.37.0" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbfb3ddf5364c9cfcd65549a1e7b801d0e8d1b14c1a1590a6408aa93cfbfa84" +checksum = "f22f29bdff3987b4d8632ef95fd6424ec7e4e0a57e2f4fc63e489e75357f6a03" dependencies = [ "memchr", ] From 0017d000e6bf82aed0da095410de6f4a4596fa4d Mon Sep 17 00:00:00 2001 From: Fabio Mazza Date: Tue, 19 Nov 2024 23:38:57 +0100 Subject: [PATCH 26/38] Read gzipped graphml files (#1315) * Use flate2 crate to read gzipped graphml files * fix typo * run rustfmt * apply suggestion from clippy * add test for gzipped graphml * write separate function * add changelog * reformat * Revert "write separate function" This reverts commit 2dba2529004f6cb7424eb27cbae319c12d6e4bf6. * run with compression argument * update contribution * lint python * add stub * try avoid error in test in Windows * use Option for compression variable * correct text signature --------- Co-authored-by: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com> --- Cargo.lock | 35 ++++++++++++ Cargo.toml | 1 + ...t-description-string-564c7e376b8e7304.yaml | 5 ++ rustworkx/rustworkx.pyi | 6 +- src/graphml.rs | 47 +++++++++++++--- tests/test_graphml.py | 55 ++++++++++++++++++- 6 files changed, 139 insertions(+), 10 deletions(-) create mode 100644 releasenotes/notes/short-description-string-564c7e376b8e7304.yaml diff --git a/Cargo.lock b/Cargo.lock index 45fd8248a7..457b25b0be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.8.11" @@ -39,6 +45,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -82,6 +97,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -194,6 +219,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + [[package]] name = "ndarray" version = "0.16.1" @@ -529,6 +563,7 @@ version = "0.16.0" dependencies = [ "ahash", "fixedbitset", + "flate2", "hashbrown 0.14.5", "indexmap", "ndarray", diff --git a/Cargo.toml b/Cargo.toml index 3bffc9f379..a07c76d632 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" smallvec = { version = "1.0", features = ["union"] } rustworkx-core = { path = "rustworkx-core", version = "=0.16.0" } +flate2 = "1.0.35" [dependencies.pyo3] version = "0.22.6" diff --git a/releasenotes/notes/short-description-string-564c7e376b8e7304.yaml b/releasenotes/notes/short-description-string-564c7e376b8e7304.yaml new file mode 100644 index 0000000000..5ddc62d189 --- /dev/null +++ b/releasenotes/notes/short-description-string-564c7e376b8e7304.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Added the ability to read GraphML files that are compressed using gzip, with function :func:`~rustworkx.read_graphml`. + The extensions `.graphmlz` and `.gz` are automatically recognised, but the gzip decompression can be forced with the "compression" optional argument. diff --git a/rustworkx/rustworkx.pyi b/rustworkx/rustworkx.pyi index 69dcac8dd1..beebd50419 100644 --- a/rustworkx/rustworkx.pyi +++ b/rustworkx/rustworkx.pyi @@ -646,7 +646,11 @@ def directed_random_bipartite_graph( # Read Write -def read_graphml(path: str, /) -> list[PyGraph | PyDiGraph]: ... +def read_graphml( + path: str, + /, + compression: str | None = ..., +) -> list[PyGraph | PyDiGraph]: ... def digraph_node_link_json( graph: PyDiGraph[_S, _T], /, diff --git a/src/graphml.rs b/src/graphml.rs index 6211b25ce2..89c71b79d3 100644 --- a/src/graphml.rs +++ b/src/graphml.rs @@ -13,11 +13,15 @@ #![allow(clippy::borrow_as_ptr)] use std::convert::From; +use std::ffi::OsStr; +use std::fs::File; +use std::io::{BufRead, BufReader}; use std::iter::FromIterator; use std::num::{ParseFloatError, ParseIntError}; use std::path::Path; use std::str::ParseBoolError; +use flate2::bufread::GzDecoder; use hashbrown::HashMap; use indexmap::IndexMap; @@ -524,19 +528,27 @@ impl GraphML { Ok(()) } + /// Open file compressed with gzip, using the GzDecoder + /// Returns a quick_xml Reader instance + fn open_file_gzip>( + path: P, + ) -> Result>>>, quick_xml::Error> { + let file = File::open(path)?; + let reader = BufReader::new(file); + let gzip_reader = BufReader::new(GzDecoder::new(reader)); + Ok(Reader::from_reader(gzip_reader)) + } - /// Parse a file written in GraphML format. + /// Parse a file written in GraphML format from a BufReader /// /// The implementation is based on a state machine in order to /// accept only valid GraphML syntax (e.g a `` element should /// be nested inside a `` element) where the internal state changes /// after handling each quick_xml event. - fn from_file>(path: P) -> Result { + fn read_graph_from_reader(mut reader: Reader) -> Result { let mut graphml = GraphML::default(); let mut buf = Vec::new(); - let mut reader = Reader::from_file(path)?; - let mut state = State::Start; let mut domain_of_last_key = Domain::Node; let mut last_data_key = String::new(); @@ -677,6 +689,23 @@ impl GraphML { Ok(graphml) } + + /// Read a graph from a file in the GraphML format + /// If the the file extension is "graphmlz" or "gz", decompress it on the fly + fn from_file>(path: P, compression: &str) -> Result { + let extension = path.as_ref().extension().unwrap_or(OsStr::new("")); + + let graph: Result = + if extension.eq("graphmlz") || extension.eq("gz") || compression.eq("gzip") { + let reader = Self::open_file_gzip(path)?; + Self::read_graph_from_reader(reader) + } else { + let reader = Reader::from_file(path)?; + Self::read_graph_from_reader(reader) + }; + + graph + } } /// Read a list of graphs from a file in GraphML format. @@ -703,9 +732,13 @@ impl GraphML { /// :rtype: list[Union[PyGraph, PyDiGraph]] /// :raises RuntimeError: when an error is encountered while parsing the GraphML file. #[pyfunction] -#[pyo3(text_signature = "(path, /)")] -pub fn read_graphml(py: Python, path: &str) -> PyResult> { - let graphml = GraphML::from_file(path)?; +#[pyo3(signature=(path, compression=None),text_signature = "(path, /, compression=None)")] +pub fn read_graphml( + py: Python, + path: &str, + compression: Option, +) -> PyResult> { + let graphml = GraphML::from_file(path, &compression.unwrap_or_default())?; let mut out = Vec::new(); for graph in graphml.graphs { diff --git a/tests/test_graphml.py b/tests/test_graphml.py index fee85da4ad..517a79d263 100644 --- a/tests/test_graphml.py +++ b/tests/test_graphml.py @@ -12,6 +12,8 @@ import unittest import tempfile +import gzip + import numpy import rustworkx @@ -55,8 +57,8 @@ def assertGraphMLRaises(self, graph_xml): with self.assertRaises(Exception): rustworkx.read_graphml(fd.name) - def test_simple(self): - graph_xml = self.HEADER.format( + def graphml_xml_example(self): + return self.HEADER.format( """ yellow @@ -80,6 +82,8 @@ def test_simple(self): """ ) + def test_simple(self): + graph_xml = self.graphml_xml_example() with tempfile.NamedTemporaryFile("wt") as fd: fd.write(graph_xml) fd.flush() @@ -96,6 +100,53 @@ def test_simple(self): ] self.assertGraphEqual(graph, nodes, edges, directed=False) + def test_gzipped(self): + graph_xml = self.graphml_xml_example() + + ## Test reading a graphmlz + with tempfile.NamedTemporaryFile("w+b") as fd: + fd.flush() + newname = fd.name + ".gz" + with gzip.open(newname, "wt") as wf: + wf.write(graph_xml) + + graphml = rustworkx.read_graphml(newname) + graph = graphml[0] + nodes = [ + {"id": "n0", "color": "blue"}, + {"id": "n1", "color": "yellow"}, + {"id": "n2", "color": "green"}, + ] + edges = [ + ("n0", "n1", {"fidelity": 0.98}), + ("n0", "n2", {"fidelity": 0.95}), + ] + self.assertGraphEqual(graph, nodes, edges, directed=False) + + def test_gzipped_force(self): + graph_xml = self.graphml_xml_example() + + ## Test reading a graphmlz + with tempfile.NamedTemporaryFile("w+b") as fd: + # close the file + fd.flush() + newname = fd.name + ".ext" + with gzip.open(newname, "wt") as wf: + wf.write(graph_xml) + + graphml = rustworkx.read_graphml(newname, compression="gzip") + graph = graphml[0] + nodes = [ + {"id": "n0", "color": "blue"}, + {"id": "n1", "color": "yellow"}, + {"id": "n2", "color": "green"}, + ] + edges = [ + ("n0", "n1", {"fidelity": 0.98}), + ("n0", "n2", {"fidelity": 0.95}), + ] + self.assertGraphEqual(graph, nodes, edges, directed=False) + def test_multiple_graphs_in_single_file(self): graph_xml = self.HEADER.format( """ From 537f67f48e08b28bfdbe23cfcd6281831f911d89 Mon Sep 17 00:00:00 2001 From: Etienne Wodey <44871469+airwoodix@users.noreply.github.com> Date: Wed, 20 Nov 2024 23:33:29 +0100 Subject: [PATCH 27/38] Add immediate_dominators function (#1323) --- .../api/algorithm_functions/dominance.rst | 9 ++ docs/source/api/algorithm_functions/index.rst | 1 + ...immediate-dominators-0a713b22657cd19a.yaml | 6 + rustworkx/__init__.pyi | 1 + rustworkx/rustworkx.pyi | 4 + src/dominance.rs | 60 ++++++++ src/lib.rs | 3 + tests/digraph/test_dominance.py | 139 ++++++++++++++++++ 8 files changed, 223 insertions(+) create mode 100644 docs/source/api/algorithm_functions/dominance.rst create mode 100644 releasenotes/notes/digraph-immediate-dominators-0a713b22657cd19a.yaml create mode 100644 src/dominance.rs create mode 100644 tests/digraph/test_dominance.py diff --git a/docs/source/api/algorithm_functions/dominance.rst b/docs/source/api/algorithm_functions/dominance.rst new file mode 100644 index 0000000000..d711e89a71 --- /dev/null +++ b/docs/source/api/algorithm_functions/dominance.rst @@ -0,0 +1,9 @@ +.. _dominance: + +Dominance +========= + +.. autosummary:: + :toctree: ../../apiref + + rustworkx.immediate_dominators diff --git a/docs/source/api/algorithm_functions/index.rst b/docs/source/api/algorithm_functions/index.rst index 1241cae34a..0bbd84b287 100644 --- a/docs/source/api/algorithm_functions/index.rst +++ b/docs/source/api/algorithm_functions/index.rst @@ -10,6 +10,7 @@ Algorithm Functions coloring connectivity_and_cycles dag_algorithms + dominance graph_operations isomorphism link_analysis diff --git a/releasenotes/notes/digraph-immediate-dominators-0a713b22657cd19a.yaml b/releasenotes/notes/digraph-immediate-dominators-0a713b22657cd19a.yaml new file mode 100644 index 0000000000..dc89cc6cca --- /dev/null +++ b/releasenotes/notes/digraph-immediate-dominators-0a713b22657cd19a.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add :func:`rustworkx.immediate_dominators` function for computing + immediate dominators of all nodes in a directed graph. + This function mirrors the ``networkx.immediate_dominators`` function. diff --git a/rustworkx/__init__.pyi b/rustworkx/__init__.pyi index 2152b4f60b..7a7af8be41 100644 --- a/rustworkx/__init__.pyi +++ b/rustworkx/__init__.pyi @@ -242,6 +242,7 @@ from .rustworkx import steiner_tree as steiner_tree from .rustworkx import metric_closure as metric_closure from .rustworkx import digraph_union as digraph_union from .rustworkx import graph_union as graph_union +from .rustworkx import immediate_dominators as immediate_dominators from .rustworkx import NodeIndices as NodeIndices from .rustworkx import PathLengthMapping as PathLengthMapping from .rustworkx import PathMapping as PathMapping diff --git a/rustworkx/rustworkx.pyi b/rustworkx/rustworkx.pyi index beebd50419..1655a759e4 100644 --- a/rustworkx/rustworkx.pyi +++ b/rustworkx/rustworkx.pyi @@ -1052,6 +1052,10 @@ def graph_union( merge_edges: bool = ..., ) -> PyGraph[_S, _T]: ... +# Dominance + +def immediate_dominators(graph: PyDiGraph[_S, _T], start_node: int, /) -> dict[int, int]: ... + # Iterators _T_co = TypeVar("_T_co", covariant=True) diff --git a/src/dominance.rs b/src/dominance.rs new file mode 100644 index 0000000000..2dd31e0f17 --- /dev/null +++ b/src/dominance.rs @@ -0,0 +1,60 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +use super::{digraph, InvalidNode, NullGraph}; +use rustworkx_core::dictmap::DictMap; + +use petgraph::algo::dominators; +use petgraph::graph::NodeIndex; + +use pyo3::prelude::*; + +/// Determine the immediate dominators of all nodes in a directed graph. +/// +/// The dominance computation uses the algorithm published in 2006 by +/// Cooper, Harvey, and Kennedy (https://hdl.handle.net/1911/96345). +/// The time complexity is quadratic in the number of vertices. +/// +/// :param PyDiGraph graph: directed graph +/// :param int start_node: the start node for the dominance computation +/// +/// :returns: a mapping of node indices to their immediate dominators +/// :rtype: dict[int, int] +/// +/// :raises NullGraph: the passed graph is empty +/// :raises InvalidNode: the start node is not in the graph +#[pyfunction] +#[pyo3(text_signature = "(graph, start_node, /)")] +pub fn immediate_dominators( + graph: &digraph::PyDiGraph, + start_node: usize, +) -> PyResult> { + if graph.graph.node_count() == 0 { + return Err(NullGraph::new_err("Invalid operation on a NullGraph")); + } + + let start_node_index = NodeIndex::new(start_node); + + if !graph.graph.contains_node(start_node_index) { + return Err(InvalidNode::new_err("Start node is not in the graph")); + } + + let dom = dominators::simple_fast(&graph.graph, start_node_index); + + // Include the root node to match networkx.immediate_dominators + let root_dom = [(start_node, start_node)]; + let others_dom = graph.graph.node_indices().filter_map(|index| { + dom.immediate_dominator(index) + .map(|res| (index.index(), res.index())) + }); + Ok(root_dom.into_iter().chain(others_dom).collect()) +} diff --git a/src/lib.rs b/src/lib.rs index 4d83e41587..45a9629d6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ mod coloring; mod connectivity; mod dag_algo; mod digraph; +mod dominance; mod dot_utils; mod generators; mod graph; @@ -47,6 +48,7 @@ use centrality::*; use coloring::*; use connectivity::*; use dag_algo::*; +use dominance::*; use graphml::*; use isomorphism::*; use json::*; @@ -464,6 +466,7 @@ fn rustworkx(py: Python<'_>, m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(graph_vf2_mapping))?; m.add_wrapped(wrap_pyfunction!(digraph_union))?; m.add_wrapped(wrap_pyfunction!(graph_union))?; + m.add_wrapped(wrap_pyfunction!(immediate_dominators))?; m.add_wrapped(wrap_pyfunction!(digraph_maximum_bisimulation))?; m.add_wrapped(wrap_pyfunction!(digraph_cartesian_product))?; m.add_wrapped(wrap_pyfunction!(graph_cartesian_product))?; diff --git a/tests/digraph/test_dominance.py b/tests/digraph/test_dominance.py new file mode 100644 index 0000000000..ed37a54962 --- /dev/null +++ b/tests/digraph/test_dominance.py @@ -0,0 +1,139 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import unittest + +import rustworkx as rx +import networkx as nx + + +class TestImmediateDominators(unittest.TestCase): + """Test `rustworkx.immediate_dominators`. + + Test cases adapted from `networkx`: + https://github.com/networkx/networkx/blob/9c5ca54b7e5310a21568bb2e0104f8c87bf74ff7/networkx/algorithms/tests/test_dominance.py + (Copyright 2004-2024 NetworkX Developers, 3-clause BSD License) + """ + + def test_empty(self): + """ + Edge case: empty graph. + """ + graph = rx.PyDiGraph() + + with self.assertRaises(rx.NullGraph): + rx.immediate_dominators(graph, 0) + + def test_start_node_not_in_graph(self): + """ + Edge case: start_node is not in the graph. + """ + graph = rx.PyDiGraph() + graph.add_node(0) + + self.assertEqual(list(graph.node_indices()), [0]) + + with self.assertRaises(rx.InvalidNode): + rx.immediate_dominators(graph, 1) + + def test_singleton(self): + """ + Edge cases: single node, optionally cyclic. + """ + graph = rx.PyDiGraph() + graph.add_node(0) + self.assertDictEqual(rx.immediate_dominators(graph, 0), {0: 0}) + graph.add_edge(0, 0, None) + self.assertDictEqual(rx.immediate_dominators(graph, 0), {0: 0}) + + nx_graph = nx.DiGraph() + nx_graph.add_edges_from(graph.edge_list()) + self.assertDictEqual(nx.immediate_dominators(nx_graph, 0), {0: 0}) + + def test_irreducible1(self): + """ + Graph taken from figure 2 of "A simple, fast dominance algorithm." (2006). + https://hdl.handle.net/1911/96345 + """ + edges = [(1, 2), (2, 1), (3, 2), (4, 1), (5, 3), (5, 4)] + graph = rx.PyDiGraph() + graph.add_node(0) + graph.extend_from_edge_list(edges) + + result = rx.immediate_dominators(graph, 5) + self.assertDictEqual(result, {i: 5 for i in range(1, 6)}) + + nx_graph = nx.DiGraph() + nx_graph.add_edges_from(graph.edge_list()) + self.assertDictEqual(nx.immediate_dominators(nx_graph, 5), result) + + def test_irreducible2(self): + """ + Graph taken from figure 4 of "A simple, fast dominance algorithm." (2006). + https://hdl.handle.net/1911/96345 + """ + edges = [(1, 2), (2, 1), (2, 3), (3, 2), (4, 2), (4, 3), (5, 1), (6, 4), (6, 5)] + graph = rx.PyDiGraph() + graph.add_node(0) + graph.extend_from_edge_list(edges) + + result = rx.immediate_dominators(graph, 6) + self.assertDictEqual(result, {i: 6 for i in range(1, 7)}) + + nx_graph = nx.DiGraph() + nx_graph.add_edges_from(graph.edge_list()) + self.assertDictEqual(nx.immediate_dominators(nx_graph, 6), result) + + def test_domrel_png(self): + """ + Graph taken from https://commons.wikipedia.org/wiki/File:Domrel.png + """ + edges = [(1, 2), (2, 3), (2, 4), (2, 6), (3, 5), (4, 5), (5, 2)] + graph = rx.PyDiGraph() + graph.add_node(0) + graph.extend_from_edge_list(edges) + + result = rx.immediate_dominators(graph, 1) + self.assertDictEqual(result, {1: 1, 2: 1, 3: 2, 4: 2, 5: 2, 6: 2}) + + nx_graph = nx.DiGraph() + nx_graph.add_edges_from(graph.edge_list()) + self.assertDictEqual(nx.immediate_dominators(nx_graph, 1), result) + + # Test postdominance. + graph.reverse() + result = rx.immediate_dominators(graph, 6) + self.assertDictEqual(result, {1: 2, 2: 6, 3: 5, 4: 5, 5: 2, 6: 6}) + + self.assertDictEqual(nx.immediate_dominators(nx_graph.reverse(copy=False), 6), result) + + def test_boost_example(self): + """ + Graph taken from Figure 1 of + http://www.boost.org/doc/libs/1_56_0/libs/graph/doc/lengauer_tarjan_dominator.htm + """ + edges = [(0, 1), (1, 2), (1, 3), (2, 7), (3, 4), (4, 5), (4, 6), (5, 7), (6, 4)] + graph = rx.PyDiGraph() + graph.extend_from_edge_list(edges) + result = rx.immediate_dominators(graph, 0) + self.assertDictEqual(result, {0: 0, 1: 0, 2: 1, 3: 1, 4: 3, 5: 4, 6: 4, 7: 1}) + + nx_graph = nx.DiGraph() + nx_graph.add_edges_from(graph.edge_list()) + self.assertDictEqual(nx.immediate_dominators(nx_graph, 0), result) + + # Test postdominance. + graph.reverse() + result = rx.immediate_dominators(graph, 7) + self.assertDictEqual(result, {0: 1, 1: 7, 2: 7, 3: 4, 4: 5, 5: 7, 6: 4, 7: 7}) + + self.assertDictEqual(nx.immediate_dominators(nx_graph.reverse(copy=False), 7), result) From eaee0b577f25ce1e773f2fd7df8fb0e779981b1c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 17:46:49 +0000 Subject: [PATCH 28/38] ci(mergify): upgrade configuration to current format (#1326) * ci(mergify): upgrade configuration to current format * Use mergify only for backporting --------- Co-authored-by: Mergify <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Ivan Carvalho --- .github/.mergify.yml | 8 ++++++++ .mergify.yml | 33 --------------------------------- 2 files changed, 8 insertions(+), 33 deletions(-) create mode 100644 .github/.mergify.yml delete mode 100644 .mergify.yml diff --git a/.github/.mergify.yml b/.github/.mergify.yml new file mode 100644 index 0000000000..4a9ee1ef66 --- /dev/null +++ b/.github/.mergify.yml @@ -0,0 +1,8 @@ +pull_request_rules: + - name: backport + conditions: + - label=stable-backport-potential + actions: + backport: + branches: + - stable/0.15 diff --git a/.mergify.yml b/.mergify.yml deleted file mode 100644 index 3438adc735..0000000000 --- a/.mergify.yml +++ /dev/null @@ -1,33 +0,0 @@ -queue_rules: - - name: automerge - conditions: - - check-success=python3.9-x64 windows-latest - - check-success=python3.9-x64 ubuntu-latest - - check-success=python3.9-x64 macOS-latest - - check-success=python3.10-x64 windows-latest - - check-success=python3.10-x64 ubuntu-latest - - check-success=python3.10-x64 macOS-latest - - check-success=python3.11-x64 windows-latest - - check-success=python3.11-x64 ubuntu-latest - - check-success=python3.11-x64 macOS-latest - - check-success=Build, rustfmt, and python lint - - check-success=Coverage - - check-success=Build Docs - -pull_request_rules: - - name: automatic merge on CI success and review - conditions: - - "#approved-reviews-by>=1" - - label=automerge - - label!=on hold - actions: - queue: - name: automerge - method: squash - - name: backport - conditions: - - label=stable-backport-potential - actions: - backport: - branches: - - stable/0.14 From 37bee6f326aede01926f8b528a6ef536e07a4961 Mon Sep 17 00:00:00 2001 From: Etienne Wodey <44871469+airwoodix@users.noreply.github.com> Date: Sat, 23 Nov 2024 23:39:23 +0100 Subject: [PATCH 29/38] Add dominance_frontiers function (#1329) Co-authored-by: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com> --- .../api/algorithm_functions/dominance.rst | 1 + .../dominance-frontiers-6e3dcd59e9201b24.yaml | 6 + rustworkx/__init__.pyi | 1 + rustworkx/rustworkx.pyi | 1 + src/dominance.rs | 52 +++++ src/lib.rs | 1 + tests/digraph/test_dominance.py | 206 ++++++++++++++++++ 7 files changed, 268 insertions(+) create mode 100644 releasenotes/notes/dominance-frontiers-6e3dcd59e9201b24.yaml diff --git a/docs/source/api/algorithm_functions/dominance.rst b/docs/source/api/algorithm_functions/dominance.rst index d711e89a71..368ea26414 100644 --- a/docs/source/api/algorithm_functions/dominance.rst +++ b/docs/source/api/algorithm_functions/dominance.rst @@ -7,3 +7,4 @@ Dominance :toctree: ../../apiref rustworkx.immediate_dominators + rustworkx.dominance_frontiers diff --git a/releasenotes/notes/dominance-frontiers-6e3dcd59e9201b24.yaml b/releasenotes/notes/dominance-frontiers-6e3dcd59e9201b24.yaml new file mode 100644 index 0000000000..947537bd39 --- /dev/null +++ b/releasenotes/notes/dominance-frontiers-6e3dcd59e9201b24.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add the :func:`rustworkx.dominance_frontiers` function to compute + the dominance frontiers of all nodes in a directed graph. + This function mirrors the ``networkx.dominance_frontiers`` function. diff --git a/rustworkx/__init__.pyi b/rustworkx/__init__.pyi index 7a7af8be41..5952e177e1 100644 --- a/rustworkx/__init__.pyi +++ b/rustworkx/__init__.pyi @@ -243,6 +243,7 @@ from .rustworkx import metric_closure as metric_closure from .rustworkx import digraph_union as digraph_union from .rustworkx import graph_union as graph_union from .rustworkx import immediate_dominators as immediate_dominators +from .rustworkx import dominance_frontiers as dominance_frontiers from .rustworkx import NodeIndices as NodeIndices from .rustworkx import PathLengthMapping as PathLengthMapping from .rustworkx import PathMapping as PathMapping diff --git a/rustworkx/rustworkx.pyi b/rustworkx/rustworkx.pyi index 1655a759e4..4a18edd614 100644 --- a/rustworkx/rustworkx.pyi +++ b/rustworkx/rustworkx.pyi @@ -1055,6 +1055,7 @@ def graph_union( # Dominance def immediate_dominators(graph: PyDiGraph[_S, _T], start_node: int, /) -> dict[int, int]: ... +def dominance_frontiers(graph: PyDiGraph[_S, _T], start_node: int, /) -> dict[int, set[int]]: ... # Iterators diff --git a/src/dominance.rs b/src/dominance.rs index 2dd31e0f17..3f1dcc5a4f 100644 --- a/src/dominance.rs +++ b/src/dominance.rs @@ -13,6 +13,8 @@ use super::{digraph, InvalidNode, NullGraph}; use rustworkx_core::dictmap::DictMap; +use hashbrown::HashSet; + use petgraph::algo::dominators; use petgraph::graph::NodeIndex; @@ -58,3 +60,53 @@ pub fn immediate_dominators( }); Ok(root_dom.into_iter().chain(others_dom).collect()) } + +/// Compute the dominance frontiers of all nodes in a directed graph. +/// +/// The dominance and dominance frontiers computations use the +/// algorithms published in 2006 by Cooper, Harvey, and Kennedy +/// (https://hdl.handle.net/1911/96345). +/// +/// :param PyDiGraph graph: directed graph +/// :param int start_node: the start node for the dominance computation +/// +/// :returns: a mapping of node indices to their dominance frontiers +/// :rtype: dict[int, set[int]] +/// +/// :raises NullGraph: the passed graph is empty +/// :raises InvalidNode: the start node is not in the graph +#[pyfunction] +#[pyo3(text_signature = "(graph, start_node, /)")] +pub fn dominance_frontiers( + graph: &digraph::PyDiGraph, + start_node: usize, +) -> PyResult>> { + let idom = immediate_dominators(graph, start_node)?; + + let mut df: DictMap<_, _> = idom + .iter() + .map(|(&node, _)| (node, HashSet::default())) + .collect(); + + for (&node, &node_idom) in &idom { + let preds = graph.predecessor_indices(node); + if preds.nodes.len() >= 2 { + for mut runner in preds.nodes { + while runner != node_idom { + df.entry(runner) + .and_modify(|e| { + e.insert(node); + }) + .or_insert([node].into_iter().collect()); + if let Some(&runner_idom) = idom.get(&runner) { + runner = runner_idom; + } else { + break; + } + } + } + } + } + + Ok(df) +} diff --git a/src/lib.rs b/src/lib.rs index 45a9629d6a..16c4214538 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -467,6 +467,7 @@ fn rustworkx(py: Python<'_>, m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(digraph_union))?; m.add_wrapped(wrap_pyfunction!(graph_union))?; m.add_wrapped(wrap_pyfunction!(immediate_dominators))?; + m.add_wrapped(wrap_pyfunction!(dominance_frontiers))?; m.add_wrapped(wrap_pyfunction!(digraph_maximum_bisimulation))?; m.add_wrapped(wrap_pyfunction!(digraph_cartesian_product))?; m.add_wrapped(wrap_pyfunction!(graph_cartesian_product))?; diff --git a/tests/digraph/test_dominance.py b/tests/digraph/test_dominance.py index ed37a54962..33e1ed6a44 100644 --- a/tests/digraph/test_dominance.py +++ b/tests/digraph/test_dominance.py @@ -137,3 +137,209 @@ def test_boost_example(self): self.assertDictEqual(result, {0: 1, 1: 7, 2: 7, 3: 4, 4: 5, 5: 7, 6: 4, 7: 7}) self.assertDictEqual(nx.immediate_dominators(nx_graph.reverse(copy=False), 7), result) + + +class TestDominanceFrontiers(unittest.TestCase): + """ + Test `rustworkx.dominance_frontiers`. + + Test cases adapted from `networkx`: + https://github.com/networkx/networkx/blob/9c5ca54b7e5310a21568bb2e0104f8c87bf74ff7/networkx/algorithms/tests/test_dominance.py + (Copyright 2004-2024 NetworkX Developers, 3-clause BSD License) + """ + + def test_empty(self): + """ + Edge case: empty graph. + """ + graph = rx.PyDiGraph() + + with self.assertRaises(rx.NullGraph): + rx.dominance_frontiers(graph, 0) + + def test_start_node_not_in_graph(self): + """ + Edge case: start_node is not in the graph. + """ + graph = rx.PyDiGraph() + graph.add_node(0) + + self.assertEqual(list(graph.node_indices()), [0]) + + with self.assertRaises(rx.InvalidNode): + rx.dominance_frontiers(graph, 1) + + def test_singleton(self): + """ + Edge cases: single node, optionally cyclic. + """ + graph = rx.PyDiGraph() + graph.add_node(0) + self.assertDictEqual(rx.dominance_frontiers(graph, 0), {0: set()}) + + graph.add_edge(0, 0, None) + self.assertDictEqual(rx.dominance_frontiers(graph, 0), {0: set()}) + + def test_irreducible1(self): + """ + Graph taken from figure 2 of "A simple, fast dominance algorithm." (2006). + https://hdl.handle.net/1911/96345 + """ + edges = [(1, 2), (2, 1), (3, 2), (4, 1), (5, 3), (5, 4)] + graph = rx.PyDiGraph() + graph.add_node(0) + graph.extend_from_edge_list(edges) + + result = rx.dominance_frontiers(graph, 5) + self.assertDictEqual(result, {1: {2}, 2: {1}, 3: {2}, 4: {1}, 5: set()}) + + nx_graph = nx.DiGraph() + nx_graph.add_edges_from(graph.edge_list()) + self.assertDictEqual(nx.dominance_frontiers(nx_graph, 5), result) + + def test_irreducible2(self): + """ + Graph taken from figure 4 of "A simple, fast dominance algorithm." (2006). + https://hdl.handle.net/1911/96345 + """ + edges = [(1, 2), (2, 1), (2, 3), (3, 2), (4, 2), (4, 3), (5, 1), (6, 4), (6, 5)] + graph = rx.PyDiGraph() + graph.add_node(0) + graph.extend_from_edge_list(edges) + + result = rx.dominance_frontiers(graph, 6) + + self.assertDictEqual( + result, + { + 1: {2}, + 2: {1, 3}, + 3: {2}, + 4: {2, 3}, + 5: {1}, + 6: set(), + }, + ) + + nx_graph = nx.DiGraph() + nx_graph.add_edges_from(graph.edge_list()) + self.assertDictEqual(nx.dominance_frontiers(nx_graph, 6), result) + + def test_domrel_png(self): + """ + Graph taken from https://commons.wikipedia.org/wiki/File:Domrel.png + """ + edges = [(1, 2), (2, 3), (2, 4), (2, 6), (3, 5), (4, 5), (5, 2)] + graph = rx.PyDiGraph() + graph.add_node(0) + graph.extend_from_edge_list(edges) + + result = rx.dominance_frontiers(graph, 1) + + self.assertDictEqual( + result, + { + 1: set(), + 2: {2}, + 3: {5}, + 4: {5}, + 5: {2}, + 6: set(), + }, + ) + + nx_graph = nx.DiGraph() + nx_graph.add_edges_from(graph.edge_list()) + self.assertDictEqual(nx.dominance_frontiers(nx_graph, 1), result) + + # Test postdominance. + graph.reverse() + result = rx.dominance_frontiers(graph, 6) + self.assertDictEqual( + result, + { + 1: set(), + 2: {2}, + 3: {2}, + 4: {2}, + 5: {2}, + 6: set(), + }, + ) + + self.assertDictEqual(nx.dominance_frontiers(nx_graph.reverse(copy=False), 6), result) + + def test_boost_example(self): + """ + Graph taken from Figure 1 of + http://www.boost.org/doc/libs/1_56_0/libs/graph/doc/lengauer_tarjan_dominator.htm + """ + edges = [(0, 1), (1, 2), (1, 3), (2, 7), (3, 4), (4, 5), (4, 6), (5, 7), (6, 4)] + graph = rx.PyDiGraph() + graph.extend_from_edge_list(edges) + + nx_graph = nx.DiGraph() + nx_graph.add_edges_from(graph.edge_list()) + + result = rx.dominance_frontiers(graph, 0) + self.assertDictEqual( + result, + { + 0: set(), + 1: set(), + 2: {7}, + 3: {7}, + 4: {4, 7}, + 5: {7}, + 6: {4}, + 7: set(), + }, + ) + + self.assertDictEqual(nx.dominance_frontiers(nx_graph, 0), result) + + # Test postdominance + graph.reverse() + result = rx.dominance_frontiers(graph, 7) + self.assertDictEqual( + result, + { + 0: set(), + 1: set(), + 2: {1}, + 3: {1}, + 4: {1, 4}, + 5: {1}, + 6: {4}, + 7: set(), + }, + ) + + self.assertDictEqual(nx.dominance_frontiers(nx_graph.reverse(copy=False), 7), result) + + def test_missing_immediate_doms(self): + """ + Test that the `dominance_frontiers` function doesn't regress on + https://github.com/networkx/networkx/issues/2070 + """ + edges = [(0, 1), (1, 2), (2, 3), (3, 4), (5, 3)] + graph = rx.PyDiGraph() + graph.extend_from_edge_list(edges) + + idom = rx.immediate_dominators(graph, 0) + self.assertNotIn(5, idom) + + # In networkx#2070, the call would fail because node 5 + # has no immediate dominators + result = rx.dominance_frontiers(graph, 0) + self.assertDictEqual( + result, + { + 0: set(), + 1: set(), + 2: set(), + 3: set(), + 4: set(), + 5: {3}, + }, + ) From b66662f7747642fadd1993686437a1fb2a7beb64 Mon Sep 17 00:00:00 2001 From: Etienne Wodey <44871469+airwoodix@users.noreply.github.com> Date: Sat, 30 Nov 2024 18:46:46 +0100 Subject: [PATCH 30/38] Fix clippy for Rust 1.83 (#1332) * clippy: fix empty_line_after_doc_comment * clippy: fix empty_line_after_outer_attr * clippy: autofix needless_return and needless_lifetimes * reno: add fragment for #1332 * Revert "reno: add fragment for #1332" This reverts commit 9d1b0c98dcf22206d05eef5e071fd7b19d96c905. --------- Co-authored-by: Ivan Carvalho --- rustworkx-core/src/generators/star_graph.rs | 2 +- rustworkx-core/src/token_swapper.rs | 1 - src/dag_algo/mod.rs | 6 +++--- src/digraph.rs | 4 ++-- src/graph.rs | 2 +- src/iterators.rs | 12 ++++++------ src/lib.rs | 2 +- 7 files changed, 14 insertions(+), 15 deletions(-) diff --git a/rustworkx-core/src/generators/star_graph.rs b/rustworkx-core/src/generators/star_graph.rs index 4fea65d278..a09565c935 100644 --- a/rustworkx-core/src/generators/star_graph.rs +++ b/rustworkx-core/src/generators/star_graph.rs @@ -34,7 +34,7 @@ use super::InvalidInputError; /// * `bidirectional` - Whether edges are added bidirectionally. If set to /// `true` then for any edge `(u, v)` an edge `(v, u)` will also be added. /// If the graph is undirected this will result in a parallel edge. - +/// /// # Example /// ```rust /// use rustworkx_core::petgraph; diff --git a/rustworkx-core/src/token_swapper.rs b/rustworkx-core/src/token_swapper.rs index a982d35e7f..39695a58d6 100644 --- a/rustworkx-core/src/token_swapper.rs +++ b/rustworkx-core/src/token_swapper.rs @@ -437,7 +437,6 @@ where /// assert_eq!(3, output.len()); /// /// ``` - pub fn token_swapper( graph: G, mapping: HashMap, diff --git a/src/dag_algo/mod.rs b/src/dag_algo/mod.rs index 769582ce13..0adfff12a2 100644 --- a/src/dag_algo/mod.rs +++ b/src/dag_algo/mod.rs @@ -524,7 +524,7 @@ pub fn collect_runs( // This is where a filter function error will be returned, otherwise Result is stripped away let py_run: Vec = run_result? .iter() - .map(|node| return graph.graph.node_weight(*node).into_py(py)) + .map(|node| graph.graph.node_weight(*node).into_py(py)) .collect(); result.push(py_run) @@ -667,7 +667,7 @@ pub fn transitive_reduction( ); } } - return Ok(( + Ok(( digraph::PyDiGraph { graph: tr, node_removed: false, @@ -680,5 +680,5 @@ pub fn transitive_reduction( .iter() .map(|(k, v)| (k.index(), v.index())) .collect::>(), - )); + )) } diff --git a/src/digraph.rs b/src/digraph.rs index 07aa2be14a..47b70b93aa 100644 --- a/src/digraph.rs +++ b/src/digraph.rs @@ -200,7 +200,7 @@ impl GraphBase for PyDiGraph { type EdgeId = EdgeIndex; } -impl<'a> NodesRemoved for &'a PyDiGraph { +impl NodesRemoved for &PyDiGraph { fn nodes_removed(&self) -> bool { self.node_removed } @@ -886,7 +886,7 @@ impl PyDiGraph { /// /// :param int node_a: The index for the first node /// :param int node_b: The index for the second node - + /// /// :returns: A list with all the data objects for the edges between nodes /// :rtype: list /// :raises NoEdgeBetweenNodes: When there is no edge between nodes diff --git a/src/graph.rs b/src/graph.rs index f74933928b..c3e627e5ce 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -161,7 +161,7 @@ impl GraphBase for PyGraph { type EdgeId = EdgeIndex; } -impl<'a> NodesRemoved for &'a PyGraph { +impl NodesRemoved for &PyGraph { fn nodes_removed(&self) -> bool { self.node_removed } diff --git a/src/iterators.rs b/src/iterators.rs index 390aa39257..cabfbe1c37 100644 --- a/src/iterators.rs +++ b/src/iterators.rs @@ -282,7 +282,7 @@ where } } -impl<'py, T> PyEq> for T +impl PyEq> for T where for<'p> T: PyEq + Clone + FromPyObject<'p>, { @@ -1031,7 +1031,7 @@ impl PyHash for EdgeList { } } -impl<'py> PyEq> for EdgeList { +impl PyEq> for EdgeList { #[inline] fn eq(&self, other: &Bound, py: Python) -> PyResult { PyEq::eq(&self.edges, other, py) @@ -1119,7 +1119,7 @@ impl PyHash for IndexPartitionBlock { } } -impl<'py> PyEq> for IndexPartitionBlock { +impl PyEq> for IndexPartitionBlock { #[inline] fn eq(&self, other: &Bound, py: Python) -> PyResult { PyEq::eq(&self.block, other, py) @@ -1522,7 +1522,7 @@ impl PyHash for PathMapping { } } -impl<'py> PyEq> for PathMapping { +impl PyEq> for PathMapping { #[inline] fn eq(&self, other: &Bound, py: Python) -> PyResult { PyEq::eq(&self.paths, other, py) @@ -1686,7 +1686,7 @@ impl PyHash for MultiplePathMapping { } } -impl<'py> PyEq> for MultiplePathMapping { +impl PyEq> for MultiplePathMapping { #[inline] fn eq(&self, other: &Bound, py: Python) -> PyResult { PyEq::eq(&self.paths, other, py) @@ -1750,7 +1750,7 @@ impl PyHash for PathLengthMapping { } } -impl<'py> PyEq> for PathLengthMapping { +impl PyEq> for PathLengthMapping { #[inline] fn eq(&self, other: &Bound, py: Python) -> PyResult { PyEq::eq(&self.path_lengths, other, py) diff --git a/src/lib.rs b/src/lib.rs index 16c4214538..4ee4189a7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -203,7 +203,7 @@ pub trait NodesRemoved { fn nodes_removed(&self) -> bool; } -impl<'a, Ty> NodesRemoved for &'a StablePyGraph +impl NodesRemoved for &StablePyGraph where Ty: EdgeType, { From 5d28053f31240182d697a6841d595d6e5fcad8f1 Mon Sep 17 00:00:00 2001 From: Etienne Wodey <44871469+airwoodix@users.noreply.github.com> Date: Sat, 30 Nov 2024 19:45:45 +0100 Subject: [PATCH 31/38] rustworkx-core: fix docs build warnings (#1333) * core/steiner_tree: fix rustdoc warnings * core/generators/dorogovtsev_goltsev_mendes: fix rustdoc warnings * core/centrality: fix rustdoc warnings * workflows/main: error on warnings during rustworkx-core doc build * Pin Rust to 1.82 for clippy * Revert: Pin Rust to 1.82 to work around #1331 This reverts commit 1977f669fa2bd78ad7baaf5c1bf6f303e992daa6. --------- Co-authored-by: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com> --- .github/workflows/main.yml | 2 + rustworkx-core/src/centrality.rs | 11 +++-- .../dorogovtsev_goltsev_mendes_graph.rs | 17 +++---- rustworkx-core/src/steiner_tree.rs | 44 +++++++++++-------- 4 files changed, 44 insertions(+), 30 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5579fb21a6..f635c608e3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -46,6 +46,8 @@ jobs: run: pushd rustworkx-core && cargo test && popd - name: rustworkx-core Docs run: pushd rustworkx-core && cargo doc && popd + env: + RUSTDOCFLAGS: '-D warnings' - uses: actions/upload-artifact@v4 with: name: rustworkx_core_docs diff --git a/rustworkx-core/src/centrality.rs b/rustworkx-core/src/centrality.rs index 47dfd21367..a90876e4df 100644 --- a/rustworkx-core/src/centrality.rs +++ b/rustworkx-core/src/centrality.rs @@ -1073,18 +1073,21 @@ mod test_katz_centrality { /// In the case of a graphs with more than one connected component there is /// an alternative improved formula that calculates the closeness centrality /// as "a ratio of the fraction of actors in the group who are reachable, to -/// the average distance" [^WF]. You can enable this by setting `wf_improved` to `true`. +/// the average distance".[^WF] +/// You can enable this by setting `wf_improved` to `true`. /// -/// [^WF] Wasserman, S., & Faust, K. (1994). Social Network Analysis: +/// [^WF]: Wasserman, S., & Faust, K. (1994). Social Network Analysis: /// Methods and Applications (Structural Analysis in the Social Sciences). -/// Cambridge: Cambridge University Press. doi:10.1017/CBO9780511815478 +/// Cambridge: Cambridge University Press. +/// /// -/// Arguments: +/// # Arguments /// /// * `graph` - The graph object to run the algorithm on /// * `wf_improved` - If `true`, scale by the fraction of nodes reachable. /// /// # Example +/// /// ```rust /// use rustworkx_core::petgraph; /// use rustworkx_core::centrality::closeness_centrality; diff --git a/rustworkx-core/src/generators/dorogovtsev_goltsev_mendes_graph.rs b/rustworkx-core/src/generators/dorogovtsev_goltsev_mendes_graph.rs index 99954f498a..2bd10991bf 100644 --- a/rustworkx-core/src/generators/dorogovtsev_goltsev_mendes_graph.rs +++ b/rustworkx-core/src/generators/dorogovtsev_goltsev_mendes_graph.rs @@ -16,18 +16,19 @@ use super::InvalidInputError; /// Generate a Dorogovtsev-Goltsev-Mendes graph /// -/// Generate a graph following the recursive procedure in [1]. +/// Generate a graph following the recursive procedure by Dorogovtsev, Goltsev, +/// and Mendes[^DGM2002]. /// Starting from the two-node, one-edge graph, iterating `n` times generates -/// a graph with `(3**n + 3) // 2` nodes and `3**n` edges. +/// a graph with `(3^n + 3) // 2` nodes and `3^n` edges. /// +/// # Arguments /// -/// Arguments: -/// -/// * `n` - The number of iterations to perform. n=0 returns the two-node, one-edge graph. +/// * `n` - The number of iterations to perform. `n = 0` returns the two-node, one-edge graph. /// * `default_node_weight` - A callable that will return the weight to use for newly created nodes. /// * `default_edge_weight` - A callable that will return the weight object to use for newly created edges. /// /// # Example +/// /// ```rust /// use rustworkx_core::petgraph; /// use rustworkx_core::generators::dorogovtsev_goltsev_mendes_graph; @@ -43,10 +44,10 @@ use super::InvalidInputError; /// ); /// ``` /// -/// .. [1] S. N. Dorogovtsev, A. V. Goltsev and J. F. F. Mendes +/// [^DGM2002]: S. N. Dorogovtsev, A. V. Goltsev and J. F. F. Mendes /// “Pseudofractal scale-free web” -/// Physical Review E 65, 066122, 2002 -/// https://arxiv.org/abs/cond-mat/0112143 +/// Physical Review E 65, 066122 (2002) +/// /// pub fn dorogovtsev_goltsev_mendes_graph( n: usize, diff --git a/rustworkx-core/src/steiner_tree.rs b/rustworkx-core/src/steiner_tree.rs index f9bdcc60bb..80fab566fd 100644 --- a/rustworkx-core/src/steiner_tree.rs +++ b/rustworkx-core/src/steiner_tree.rs @@ -437,6 +437,9 @@ where Ok(out_edges) } +/// Solution to a minimum Steiner tree problem. +/// +/// This `struct` is created by the [steiner_tree] function. pub struct SteinerTreeResult { pub used_node_indices: HashSet, pub used_edge_endpoints: HashSet<(usize, usize)>, @@ -454,21 +457,25 @@ pub struct SteinerTreeResult { /// complete graph in which each edge is weighted by the shortest path distance /// between nodes in ``graph``. /// -/// This algorithm [1]_ produces a tree whose weight is within a -/// :math:`(2 - (2 / t))` factor of the weight of the optimal Steiner tree -/// where :math:`t` is the number of terminal nodes. The algorithm implemented -/// here is due to [2]_ . It avoids computing all pairs shortest paths but rather -/// reduces the problem to a single source shortest path and a minimum spanning tree -/// problem. +/// This algorithm by Kou, Markowsky, and Berman[^KouMarkowskyBerman1981] +/// produces a tree whose weight is within a `(2 - (2 / t))` factor of +/// the weight of the optimal Steiner tree where `t` is the number of +/// terminal nodes. +/// The algorithm implemented here is due to Mehlhorn[^Mehlhorn1987]. It avoids +/// computing all pairs shortest paths but rather reduces the problem to a +/// single source shortest path and a minimum spanning tree problem. /// -/// Arguments: -/// `graph`: The input graph to compute the steiner tree of -/// `terminal_nodes`: The terminal nodes of the steiner tree -/// `weight_fn`: A callable weight function that will be passed an edge reference -/// for each edge in the graph and it is expected to return a `Result` -/// which if it doesn't error represents the weight of that edge. +/// # Arguments +/// +/// - `graph` - The input graph to compute the Steiner tree of +/// - `terminal_nodes` - The terminal nodes of the Steiner tree +/// - `weight_fn` - A callable weight function that will be passed an edge reference +/// for each edge in the graph and it is expected to return a [`Result`] +/// which if it doesn't error represents the weight of that edge. +/// +/// # Returns /// -/// Returns a custom struct that contains a set of nodes and edges and `None` +/// A custom struct that contains a set of nodes and edges and `None` /// if the graph is disconnected relative to the terminal nodes. /// /// # Example @@ -509,13 +516,14 @@ pub struct SteinerTreeResult { /// let tree = steiner_tree(&input_graph, &terminal_nodes, weight_fn).unwrap().unwrap(); /// ``` /// -/// .. [1] Kou, Markowsky & Berman, +/// [^KouMarkowskyBerman1981]: Kou, Markowsky & Berman, /// "A fast algorithm for Steiner trees" -/// Acta Informatica 15, 141–145 (1981). -/// https://link.springer.com/article/10.1007/BF00288961 -/// .. [2] Kurt Mehlhorn, +/// Acta Informatica 15, 141–145 (1981) +/// +/// [^Mehlhorn1987]: Kurt Mehlhorn, /// "A faster approximation algorithm for the Steiner problem in graphs" -/// https://doi.org/10.1016/0020-0190(88)90066-X +/// Information Processing Letters 27(3), 125-128 (1987) +/// pub fn steiner_tree( graph: G, terminal_nodes: &[G::NodeId], From 680789b5147198b02f426fef036d0b2269e98044 Mon Sep 17 00:00:00 2001 From: Etienne Wodey <44871469+airwoodix@users.noreply.github.com> Date: Sat, 30 Nov 2024 20:24:43 +0100 Subject: [PATCH 32/38] Fix typos detected by the typos tool (#1330) * typos: apply automatic corrections * typos: apply manual corrections * typos: run as linter * typos: apply automatic corrections in releasenotes/ * typos: also check releasenotes content * Add release notes fragment * workflows/main: add spell check * Fix docs build * typos: upgrade to 1.28 * workflows/main: pin rust to 1.82 * workflows/main: remove spell check * Use Rust stable again * Typos: dedicated nox command * Move typos configuration to pyproject.toml --------- Co-authored-by: Ivan Carvalho Co-authored-by: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- CONTRIBUTING.md | 4 +-- README.md | 2 +- docs/source/benchmarks.rst | 2 +- docs/source/install.rst | 4 +-- docs/source/release_notes.rst | 4 +-- .../tutorial/betweenness_centrality.rst | 4 +-- docs/source/tutorial/dags.rst | 4 +-- noxfile.py | 8 ++++++ pyproject.toml | 5 ++++ .../edge-index-methods-427f7301c720f565.yaml | 2 +- .../0.11/fix-dispatch-3596ef110cc68338.yaml | 2 +- .../0.11/prepare-0.11-af688e532712c830.yaml | 4 +-- ...ix-sequence-protocol-e95246e864cc850a.yaml | 2 +- .../0.13/prepare-0.13.0-5e579fb3ab1e3b60.yaml | 2 +- ...apping-token_swapper-55d5b045b0b55345.yaml | 2 +- .../platform-updates-e9b296144e633c95.yaml | 2 +- .../0.14/s390x-tier-4-1701a0f044759cd1.yaml | 2 +- ...transitive-reduction-6db2b80351c15887.yaml | 2 +- .../custom-iterators-dce8557a8f87e8c0.yaml | 2 +- ...l-draw-digraph-plots-aecf86738ab9b0db.yaml | 2 +- ...hical-topo-sort-core-e85fba409d612600.yaml | 2 +- ...maximum-bisimulation-942a9d0dc9b46ee4.yaml | 2 +- .../0.15/swap-nox-tox-dea2bb14c400641c.yaml | 2 +- .../add-nx-converter-1feffc8d5aa13365.yaml | 2 +- .../notes/fix-typos-8f68ff3d0680b924.yaml | 5 ++++ rustworkx-core/src/bipartite_coloring.rs | 2 +- rustworkx-core/src/coloring.rs | 2 +- .../src/connectivity/all_simple_paths.rs | 8 +++--- .../src/connectivity/biconnected.rs | 2 +- rustworkx-core/src/connectivity/chain.rs | 2 +- .../src/connectivity/cycle_basis.rs | 4 +-- rustworkx-core/src/dag_algo.rs | 4 +-- .../src/generators/complete_graph.rs | 2 +- rustworkx-core/src/generators/cycle_graph.rs | 2 +- rustworkx-core/src/generators/karate_club.rs | 4 +-- rustworkx-core/src/generators/path_graph.rs | 2 +- rustworkx-core/src/generators/star_graph.rs | 2 +- rustworkx-core/src/max_weight_matching.rs | 22 ++++++++-------- rustworkx-core/src/planar/lr_planar.rs | 2 +- rustworkx-core/src/steiner_tree.rs | 2 +- rustworkx/__init__.py | 16 ++++++------ rustworkx/visualization/matplotlib.py | 4 +-- src/bisimulation.rs | 8 +++--- src/connectivity/mod.rs | 2 +- src/dag_algo/mod.rs | 2 +- src/digraph.rs | 26 +++++++++---------- src/generators.rs | 4 +-- src/graph.rs | 6 ++--- src/isomorphism/mod.rs | 8 +++--- src/isomorphism/vf2.rs | 12 ++++----- src/iterators.rs | 6 ++--- src/layout/mod.rs | 2 +- src/layout/spring.rs | 2 +- src/shortest_path/all_pairs_dijkstra.rs | 4 +-- src/shortest_path/mod.rs | 4 +-- src/steiner_tree.rs | 2 +- src/transitivity.rs | 12 ++++----- src/traversal/mod.rs | 16 ++++++------ tests/digraph/test_bellman_ford.py | 14 +++++----- tests/digraph/test_bisimulation.py | 2 +- tests/digraph/test_dijkstra.py | 6 ++--- tests/digraph/test_hits.py | 2 +- tests/digraph/test_k_shortest_path.py | 4 +-- tests/digraph/test_nodes.py | 2 +- tests/digraph/test_pagerank.py | 2 +- tests/digraph/test_pred_succ.py | 2 +- tests/graph/test_bellman_ford.py | 14 +++++----- tests/graph/test_dijkstra.py | 6 ++--- tests/graph/test_k_shortest_path.py | 4 +-- tests/graph/test_max_weight_matching.py | 2 +- tests/test_custom_return_types.py | 2 +- tests/test_token_swapper.py | 2 +- tox.ini | 12 ++++----- 74 files changed, 184 insertions(+), 166 deletions(-) create mode 100644 releasenotes/notes/fix-typos-8f68ff3d0680b924.yaml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f635c608e3..92ee8ec96e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,7 @@ jobs: - run: pip install -U ruff==0.6.8 black~=24.8 - uses: dtolnay/rust-toolchain@stable with: - components: rustfmt + components: rustfmt, clippy - name: Test Build run: cargo build - name: Rust Format diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5ae80b1d9e..fdfb4b5756 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -284,7 +284,7 @@ cargo doc --open ### Type Annotations If you have added new methods, functions, or classes, and/or changed any -signatures, type anotations for Python are required to be included in a pull +signatures, type annotations for Python are required to be included in a pull request. Type annotations are added using type [stub files](https://typing.readthedocs.io/en/latest/source/stubs.html) which provide type annotations to python tooling which use type annotations. The stub @@ -523,7 +523,7 @@ primary exception to this is adding support for new python versions. If a new python version is released backporting that feature change with that new support is an acceptable backport. -In rustworkx at least until the 1.0 release we only maintaing a single stable +In rustworkx at least until the 1.0 release we only maintain a single stable branch at a time for the most recent minor version release. #### Backporting procedure diff --git a/README.md b/README.md index 0c4575e9c2..7e2292637a 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ it just as it would if there was a prebuilt binary available. > [!NOTE] > To build from source you will need to ensure you have pip >=19.0.0 installed, which supports PEP-517, or that you have manually installed -`setuptools-rust` prior to running `pip install rustworkx`. If you recieve an +`setuptools-rust` prior to running `pip install rustworkx`. If you receive an error about `setuptools-rust` not being found you should upgrade pip with `pip install -U pip` or manually install `setuptools-rust` with `pip install setuptools-rust` and try again. diff --git a/docs/source/benchmarks.rst b/docs/source/benchmarks.rst index 959573ae34..ce2e4ceea9 100644 --- a/docs/source/benchmarks.rst +++ b/docs/source/benchmarks.rst @@ -2,7 +2,7 @@ Rustworkx Comparison Benchmarks With Other Libraries **************************************************** -rustworkx is competitive against other popular graph libraries for Python. We compared rustworkx to the igraph, graph-tools and NetworkIt libraries `in a benchmark consisting of four tasks available on Github for reproducibility `__. We report the results from a machine with an Intel(R) i9-9900K CPU at 3.60GHz with eight cores, 16 theads, and 32GB of RAM avaialble. +rustworkx is competitive against other popular graph libraries for Python. We compared rustworkx to the igraph, graph-tools and NetworkIt libraries `in a benchmark consisting of four tasks available on Github for reproducibility `__. We report the results from a machine with an Intel(R) i9-9900K CPU at 3.60GHz with eight cores, 16 threads, and 32GB of RAM available. Graph Creation ============== diff --git a/docs/source/install.rst b/docs/source/install.rst index 942614b2c3..92b85e7a82 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -44,12 +44,12 @@ just as it would if there was a prebuilt binary available. To build from source you will need to ensure you have pip >=19.0.0 installed, which supports PEP-517, or that you have manually installed - setuptools-rust prior to running pip install rustworkx. If you recieve an + setuptools-rust prior to running pip install rustworkx. If you receive an error about ``setuptools-rust`` not being found you should upgrade pip with ``pip install -U pip`` or manually install ``setuptools-rust`` with: ``pip install setuptools-rust`` and try again. -.. _platform-suppport: +.. _platform-support: Platform Support ================ diff --git a/docs/source/release_notes.rst b/docs/source/release_notes.rst index f8d414bc76..79ba7847d5 100644 --- a/docs/source/release_notes.rst +++ b/docs/source/release_notes.rst @@ -266,7 +266,7 @@ New Features - A new method, :meth:`~rustworkx.PyDiGraph.remove_node_retain_edges`, has been added to the :class:`~rustworkx.PyDiGraph` class. This method can be used to - remove a node and add edges from its predecesors to its successors. + remove a node and add edges from its predecessors to its successors. - Two new methods, :meth:`~rustworkx.PyGraph.edge_list` and :meth:`~rustworkx.PyGraph.weighted_edge_list`, for getting a list of tuples with the edge source and target (with or without edge weights) have been @@ -285,7 +285,7 @@ New Features edge list file and will read that file and generate a new object from the contents. - Two new methods, :meth:`~rustworkx.PyGraph.extend_from_edge_list` and - :meth:`~rustworkx.PyGraoh.extend_from_weighted_edge_list` has been added + :meth:`~rustworkx.PyGraph.extend_from_weighted_edge_list` has been added to :class:`~rustworkx.PyGraph` and :class:`~rustworkx.PyDiGraph` (:meth:`~rustworkx.PyDiGraph.extend_from_edge_list` and :meth:`~rustworkx.PyDiGraph.extend_from_weighted_edge_list`). This method diff --git a/docs/source/tutorial/betweenness_centrality.rst b/docs/source/tutorial/betweenness_centrality.rst index 38114101fb..2ccc511026 100644 --- a/docs/source/tutorial/betweenness_centrality.rst +++ b/docs/source/tutorial/betweenness_centrality.rst @@ -37,8 +37,8 @@ To start we need to generate a graph: mpl_draw(graph) -Calculate the Betweeness Centrality ------------------------------------ +Calculate the Betweenness Centrality +------------------------------------ The :func:`~rustworkx.betweenness_centrality` function can be used to calculate the betweenness centrality for each node in the graph. diff --git a/docs/source/tutorial/dags.rst b/docs/source/tutorial/dags.rst index cc8780e7fb..70fd443616 100644 --- a/docs/source/tutorial/dags.rst +++ b/docs/source/tutorial/dags.rst @@ -113,7 +113,7 @@ jobs. For example: Above we define a DAG with 6 jobs and dependency relationship between these jobs. Now if we run the :func:`~rustworkx.topological_sort` function on the graph it will return a linear order to execute the jobs that will respect -the dependency releationship. +the dependency relationship. .. jupyter-execute:: @@ -151,7 +151,7 @@ computation. A quantum circuit is represented graphically like: The specifics of this circuit aren't important here beyond the fact that we have 2 qubits, ``q_0`` and ``q_1``, 2 classical bits, ``c_0`` and ``c_1``, -and a series of operations on those qubits with a depedency ordering. The last +and a series of operations on those qubits with a dependency ordering. The last operation on each qubit is a measurement on ``q_0`` that is stored in ``c_0`` and ``q_1`` that is stored in ``c_1``. diff --git a/noxfile.py b/noxfile.py index d7d5229cb2..c0f08ff968 100644 --- a/noxfile.py +++ b/noxfile.py @@ -15,6 +15,7 @@ "black~=24.8", "ruff~=0.6", "setuptools-rust", + "typos~=1.28", ] stubs_deps = [ @@ -45,6 +46,7 @@ def test_with_version(session): @nox.session(python=["3"]) def lint(session): black(session) + typos(session) session.install(*lint_deps) session.run("ruff", "check", "rustworkx", "retworkx", "setup.py") session.run("cargo", "fmt", "--all", "--", "--check", external=True) @@ -69,6 +71,12 @@ def black(session): session.install(*[d for d in lint_deps if "black" in d]) session.run("black", "rustworkx", "tests", "retworkx", *session.posargs) +@nox.session(python=["3"]) +def typos(session): + session.install(*[d for d in lint_deps if "typos" in d]) + session.run("typos", "--exclude", "releasenotes") + session.run("typos", "--no-check-filenames", "releasenotes") + @nox.session(python=["3"]) def stubs(session): install_rustworkx(session) diff --git a/pyproject.toml b/pyproject.toml index 2a752ec1be..759440db32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,11 @@ extend-exclude = ["doc"] "rustworkx/__init__.py" = ["F405", "F403"] "*.pyi" = ["F403", "F405", "PYI001", "PYI002"] +[tool.typos.default] +extend-ignore-words-re = [ + "[Ss]toer", +] + [tool.cibuildwheel] manylinux-x86_64-image = "manylinux2014" manylinux-i686-image = "manylinux2014" diff --git a/releasenotes/notes/0.11/edge-index-methods-427f7301c720f565.yaml b/releasenotes/notes/0.11/edge-index-methods-427f7301c720f565.yaml index 3c55e2c45c..ff41fbeae7 100644 --- a/releasenotes/notes/0.11/edge-index-methods-427f7301c720f565.yaml +++ b/releasenotes/notes/0.11/edge-index-methods-427f7301c720f565.yaml @@ -8,7 +8,7 @@ features: Added a new method, :meth:`~rustworkx.PyGraph.incident_edge_index_map`, to the :class:`~rustworkx.PyGraph` and :class:`~rustworkx.PyDiGraph` class. This method returns a mapping of edge indices for edges incident to a provided node - to the endoint and weight tuple for that edge index. For example: + to the endpoint and weight tuple for that edge index. For example: .. jupyter-execute:: diff --git a/releasenotes/notes/0.11/fix-dispatch-3596ef110cc68338.yaml b/releasenotes/notes/0.11/fix-dispatch-3596ef110cc68338.yaml index 0ebbeb7505..1127ba9382 100644 --- a/releasenotes/notes/0.11/fix-dispatch-3596ef110cc68338.yaml +++ b/releasenotes/notes/0.11/fix-dispatch-3596ef110cc68338.yaml @@ -8,4 +8,4 @@ fixes: - | Fixed an oversight in the :func:`~rustworkx.union` function where user-defined values for the ``merge_nodes`` and ``merge_edges`` arguments - were being ingored. + were being ignored. diff --git a/releasenotes/notes/0.11/prepare-0.11-af688e532712c830.yaml b/releasenotes/notes/0.11/prepare-0.11-af688e532712c830.yaml index 3d07b3068a..b009abef98 100644 --- a/releasenotes/notes/0.11/prepare-0.11-af688e532712c830.yaml +++ b/releasenotes/notes/0.11/prepare-0.11-af688e532712c830.yaml @@ -56,7 +56,7 @@ features: g = nx.Graph() g.add_nodes_from([ ("A", {"color": "turquoise", "size": "extra large"}), - ("B", {"color": "fuschia", "size": "tiny"}), + ("B", {"color": "fuchsia", "size": "tiny"}), ]) g.add_edge("A", "B") rx_graph = rx.networkx_converter(g, keep_attributes=True) @@ -65,7 +65,7 @@ features: will output:: - [{'color': 'turquoise', 'size': 'extra large', '__networkx_node__': 'A'}, {'color': 'fuschia', 'size': 'tiny', '__networkx_node__': 'B'}] + [{'color': 'turquoise', 'size': 'extra large', '__networkx_node__': 'A'}, {'color': 'fuchsia', 'size': 'tiny', '__networkx_node__': 'B'}] WeightedEdgeList[(0, 1, {})] fixes: diff --git a/releasenotes/notes/0.13/fix-sequence-protocol-e95246e864cc850a.yaml b/releasenotes/notes/0.13/fix-sequence-protocol-e95246e864cc850a.yaml index 4bfb6fe377..19934be6b0 100644 --- a/releasenotes/notes/0.13/fix-sequence-protocol-e95246e864cc850a.yaml +++ b/releasenotes/notes/0.13/fix-sequence-protocol-e95246e864cc850a.yaml @@ -4,7 +4,7 @@ fixes: Fixed an issue with the custom sequence return types, :class:`~.BFSSuccessors`, :class:`~.NodeIndices`, :class:`~.EdgeList`, :class:`~.WeightedEdgeList`, :class:`~.EdgeIndices`, and :class:`~.Chains` - where they previosuly were missing certain attributes that prevented them + where they previously were missing certain attributes that prevented them being used as a sequence for certain built-in functions such as ``reversed()``. Fixed `#696 `__. diff --git a/releasenotes/notes/0.13/prepare-0.13.0-5e579fb3ab1e3b60.yaml b/releasenotes/notes/0.13/prepare-0.13.0-5e579fb3ab1e3b60.yaml index 3104afa191..9a1f219174 100644 --- a/releasenotes/notes/0.13/prepare-0.13.0-5e579fb3ab1e3b60.yaml +++ b/releasenotes/notes/0.13/prepare-0.13.0-5e579fb3ab1e3b60.yaml @@ -13,5 +13,5 @@ prelude: | This is also the final rustworkx release that supports running with Python 3.7. Starting in the 0.14.0 release Python >= 3.8 will be required to use - rustworkx. This release also increased the minimum suported Rust version for + rustworkx. This release also increased the minimum supported Rust version for compiling rustworkx and rustworkx-core from source to 1.56.1. diff --git a/releasenotes/notes/0.14/handle-invalid-mapping-token_swapper-55d5b045b0b55345.yaml b/releasenotes/notes/0.14/handle-invalid-mapping-token_swapper-55d5b045b0b55345.yaml index 5cc97023e0..c88a4475e0 100644 --- a/releasenotes/notes/0.14/handle-invalid-mapping-token_swapper-55d5b045b0b55345.yaml +++ b/releasenotes/notes/0.14/handle-invalid-mapping-token_swapper-55d5b045b0b55345.yaml @@ -31,4 +31,4 @@ upgrade: token_swapper(&g, mapping, Some(10), Some(4), Some(50)); will now return ``Err(MapNotPossible)`` instead of panicking. If you were using this - funciton before you'll need to handle the result type. + function before you'll need to handle the result type. diff --git a/releasenotes/notes/0.14/platform-updates-e9b296144e633c95.yaml b/releasenotes/notes/0.14/platform-updates-e9b296144e633c95.yaml index adbf3bd271..8e0df2bbdd 100644 --- a/releasenotes/notes/0.14/platform-updates-e9b296144e633c95.yaml +++ b/releasenotes/notes/0.14/platform-updates-e9b296144e633c95.yaml @@ -6,7 +6,7 @@ features: upgrade: - | Support for the Linux ppc64le pllatform has changed from tier 3 to tier 4 - (as documented in :ref:`platform-suppport`). This is a result of no longer + (as documented in :ref:`platform-support`). This is a result of no longer being able to run tests during the pre-compiled wheel publishing jobs due to constraints in the available CI infrastructure. There hopefully shouldn't be any meaningful impact resulting from this change, but as there diff --git a/releasenotes/notes/0.14/s390x-tier-4-1701a0f044759cd1.yaml b/releasenotes/notes/0.14/s390x-tier-4-1701a0f044759cd1.yaml index b4f3b3f9a6..5794720def 100644 --- a/releasenotes/notes/0.14/s390x-tier-4-1701a0f044759cd1.yaml +++ b/releasenotes/notes/0.14/s390x-tier-4-1701a0f044759cd1.yaml @@ -2,7 +2,7 @@ upgrade: - | Support for the Linux s390x platform has changed from tier 3 to tier 4 (as - documented in :ref:`platform-suppport`). This is a result of no longer being + documented in :ref:`platform-support`). This is a result of no longer being able to run tests during the pre-compiled wheel publishing jobs due to constraints in the available CI infrastructure. There hopefully shouldn't be any meaningful impact resulting from this change, but as there are no longer tests being diff --git a/releasenotes/notes/0.14/transitive-reduction-6db2b80351c15887.yaml b/releasenotes/notes/0.14/transitive-reduction-6db2b80351c15887.yaml index 03ce860c6b..a6cced9e4e 100644 --- a/releasenotes/notes/0.14/transitive-reduction-6db2b80351c15887.yaml +++ b/releasenotes/notes/0.14/transitive-reduction-6db2b80351c15887.yaml @@ -1,7 +1,7 @@ --- features: - | - Added a new function, :func:`~.transitive_reduction` which returns the transtive reduction + Added a new function, :func:`~.transitive_reduction` which returns the transitive reduction of a given :class:`~rustworkx.PyDiGraph` and a dictionary with the mapping of indices from the given graph to the returned graph. The given graph must be a Directed Acyclic Graph (DAG). For example: diff --git a/releasenotes/notes/0.15/custom-iterators-dce8557a8f87e8c0.yaml b/releasenotes/notes/0.15/custom-iterators-dce8557a8f87e8c0.yaml index dd19a50e90..43b0ff813d 100644 --- a/releasenotes/notes/0.15/custom-iterators-dce8557a8f87e8c0.yaml +++ b/releasenotes/notes/0.15/custom-iterators-dce8557a8f87e8c0.yaml @@ -7,4 +7,4 @@ features: of approximately 40% for iterating through the custom iterables. These types are not directly nameable or constructable from Python space, and other than the - performance improvement, the behavior should largely not be noticable from Python space. + performance improvement, the behavior should largely not be noticeable from Python space. diff --git a/releasenotes/notes/0.15/fix-mpl-draw-digraph-plots-aecf86738ab9b0db.yaml b/releasenotes/notes/0.15/fix-mpl-draw-digraph-plots-aecf86738ab9b0db.yaml index 377d74e67c..2ffd392d95 100644 --- a/releasenotes/notes/0.15/fix-mpl-draw-digraph-plots-aecf86738ab9b0db.yaml +++ b/releasenotes/notes/0.15/fix-mpl-draw-digraph-plots-aecf86738ab9b0db.yaml @@ -4,7 +4,7 @@ fixes: Fixed the plots of multigraphs using :func:`.mpl_draw`. Previously, parallel edges of multigraphs were plotted on top of each other, with overlapping arrows and labels. The radius of parallel edges of the multigraph was fixed to be `0.25` for - `connectionstyle` supporting this argument in :func:`.draw_edges`. The edge lables + `connectionstyle` supporting this argument in :func:`.draw_edges`. The edge labels were offset to `0.25` in :func:`.draw_edge_labels` to align with their respective edges. This fix can be tested using the following code: diff --git a/releasenotes/notes/0.15/lexicographical-topo-sort-core-e85fba409d612600.yaml b/releasenotes/notes/0.15/lexicographical-topo-sort-core-e85fba409d612600.yaml index 7aa6d5f6a6..fc8049afdb 100644 --- a/releasenotes/notes/0.15/lexicographical-topo-sort-core-e85fba409d612600.yaml +++ b/releasenotes/notes/0.15/lexicographical-topo-sort-core-e85fba409d612600.yaml @@ -2,6 +2,6 @@ features: - | Added a new function ``lexicographical_topological_sort`` to the - ``rustworkx_core::dag_algo`` module. That is a gneric Rust implementation + ``rustworkx_core::dag_algo`` module. That is a generic Rust implementation for the core rust library that provides the :func:`.lexicographical_topological_sort` function to Rust users. diff --git a/releasenotes/notes/0.15/maximum-bisimulation-942a9d0dc9b46ee4.yaml b/releasenotes/notes/0.15/maximum-bisimulation-942a9d0dc9b46ee4.yaml index bcb9e20493..cb3b3bfdfb 100644 --- a/releasenotes/notes/0.15/maximum-bisimulation-942a9d0dc9b46ee4.yaml +++ b/releasenotes/notes/0.15/maximum-bisimulation-942a9d0dc9b46ee4.yaml @@ -4,7 +4,7 @@ features: compute the maximum bisimulation or relational coarsest partition of a graph. This function is based on the algorithm described in the publication "Three partition refinement algorithms" by Paige and Tarjan. This function - recieves a graph and returns a + receives a graph and returns a :class:`~rustworkx.RelationalCoarsestPartition`. - | Added a new class :class:`~rustworkx.RelationalCoarsestPartition` to output diff --git a/releasenotes/notes/0.15/swap-nox-tox-dea2bb14c400641c.yaml b/releasenotes/notes/0.15/swap-nox-tox-dea2bb14c400641c.yaml index fd94ad28d5..d3bca14661 100644 --- a/releasenotes/notes/0.15/swap-nox-tox-dea2bb14c400641c.yaml +++ b/releasenotes/notes/0.15/swap-nox-tox-dea2bb14c400641c.yaml @@ -1,7 +1,7 @@ --- other: - | - For developement of rustworkx the automated testing environment + For development of rustworkx the automated testing environment tooling used has switched from Tox to instead `Nox `__. This is has no impact for end users and is only relevant if you contribute code to rustworkx. diff --git a/releasenotes/notes/0.9.0/add-nx-converter-1feffc8d5aa13365.yaml b/releasenotes/notes/0.9.0/add-nx-converter-1feffc8d5aa13365.yaml index 2d8fb96a27..2046bf24f3 100644 --- a/releasenotes/notes/0.9.0/add-nx-converter-1feffc8d5aa13365.yaml +++ b/releasenotes/notes/0.9.0/add-nx-converter-1feffc8d5aa13365.yaml @@ -4,7 +4,7 @@ features: A new function :func:`rustworkx.networkx_converter` has been added. This function takes in a networkx ``Graph`` object and will generate an equivalent :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` - object. While this function is provided as a convience for users of + object. While this function is provided as a convenience for users of both rustworkx and networkx, networkx will **not** be added as a dependency of rustworkx (which precludes a rustworkx->networkx converter, see :ref:`networkx_converter` for example code on how to build this yourself). diff --git a/releasenotes/notes/fix-typos-8f68ff3d0680b924.yaml b/releasenotes/notes/fix-typos-8f68ff3d0680b924.yaml new file mode 100644 index 0000000000..21f6b9712e --- /dev/null +++ b/releasenotes/notes/fix-typos-8f68ff3d0680b924.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fix typos detected by `typos `_. + Add spell checker invocations to the Nox ``lint`` session. diff --git a/rustworkx-core/src/bipartite_coloring.rs b/rustworkx-core/src/bipartite_coloring.rs index a4bdbecf42..a8519f3578 100644 --- a/rustworkx-core/src/bipartite_coloring.rs +++ b/rustworkx-core/src/bipartite_coloring.rs @@ -538,7 +538,7 @@ where } // Reconstruct coloring of the original graph by iterating over the edges, finding the - // correponding edge (endpoints) in the multigraph, and selecting the last (not yet + // corresponding edge (endpoints) in the multigraph, and selecting the last (not yet // assigned) color of that edge let mut edge_coloring: DictMap = DictMap::with_capacity(graph.edge_count()); for edge in graph.edge_references() { diff --git a/rustworkx-core/src/coloring.rs b/rustworkx-core/src/coloring.rs index 730119edc4..d764ffcf55 100644 --- a/rustworkx-core/src/coloring.rs +++ b/rustworkx-core/src/coloring.rs @@ -401,7 +401,7 @@ where /// Arguments: /// /// * `graph` - The graph object to run the algorithm on -/// * `preset_color_fn` - A callback function that will recieve the node identifier +/// * `preset_color_fn` - A callback function that will receive the node identifier /// for each node in the graph and is expected to return an `Option` /// (wrapped in a `Result`) that is `None` if the node has no preset and /// the usize represents the preset color. diff --git a/rustworkx-core/src/connectivity/all_simple_paths.rs b/rustworkx-core/src/connectivity/all_simple_paths.rs index c3f08c48fc..bbe39acf71 100644 --- a/rustworkx-core/src/connectivity/all_simple_paths.rs +++ b/rustworkx-core/src/connectivity/all_simple_paths.rs @@ -80,8 +80,8 @@ where // list of visited nodes let mut visited: IndexSet = IndexSet::from_iter(Some(from)); - // list of childs of currently exploring path nodes, - // last elem is list of childs of last visited node + // list of children of currently exploring path nodes, + // last elem is list of children of last visited node let mut stack = vec![graph.neighbors_directed(from, Outgoing)]; let mut output: DictMap>> = DictMap::with_capacity(to.len()); @@ -174,8 +174,8 @@ where { // list of visited nodes let mut visited: IndexSet = IndexSet::from_iter(Some(from)); - // list of childs of currently exploring path nodes, - // last elem is list of childs of last visited node + // list of children of currently exploring path nodes, + // last elem is list of children of last visited node let mut stack = vec![graph.neighbors_directed(from, Outgoing)]; let mut output_path: Option> = None; diff --git a/rustworkx-core/src/connectivity/biconnected.rs b/rustworkx-core/src/connectivity/biconnected.rs index b0a1b0f6ac..edf0d492db 100644 --- a/rustworkx-core/src/connectivity/biconnected.rs +++ b/rustworkx-core/src/connectivity/biconnected.rs @@ -336,7 +336,7 @@ mod tests { #[test] fn test_biconnected_components1() { - // exmaple from https://web.archive.org/web/20121229123447/http://www.ibluemojo.com/school/articul_algorithm.html + // example from https://web.archive.org/web/20121229123447/http://www.ibluemojo.com/school/articul_algorithm.html let graph = UnGraph::<(), ()>::from_edges([ (0, 1), (0, 5), diff --git a/rustworkx-core/src/connectivity/chain.rs b/rustworkx-core/src/connectivity/chain.rs index 5f8c52ca55..c2190b0ec1 100644 --- a/rustworkx-core/src/connectivity/chain.rs +++ b/rustworkx-core/src/connectivity/chain.rs @@ -61,7 +61,7 @@ where /// The graph should be undirected. If `source` is specified only the chain /// decomposition for the connected component containing this node will be returned. /// This node indicates the root of the depth-first search tree. If it's not -/// specified, a source will be chosen arbitrarly and repeated until all components +/// specified, a source will be chosen arbitrarily and repeated until all components /// of the graph are searched. /// /// Returns a list of list of edges where each inner list is a chain. diff --git a/rustworkx-core/src/connectivity/cycle_basis.rs b/rustworkx-core/src/connectivity/cycle_basis.rs index f638aa0ef6..4f8e72b4e4 100644 --- a/rustworkx-core/src/connectivity/cycle_basis.rs +++ b/rustworkx-core/src/connectivity/cycle_basis.rs @@ -92,10 +92,10 @@ where cycles.push(cycle); // A cycle was found: } else if !used.get(&z).unwrap().contains(&neighbor) { - let pn = used.get(&neighbor).unwrap(); + let prev_n = used.get(&neighbor).unwrap(); let mut cycle: Vec = vec![neighbor, z]; let mut p = pred.get(&z).unwrap(); - while !pn.contains(p) { + while !prev_n.contains(p) { cycle.push(*p); p = pred.get(p).unwrap(); } diff --git a/rustworkx-core/src/dag_algo.rs b/rustworkx-core/src/dag_algo.rs index 7af62d6207..34c7e19f21 100644 --- a/rustworkx-core/src/dag_algo.rs +++ b/rustworkx-core/src/dag_algo.rs @@ -33,7 +33,7 @@ use num_traits::{Num, Zero}; use crate::err::LayersError; /// Return a pair of [`petgraph::Direction`] values corresponding to the "forwards" and "backwards" -/// direction of graph traversal, based on whether the graph is being traved forwards (following +/// direction of graph traversal, based on whether the graph is being traversed forwards (following /// the edges) or backward (reversing along edges). The order of returns is (forwards, backwards). #[inline(always)] pub fn traversal_directions(reverse: bool) -> (petgraph::Direction, petgraph::Direction) { @@ -719,7 +719,7 @@ where Some(runs) } -/// Auxiliary struct to make the output of [`collect_runs`] iteratable +/// Auxiliary struct to make the output of [`collect_runs`] iterable /// /// If the filtering function passed to [`collect_runs`] returns an error, it is propagated /// through `next` as `Err`. In this case the run in which the error occurred will be skipped diff --git a/rustworkx-core/src/generators/complete_graph.rs b/rustworkx-core/src/generators/complete_graph.rs index c07f4ee3d3..df8b36d7ea 100644 --- a/rustworkx-core/src/generators/complete_graph.rs +++ b/rustworkx-core/src/generators/complete_graph.rs @@ -22,7 +22,7 @@ use super::InvalidInputError; /// /// * `num_nodes` - The number of nodes to create a complete graph for. Either this or /// `weights` must be specified. If both this and `weights` are specified, `weights` -/// will take priorty and this argument will be ignored +/// will take priority and this argument will be ignored /// * `weights` - A `Vec` of node weight objects. /// * `default_node_weight` - A callable that will return the weight to use /// for newly created nodes. This is ignored if `weights` is specified. diff --git a/rustworkx-core/src/generators/cycle_graph.rs b/rustworkx-core/src/generators/cycle_graph.rs index 0cc603d8be..6462e0515f 100644 --- a/rustworkx-core/src/generators/cycle_graph.rs +++ b/rustworkx-core/src/generators/cycle_graph.rs @@ -22,7 +22,7 @@ use super::InvalidInputError; /// /// * `num_nodes` - The number of nodes to create a cycle graph for. Either this or /// `weights` must be specified. If both this and `weights` are specified, `weights` -/// will take priorty and this argument will be ignored. +/// will take priority and this argument will be ignored. /// * `weights` - A `Vec` of node weight objects. /// * `default_node_weight` - A callable that will return the weight to use /// for newly created nodes. This is ignored if `weights` is specified. diff --git a/rustworkx-core/src/generators/karate_club.rs b/rustworkx-core/src/generators/karate_club.rs index d0e0ac495e..63698665c1 100644 --- a/rustworkx-core/src/generators/karate_club.rs +++ b/rustworkx-core/src/generators/karate_club.rs @@ -23,9 +23,9 @@ use petgraph::visit::{Data, NodeIndexable}; /// /// * `default_node_weight` - A callable that will receive a boolean, indicating /// if a node is part of Mr Hi's faction (True) or the Officer's faction (false). -/// It shoudl return the node weight according to the desired type. +/// It should return the node weight according to the desired type. /// * `default_edge_weight` - A callable that will receive the integer representing -/// the strenght of the relation between two nodes. It should return the edge +/// the strength of the relation between two nodes. It should return the edge /// weight according to the desired type. /// pub fn karate_club_graph(mut default_node_weight: F, mut default_edge_weight: H) -> G diff --git a/rustworkx-core/src/generators/path_graph.rs b/rustworkx-core/src/generators/path_graph.rs index 7aff79d7b3..373a7d62a0 100644 --- a/rustworkx-core/src/generators/path_graph.rs +++ b/rustworkx-core/src/generators/path_graph.rs @@ -22,7 +22,7 @@ use super::InvalidInputError; /// /// * `num_nodes` - The number of nodes to create a path graph for. Either this or /// `weights` must be specified. If both this and `weights` are specified, `weights` -/// will take priorty and this argument will be ignored +/// will take priority and this argument will be ignored /// * `weights` - A `Vec` of node weight objects. /// * `default_node_weight` - A callable that will return the weight to use /// for newly created nodes. This is ignored if `weights` is specified. diff --git a/rustworkx-core/src/generators/star_graph.rs b/rustworkx-core/src/generators/star_graph.rs index a09565c935..d9630dfdcf 100644 --- a/rustworkx-core/src/generators/star_graph.rs +++ b/rustworkx-core/src/generators/star_graph.rs @@ -22,7 +22,7 @@ use super::InvalidInputError; /// /// * `num_nodes` - The number of nodes to create a star graph for. Either this or /// `weights` must be specified. If both this and `weights` are specified, weights -/// will take priorty and this argument will be ignored +/// will take priority and this argument will be ignored /// * `weights` - A `Vec` of node weight objects. /// * `default_node_weight` - A callable that will return the weight to use /// for newly created nodes. This is ignored if `weights` is specified. diff --git a/rustworkx-core/src/max_weight_matching.rs b/rustworkx-core/src/max_weight_matching.rs index 193b1ff877..d7400d32b5 100644 --- a/rustworkx-core/src/max_weight_matching.rs +++ b/rustworkx-core/src/max_weight_matching.rs @@ -11,7 +11,7 @@ // under the License. // Needed to pass shared state between functions -// closures don't work because of recurssion +// closures don't work because of recursion #![allow(clippy::too_many_arguments)] // Allow single character names to match naming convention from // paper @@ -87,7 +87,7 @@ fn assign_label( best_edge[w] = None; best_edge[b] = None; if t == 1 { - // b became an S-vertex/blossom; add it(s verticies) to the queue + // b became an S-vertex/blossom; add it(s vertices) to the queue queue.append(&mut blossom_leaves(b, num_nodes, blossom_children)?); } else if t == 2 { // b became a T-vertex/blossom; assign label S to its mate. @@ -142,7 +142,7 @@ fn scan_blossom( assert!(labels[blossom] == Some(1)); path.push(blossom); labels[blossom] = Some(5); - // Trace one step bacl. + // Trace one step back. assert!(label_ends[blossom] == mate.get(&blossom_base[blossom].unwrap()).copied()); if label_ends[blossom].is_none() { // The base of blossom is single; stop tracing this path @@ -160,7 +160,7 @@ fn scan_blossom( mem::swap(&mut v, &mut w); } } - // Remvoe breadcrumbs. + // Remove breadcrumbs. for blossom in path { labels[blossom] = Some(1); } @@ -371,7 +371,7 @@ fn expand_blossom( // base. assert!(label_ends[blossom].is_some()); let entry_child = in_blossoms[endpoints[label_ends[blossom].unwrap() ^ 1]]; - // Decied in which direction we will go around the blossom. + // Decide in which direction we will go around the blossom. let i = blossom_children[blossom] .iter() .position(|x| *x == entry_child) @@ -809,7 +809,7 @@ fn verify_optimum( /// Based on networkx implementation /// /// -/// With reference to the standalone protoype implementation from: +/// With reference to the standalone prototype implementation from: /// /// /// @@ -884,9 +884,9 @@ where if num_edges == 0 { return Ok(HashSet::new()); } - // Node indicies in the PyGraph may not be contiguous however the + // Node indices in the PyGraph may not be contiguous however the // algorithm operates on contiguous indices 0..num_nodes. node_map maps - // the PyGraph's NodeIndex to the contingous usize used inside the + // the PyGraph's NodeIndex to the contiguous usize used inside the // algorithm let node_map: HashMap = graph .node_identifiers() @@ -1018,7 +1018,7 @@ where best_edge = vec![None; 2 * num_nodes]; blossom_best_edges.splice(num_nodes.., (0..num_nodes).map(|_| Vec::new())); // Loss of labeling means that we can not be sure that currently - // allowable edges remain allowable througout this stage. + // allowable edges remain allowable throughout this stage. allowed_edge = vec![false; num_edges]; // Make queue empty queue.clear(); @@ -1248,7 +1248,7 @@ where if delta_type == -1 { // No further improvement possible; max-cardinality optimum // reached. Do a final delta update to make the optimum - // verifyable + // verifiable assert!(max_cardinality); delta_type = 1; delta = Some(max(0, *dual_var[..num_nodes].iter().min().unwrap())); @@ -1275,7 +1275,7 @@ where } } } - // Take action at the point where minimum delta occured. + // Take action at the point where minimum delta occurred. if delta_type == 1 { // No further improvement possible; optimum reached break; diff --git a/rustworkx-core/src/planar/lr_planar.rs b/rustworkx-core/src/planar/lr_planar.rs index bfe77fe5cd..7db398a5c1 100644 --- a/rustworkx-core/src/planar/lr_planar.rs +++ b/rustworkx-core/src/planar/lr_planar.rs @@ -211,7 +211,7 @@ where graph: G, /// roots of the DFS forest. roots: Vec, - /// distnace from root. + /// distance from root. height: HashMap, /// parent edge. eparent: HashMap>, diff --git a/rustworkx-core/src/steiner_tree.rs b/rustworkx-core/src/steiner_tree.rs index 80fab566fd..3d6afb2977 100644 --- a/rustworkx-core/src/steiner_tree.rs +++ b/rustworkx-core/src/steiner_tree.rs @@ -449,7 +449,7 @@ pub struct SteinerTreeResult { /// /// The minimum tree of ``graph`` with regard to a set of ``terminal_nodes`` /// is a tree within ``graph`` that spans those nodes and has a minimum size -/// (measured as the sum of edge weights) amoung all such trees. +/// (measured as the sum of edge weights) among all such trees. /// /// The minimum steiner tree can be approximated by computing the minimum /// spanning tree of the subgraph of the metric closure of ``graph`` induced diff --git a/rustworkx/__init__.py b/rustworkx/__init__.py index a470c71f07..a00dd5a4a8 100644 --- a/rustworkx/__init__.py +++ b/rustworkx/__init__.py @@ -24,7 +24,7 @@ class PyDAG(PyDiGraph): """A class for creating direct acyclic graphs. PyDAG is just an alias of the PyDiGraph class and behaves identically to - the :class:`~rustworkx.PyDiGraph` class and can be used interchangably + the :class:`~rustworkx.PyDiGraph` class and can be used interchangeably with ``PyDiGraph``. It currently exists solely as a backwards compatibility alias for users of rustworkx from prior to the 0.4.0 release when there was no PyDiGraph class. @@ -641,7 +641,7 @@ def dfs_edges(graph, source=None): :param int source: An optional node index to use as the starting node for the depth-first search. The edge list will only return edges in the components reachable from this index. If this is not specified - then a source will be chosen arbitrarly and repeated until all + then a source will be chosen arbitrarily and repeated until all components of the graph are searched. :returns: A list of edges as a tuple of the form ``(source, target)`` in @@ -1003,7 +1003,7 @@ def bipartite_layout( :param graph: The graph to generate the layout for. Can either be a :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` :param set first_nodes: The set of node indices on the left (or top if - horitontal is true) + horizontal is true) :param bool horizontal: An optional bool specifying the orientation of the layout :param float scale: An optional scaling factor to scale positions @@ -1344,7 +1344,7 @@ def vf2_mapping( """ Return an iterator over all vf2 mappings between two graphs. - This funcion will run the vf2 algorithm used from + This function will run the vf2 algorithm used from :func:`~rustworkx.is_isomorphic` and :func:`~rustworkx.is_subgraph_isomorphic` but instead of returning a boolean it will return an iterator over all possible mapping of node ids found from ``first`` to ``second``. If the graphs are not @@ -1381,7 +1381,7 @@ def vf2_mapping( algorithm visits while searching for a solution. If it exceeds this limit, the algorithm will stop. Default: ``None``. - :returns: An iterator over dicitonaries of node indices from ``first`` to node + :returns: An iterator over dictionaries of node indices from ``first`` to node indices in ``second`` representing the mapping found. :rtype: Iterable[NodeMap] """ @@ -1555,7 +1555,7 @@ def tree_edge(self, edge): or a :class:`~rustworkx.PyDiGraph` :param List[int] source: An optional list of node indices to use as the starting nodes for the breadth-first search. If this is not specified then a source - will be chosen arbitrarly and repeated until all components of the + will be chosen arbitrarily and repeated until all components of the graph are searched. :param visitor: A visitor object that is invoked at the event points inside the algorithm. This should be a subclass of :class:`~rustworkx.visit.BFSVisitor`. @@ -1625,7 +1625,7 @@ def tree_edge(self, edge): :param PyGraph graph: The graph to be used. :param List[int] source: An optional list of node indices to use as the starting nodes for the depth-first search. If this is not specified then a source - will be chosen arbitrarly and repeated until all components of the + will be chosen arbitrarily and repeated until all components of the graph are searched. :param visitor: A visitor object that is invoked at the event points inside the algorithm. This should be a subclass of :class:`~rustworkx.visit.DFSVisitor`. @@ -1678,7 +1678,7 @@ def dijkstra_search(graph, source, weight_fn, visitor): or a :class:`~rustworkx.PyDiGraph`. :param List[int] source: An optional list of node indices to use as the starting nodes for the dijkstra search. If this is not specified then a source - will be chosen arbitrarly and repeated until all components of the + will be chosen arbitrarily and repeated until all components of the graph are searched. :param weight_fn: An optional weight function for an edge. It will accept a single argument, the edge's weight object and will return a float which diff --git a/rustworkx/visualization/matplotlib.py b/rustworkx/visualization/matplotlib.py index bd06c243b8..458eafe25e 100644 --- a/rustworkx/visualization/matplotlib.py +++ b/rustworkx/visualization/matplotlib.py @@ -581,7 +581,7 @@ def draw_edges( Label for legend min_source_margin : int (default=0) - The minimum margin (gap) at the begining of the edge at the source. + The minimum margin (gap) at the beginning of the edge at the source. min_target_margin : int (default=0) The minimum margin (gap) at the end of the edge at the target. @@ -978,7 +978,7 @@ def draw_edge_labels( ax : Matplotlib Axes object, optional Draw the graph in the specified Matplotlib axes. - rotate : bool (deafult=True) + rotate : bool (default=True) Rotate edge labels to lie parallel to edges clip_on : bool (default=True) diff --git a/src/bisimulation.rs b/src/bisimulation.rs index 4a0525f38c..96883e96ca 100644 --- a/src/bisimulation.rs +++ b/src/bisimulation.rs @@ -309,11 +309,11 @@ fn maximum_bisimulation(graph: &StablePyGraph) -> Option> { let mut counterimage = build_counterimage(graph, smaller_component); let counterimage_group = group_by_counterimage(counterimage.clone(), &node_to_block_vec); - let ((new_fine_blocks, removeable_fine_blocks), coarse_block_that_are_now_compound) = + let ((new_fine_blocks, removable_fine_blocks), coarse_block_that_are_now_compound) = split_blocks_with_grouped_counterimage(counterimage_group, &mut node_to_block_vec); all_fine_blocks.extend(new_fine_blocks); - all_fine_blocks.retain(|x| !removeable_fine_blocks.iter().any(|y| Rc::ptr_eq(x, y))); + all_fine_blocks.retain(|x| !removable_fine_blocks.iter().any(|y| Rc::ptr_eq(x, y))); queue.extend(coarse_block_that_are_now_compound); // counterimage = E^{-1}(B) - E^{-1}(S-B) @@ -327,11 +327,11 @@ fn maximum_bisimulation(graph: &StablePyGraph) -> Option> { } let counterimage_group = group_by_counterimage(counterimage, &node_to_block_vec); - let ((new_fine_blocks, removeable_fine_blocks), coarse_block_that_are_now_compound) = + let ((new_fine_blocks, removable_fine_blocks), coarse_block_that_are_now_compound) = split_blocks_with_grouped_counterimage(counterimage_group, &mut node_to_block_vec); all_fine_blocks.extend(new_fine_blocks); - all_fine_blocks.retain(|x| !removeable_fine_blocks.iter().any(|y| Rc::ptr_eq(x, y))); + all_fine_blocks.retain(|x| !removable_fine_blocks.iter().any(|y| Rc::ptr_eq(x, y))); queue.extend(coarse_block_that_are_now_compound); } diff --git a/src/connectivity/mod.rs b/src/connectivity/mod.rs index 353a57fe25..ecd2858be9 100644 --- a/src/connectivity/mod.rs +++ b/src/connectivity/mod.rs @@ -1073,7 +1073,7 @@ pub fn biconnected_components(graph: &graph::PyGraph) -> BiconnectedComponents { /// only the chain decomposition for the connected component containing /// this node will be returned. This node indicates the root of the depth-first /// search tree. If this is not specified then a source will be chosen -/// arbitrarly and repeated until all components of the graph are searched. +/// arbitrarily and repeated until all components of the graph are searched. /// :returns: A list of list of edges where each inner list is a chain. /// :rtype: list of EdgeList /// diff --git a/src/dag_algo/mod.rs b/src/dag_algo/mod.rs index 0adfff12a2..da5de0cb5b 100644 --- a/src/dag_algo/mod.rs +++ b/src/dag_algo/mod.rs @@ -81,7 +81,7 @@ where } /// Return a pair of [`petgraph::Direction`] values corresponding to the "forwards" and "backwards" -/// direction of graph traversal, based on whether the graph is being traved forwards (following +/// direction of graph traversal, based on whether the graph is being traversed forwards (following /// the edges) or backward (reversing along edges). The order of returns is (forwards, backwards). #[inline(always)] pub fn traversal_directions(reverse: bool) -> (petgraph::Direction, petgraph::Direction) { diff --git a/src/digraph.rs b/src/digraph.rs index 47b70b93aa..e70d111b6e 100644 --- a/src/digraph.rs +++ b/src/digraph.rs @@ -637,15 +637,15 @@ impl PyDiGraph { let children = self .graph .neighbors_directed(index, petgraph::Direction::Outgoing); - let mut succesors: Vec<&PyObject> = Vec::new(); + let mut successors: Vec<&PyObject> = Vec::new(); let mut used_indices: HashSet = HashSet::new(); for succ in children { if !used_indices.contains(&succ) { - succesors.push(self.graph.node_weight(succ).unwrap()); + successors.push(self.graph.node_weight(succ).unwrap()); used_indices.insert(succ); } } - succesors + successors } /// Return a list of all the node predecessor data. @@ -692,7 +692,7 @@ impl PyDiGraph { filter_fn: PyObject, ) -> PyResult> { let index = NodeIndex::new(node); - let mut succesors: Vec<&PyObject> = Vec::new(); + let mut successors: Vec<&PyObject> = Vec::new(); let mut used_indices: HashSet = HashSet::new(); let filter_edge = |edge: &PyObject| -> PyResult { @@ -710,11 +710,11 @@ impl PyDiGraph { let edge_weight = edge.weight(); if filter_edge(edge_weight)? { used_indices.insert(succ); - succesors.push(self.graph.node_weight(succ).unwrap()); + successors.push(self.graph.node_weight(succ).unwrap()); } } } - Ok(succesors) + Ok(successors) } /// Return a filtered list of predecessor data such that each @@ -1004,7 +1004,7 @@ impl PyDiGraph { /// :meth:`remove_node_retain_edges_by_id`. /// /// :param int node: The index of the node to remove. If the index is not - /// present in the graph it will be ingored and this function willl have + /// present in the graph it will be ignored and this function will have /// no effect. /// :param bool use_outgoing: If set to true the weight/data from the /// edge outgoing from ``node`` will be used in the retained edge @@ -1754,12 +1754,12 @@ impl PyDiGraph { /// Get the successor indices of a node. /// - /// This will return a list of the node indicies for the succesors of + /// This will return a list of the node indices for the successors of /// a node /// /// :param int node: The index of the node to get the successors of /// - /// :returns: A list of the neighbor node indicies + /// :returns: A list of the neighbor node indices /// :rtype: NodeIndices #[pyo3(text_signature = "(self, node, /)")] pub fn successor_indices(&self, node: usize) -> NodeIndices { @@ -1774,12 +1774,12 @@ impl PyDiGraph { /// Get the predecessor indices of a node. /// - /// This will return a list of the node indicies for the predecessors of + /// This will return a list of the node indices for the predecessors of /// a node /// /// :param int node: The index of the node to get the predecessors of /// - /// :returns: A list of the neighbor node indicies + /// :returns: A list of the neighbor node indices /// :rtype: NodeIndices #[pyo3(text_signature = "(self, node, /)")] pub fn predecessor_indices(&self, node: usize) -> NodeIndices { @@ -2162,8 +2162,8 @@ impl PyDiGraph { /// Read an edge list file and create a new PyDiGraph object from the /// contents /// - /// The expected format for the edge list file is a line seperated list - /// of deliminated node ids. If there are more than 3 elements on + /// The expected format for the edge list file is a line separated list + /// of delimited node ids. If there are more than 3 elements on /// a line the 3rd on will be treated as a string weight for the edge /// /// :param str path: The path of the file to open diff --git a/src/generators.rs b/src/generators.rs index 82b75b8a4d..3db2765d33 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -989,7 +989,7 @@ pub fn binomial_tree_graph( /// /// :returns: A directed binomial tree with 2^n vertices and 2^n - 1 edges. /// :rtype: PyDiGraph -/// :raises IndexError: If the lenght of ``weights`` is greater that 2^n +/// :raises IndexError: If the length of ``weights`` is greater that 2^n /// :raises OverflowError: If the input order exceeds the maximum value for the /// current platform. /// @@ -1059,7 +1059,7 @@ pub fn directed_binomial_tree_graph( /// /// :returns: A r-ary tree. /// :rtype: PyGraph -/// :raises IndexError: If the lenght of ``weights`` is greater that n +/// :raises IndexError: If the length of ``weights`` is greater that n /// /// .. jupyter-execute:: /// diff --git a/src/graph.rs b/src/graph.rs index c3e627e5ce..21f375b93e 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -1124,7 +1124,7 @@ impl PyGraph { /// /// :param int node: The index of the node to get the neighbors of /// - /// :returns: A list of the neighbor node indicies + /// :returns: A list of the neighbor node indices /// :rtype: NodeIndices #[pyo3(text_signature = "(self, node, /)")] pub fn neighbors(&self, node: usize) -> NodeIndices { @@ -1284,8 +1284,8 @@ impl PyGraph { /// Read an edge list file and create a new PyGraph object from the /// contents /// - /// The expected format for the edge list file is a line seperated list - /// of deliminated node ids. If there are more than 3 elements on + /// The expected format for the edge list file is a line separated list + /// of delimited node ids. If there are more than 3 elements on /// a line the 3rd on will be treated as a string weight for the edge /// /// :param str path: The path of the file to open diff --git a/src/isomorphism/mod.rs b/src/isomorphism/mod.rs index 6e48f1f108..43d4100b2c 100644 --- a/src/isomorphism/mod.rs +++ b/src/isomorphism/mod.rs @@ -295,7 +295,7 @@ pub fn graph_is_subgraph_isomorphic( /// Return an iterator over all vf2 mappings between two :class:`~rustworkx.PyDiGraph` objects /// -/// This funcion will run the vf2 algorithm used from +/// This function will run the vf2 algorithm used from /// :func:`~rustworkx.is_isomorphic` and :func:`~rustworkx.is_subgraph_isomorphic` /// but instead of returning a boolean it will return an iterator over all possible /// mapping of node ids found from ``first`` to ``second``. If the graphs are not @@ -333,7 +333,7 @@ pub fn graph_is_subgraph_isomorphic( /// visits while searching for a solution. If it exceeds this limit, the algorithm /// will stop. /// -/// :returns: An iterator over dicitonaries of node indices from ``first`` to node +/// :returns: An iterator over dictionaries of node indices from ``first`` to node /// indices in ``second`` representing the mapping found. /// :rtype: Iterable[NodeMap] #[pyfunction] @@ -374,7 +374,7 @@ pub fn digraph_vf2_mapping( /// Return an iterator over all vf2 mappings between two :class:`~rustworkx.PyGraph` objects /// -/// This funcion will run the vf2 algorithm used from +/// This function will run the vf2 algorithm used from /// :func:`~rustworkx.is_isomorphic` and :func:`~rustworkx.is_subgraph_isomorphic` /// but instead of returning a boolean it will return an iterator over all possible /// mapping of node ids found from ``first`` to ``second``. If the graphs are not @@ -411,7 +411,7 @@ pub fn digraph_vf2_mapping( /// visits while searching for a solution. If it exceeds this limit, the algorithm /// will stop. Default: ``None``. /// -/// :returns: An iterator over dicitonaries of node indices from ``first`` to node +/// :returns: An iterator over dictionaries of node indices from ``first`` to node /// indices in ``second`` representing the mapping found. /// :rtype: Iterable[NodeMap] #[pyfunction] diff --git a/src/isomorphism/vf2.rs b/src/isomorphism/vf2.rs index f3e77c7c1e..af8104d505 100644 --- a/src/isomorphism/vf2.rs +++ b/src/isomorphism/vf2.rs @@ -168,7 +168,7 @@ where fn sort(&self, graph: &StablePyGraph) -> Vec { let n = graph.node_bound(); - let dout: Vec = (0..n) + let d_out: Vec = (0..n) .map(|idx| { graph .neighbors_directed(graph.from_index(idx), Outgoing) @@ -176,9 +176,9 @@ where }) .collect(); - let mut din: Vec = vec![0; n]; + let mut d_in: Vec = vec![0; n]; if graph.is_directed() { - din = (0..n) + d_in = (0..n) .map(|idx| { graph .neighbors_directed(graph.from_index(idx), Incoming) @@ -202,9 +202,9 @@ where .max_by_key(|&(_, &node)| { ( conn_in[node], - dout[node], + d_out[node], conn_out[node], - din[node], + d_in[node], Reverse(node), ) }) @@ -256,7 +256,7 @@ where }; let mut sorted_nodes: Vec = graph.node_indices().map(|node| node.index()).collect(); - sorted_nodes.par_sort_by_key(|&node| (dout[node], din[node], Reverse(node))); + sorted_nodes.par_sort_by_key(|&node| (d_out[node], d_in[node], Reverse(node))); sorted_nodes.reverse(); for node in sorted_nodes { diff --git a/src/iterators.rs b/src/iterators.rs index cabfbe1c37..34acb85693 100644 --- a/src/iterators.rs +++ b/src/iterators.rs @@ -34,7 +34,7 @@ // don't store any python object, just use `impl PyGCProtocol for MyReadOnlyType {}`. // // Types `T, K, V` above should implement `PyHash`, `PyEq`, `PyDisplay` traits. -// These are arleady implemented for many primitive rust types and `PyObject`. +// These are already implemented for many primitive rust types and `PyObject`. #![allow(clippy::float_cmp, clippy::upper_case_acronyms)] @@ -1088,7 +1088,7 @@ custom_vec_iter_impl!( The class is a read-only sequence of integers instances. - This class is a container class for the results of the digraph_maximum_bisimulation funtion. + This class is a container class for the results of the digraph_maximum_bisimulation function. It implements the Python sequence protocol. So you can treat the return as a read-only sequence/list that is integer indexed. If you want to use it as an iterator you @@ -1144,7 +1144,7 @@ custom_vec_iter_impl!( The class is a read-only sequence of :class:`.NodeIndices` instances. - This class is a container class for the results of the digraph_maximum_bisimulation funtion. + This class is a container class for the results of the digraph_maximum_bisimulation function. It implements the Python sequence protocol. So you can treat the return as a read-only sequence/list that is integer indexed. If you want to use it as an iterator you diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 802207b1c4..786642bfb8 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -237,7 +237,7 @@ pub fn digraph_random_layout( /// /// :param PyGraph graph: The graph to generate the layout for /// :param set first_nodes: The set of node indices on the left (or top if -/// horitontal is true) +/// horizontal is true) /// :param bool horizontal: An optional bool specifying the orientation of the /// layout /// :param float scale: An optional scaling factor to scale positions diff --git a/src/layout/spring.rs b/src/layout/spring.rs index 6209524998..8aa96223f9 100644 --- a/src/layout/spring.rs +++ b/src/layout/spring.rs @@ -183,7 +183,7 @@ pub fn rescale(pos: &mut [Point], scale: Nt, indices: Vec) { mu[0] /= n as Nt; mu[1] /= n as Nt; - // substract mean and find max coordinate for all axes + // subtract mean and find max coordinate for all axes let mut lim = f64::NEG_INFINITY; for n in indices { let [px, py] = pos.get_mut(n).unwrap(); diff --git a/src/shortest_path/all_pairs_dijkstra.rs b/src/shortest_path/all_pairs_dijkstra.rs index a6daff6117..bcfe94485c 100644 --- a/src/shortest_path/all_pairs_dijkstra.rs +++ b/src/shortest_path/all_pairs_dijkstra.rs @@ -76,10 +76,10 @@ pub fn all_pairs_dijkstra_path_lengths( let out_map: DictMap = node_indices .into_par_iter() .map(|x| { - let path_lenghts: PyResult>> = + let path_lengths: PyResult>> = dijkstra(graph, x, None, |e| edge_cost(e.id()), None); let out_map = PathLengthMapping { - path_lengths: path_lenghts + path_lengths: path_lengths .unwrap() .into_iter() .enumerate() diff --git a/src/shortest_path/mod.rs b/src/shortest_path/mod.rs index 1b9970327d..c062945d2a 100644 --- a/src/shortest_path/mod.rs +++ b/src/shortest_path/mod.rs @@ -1361,7 +1361,7 @@ pub fn graph_num_shortest_paths_unweighted( /// output distance matrix. /// :param float null_value: An optional float that will treated as a null /// value. This element will be the default in the matrix and represents -/// the absense of a path in the graph. By default this is ``0.0``. +/// the absence of a path in the graph. By default this is ``0.0``. /// /// :returns: The distance matrix /// :rtype: numpy.ndarray @@ -1403,7 +1403,7 @@ pub fn digraph_distance_matrix( /// be tuned /// :param float null_value: An optional float that will treated as a null /// value. This element will be the default in the matrix and represents -/// the absense of a path in the graph. By default this is ``0.0``. +/// the absence of a path in the graph. By default this is ``0.0``. /// /// :returns: The distance matrix /// :rtype: numpy.ndarray diff --git a/src/steiner_tree.rs b/src/steiner_tree.rs index 57819b9922..a854dcc1db 100644 --- a/src/steiner_tree.rs +++ b/src/steiner_tree.rs @@ -79,7 +79,7 @@ pub fn metric_closure( /// /// The minimum tree of ``graph`` with regard to a set of ``terminal_nodes`` /// is a tree within ``graph`` that spans those nodes and has a minimum size -/// (measured as the sum of edge weights) amoung all such trees. +/// (measured as the sum of edge weights) among all such trees. /// /// The minimum steiner tree can be approximated by computing the minimum /// spanning tree of the subgraph of the metric closure of ``graph`` induced diff --git a/src/transitivity.rs b/src/transitivity.rs index 6bcb25f94f..565342eefd 100644 --- a/src/transitivity.rs +++ b/src/transitivity.rs @@ -127,14 +127,14 @@ fn _digraph_triangles(graph: &digraph::PyDiGraph, node: usize) -> (usize, usize) .sum::(); } - let din: usize = in_neighbors.len(); - let dout: usize = out_neighbors.len(); + let d_in: usize = in_neighbors.len(); + let d_out: usize = out_neighbors.len(); - let dtot = dout + din; - let dbil: usize = out_neighbors.intersection(&in_neighbors).count(); - let triples: usize = match dtot { + let d_tot = d_out + d_in; + let d_bil: usize = out_neighbors.intersection(&in_neighbors).count(); + let triples: usize = match d_tot { 0 => 0, - _ => dtot * (dtot - 1) - 2 * dbil, + _ => d_tot * (d_tot - 1) - 2 * d_bil, }; (triangles / 2, triples) diff --git a/src/traversal/mod.rs b/src/traversal/mod.rs index 2d8531b48b..17444431d7 100644 --- a/src/traversal/mod.rs +++ b/src/traversal/mod.rs @@ -64,7 +64,7 @@ use crate::iterators::EdgeList; /// :param int source: An optional node index to use as the starting node /// for the depth-first search. The edge list will only return edges in /// the components reachable from this index. If this is not specified -/// then a source will be chosen arbitrarly and repeated until all +/// then a source will be chosen arbitrarily and repeated until all /// components of the graph are searched. /// /// :returns: A list of edges as a tuple of the form ``(source, target)`` in @@ -109,7 +109,7 @@ pub fn digraph_dfs_edges(graph: &digraph::PyDiGraph, source: Option) -> E /// :param int source: An optional node index to use as the starting node /// for the depth-first search. The edge list will only return edges in /// the components reachable from this index. If this is not specified -/// then a source will be chosen arbitrarly and repeated until all +/// then a source will be chosen arbitrarily and repeated until all /// components of the graph are searched. /// /// :returns: A list of edges as a tuple of the form ``(source, target)`` in @@ -310,7 +310,7 @@ pub fn descendants(graph: &digraph::PyDiGraph, node: usize) -> HashSet { /// :param PyDiGraph graph: The graph to be used. /// :param List[int] source: An optional list of node indices to use as the starting nodes /// for the breadth-first search. If this is not specified then a source -/// will be chosen arbitrarly and repeated until all components of the +/// will be chosen arbitrarily and repeated until all components of the /// graph are searched. /// :param visitor: A visitor object that is invoked at the event points inside the /// algorithm. This should be a subclass of :class:`~rustworkx.visit.BFSVisitor`. @@ -403,7 +403,7 @@ pub fn digraph_bfs_search( /// :param PyGraph graph: The graph to be used. /// :param List[int] source: An optional list of node indices to use as the starting nodes /// for the breadth-first search. If this is not specified then a source -/// will be chosen arbitrarly and repeated until all components of the +/// will be chosen arbitrarily and repeated until all components of the /// graph are searched. /// :param visitor: A visitor object that is invoked at the event points inside the /// algorithm. This should be a subclass of :class:`~rustworkx.visit.BFSVisitor`. @@ -494,7 +494,7 @@ pub fn graph_bfs_search( /// :param PyDiGraph graph: The graph to be used. /// :param List[int] source: An optional list of node indices to use as the starting nodes /// for the depth-first search. If this is not specified then a source -/// will be chosen arbitrarly and repeated until all components of the +/// will be chosen arbitrarily and repeated until all components of the /// graph are searched. /// :param visitor: A visitor object that is invoked at the event points inside the /// algorithm. This should be a subclass of :class:`~rustworkx.visit.DFSVisitor`. @@ -585,7 +585,7 @@ pub fn digraph_dfs_search( /// :param PyGraph graph: The graph to be used. /// :param List[int] source: An optional list of node indices to use as the starting nodes /// for the depth-first search. If this is not specified then a source -/// will be chosen arbitrarly and repeated until all components of the +/// will be chosen arbitrarily and repeated until all components of the /// graph are searched. /// :param visitor: A visitor object that is invoked at the event points inside the /// algorithm. This should be a subclass of :class:`~rustworkx.visit.DFSVisitor`. @@ -658,7 +658,7 @@ pub fn graph_dfs_search( /// :param PyDiGraph graph: The graph to be used. /// :param List[int] source: An optional list of node indices to use as the starting nodes /// for the dijkstra search. If this is not specified then a source -/// will be chosen arbitrarly and repeated until all components of the +/// will be chosen arbitrarily and repeated until all components of the /// graph are searched. /// :param weight_fn: An optional weight function for an edge. It will accept /// a single argument, the edge's weight object and will return a float which @@ -740,7 +740,7 @@ pub fn digraph_dijkstra_search( /// :param PyGraph graph: The graph to be used. /// :param List[int] source: An optional list of node indices to use as the starting nodes /// for the dijkstra search. If this is not specified then a source -/// will be chosen arbitrarly and repeated until all components of the +/// will be chosen arbitrarily and repeated until all components of the /// graph are searched. /// :param weight_fn: An optional weight function for an edge. It will accept /// a single argument, the edge's weight object and will return a float which diff --git a/tests/digraph/test_bellman_ford.py b/tests/digraph/test_bellman_ford.py index a502a69566..ee5b5731ef 100644 --- a/tests/digraph/test_bellman_ford.py +++ b/tests/digraph/test_bellman_ford.py @@ -48,11 +48,11 @@ def test_bellman_ford_length_with_no_path(self): g = rustworkx.PyDiGraph() a = g.add_node("A") g.add_node("B") - path_lenghts = rustworkx.digraph_bellman_ford_shortest_path_lengths( + path_lengths = rustworkx.digraph_bellman_ford_shortest_path_lengths( g, a, edge_cost_fn=float ) expected = {} - self.assertEqual(expected, path_lenghts) + self.assertEqual(expected, path_lengths) def test_bellman_ford_path(self): paths = rustworkx.digraph_bellman_ford_shortest_paths(self.graph, self.a) @@ -136,13 +136,13 @@ def test_bellman_ford_length_with_no_path_and_goal(self): g = rustworkx.PyDiGraph() a = g.add_node("A") b = g.add_node("B") - path_lenghts = rustworkx.digraph_bellman_ford_shortest_path_lengths( + path_lengths = rustworkx.digraph_bellman_ford_shortest_path_lengths( g, a, edge_cost_fn=float, goal=b ) expected = rustworkx.digraph_dijkstra_shortest_path_lengths( g, a, edge_cost_fn=float, goal=b ) - self.assertEqual(expected, path_lenghts) + self.assertEqual(expected, path_lengths) def test_bellman_ford_with_no_path(self): g = rustworkx.PyDiGraph() @@ -212,7 +212,7 @@ def test_raises_negative_cycle_bellman_ford_paths(self): with self.assertRaises(rustworkx.NegativeCycle): rustworkx.bellman_ford_shortest_paths(graph, 0, weight_fn=float) - def test_raises_negative_cycle_bellman_ford_path_lenghts(self): + def test_raises_negative_cycle_bellman_ford_path_lengths(self): graph = rustworkx.PyDiGraph() graph.add_nodes_from(list(range(4))) graph.add_edges_from( @@ -428,7 +428,7 @@ def test_raises_negative_cycle_all_pairs_bellman_ford_paths(self): with self.assertRaises(rustworkx.NegativeCycle): rustworkx.all_pairs_bellman_ford_shortest_paths(graph, float) - def test_raises_negative_cycle_all_pairs_bellman_ford_path_lenghts(self): + def test_raises_negative_cycle_all_pairs_bellman_ford_path_lengths(self): graph = rustworkx.PyDiGraph() graph.add_nodes_from(list(range(4))) graph.add_edges_from( @@ -449,7 +449,7 @@ def test_raises_index_error_bellman_ford_paths(self): self.graph, len(self.graph.node_indices()) + 1, weight_fn=lambda x: float(x) ) - def test_raises_index_error_bellman_ford_path_lenghts(self): + def test_raises_index_error_bellman_ford_path_lengths(self): with self.assertRaises(IndexError): rustworkx.digraph_bellman_ford_shortest_path_lengths( self.graph, len(self.graph.node_indices()) + 1, edge_cost_fn=lambda x: float(x) diff --git a/tests/digraph/test_bisimulation.py b/tests/digraph/test_bisimulation.py index ddf4fb0129..a55f281999 100644 --- a/tests/digraph/test_bisimulation.py +++ b/tests/digraph/test_bisimulation.py @@ -34,7 +34,7 @@ def test_empty_graph(self): res = rustworkx.digraph_maximum_bisimulation(graph) self.assertEqual(res, []) - def test_multigraph_compatability(self): + def test_multigraph_compatibility(self): graph = rustworkx.PyDiGraph() graph.add_nodes_from(range(5)) graph.add_edges_from_no_data([(0, 1), (1, 4), (1, 4), (1, 4), (1, 4), (2, 3), (3, 0)]) diff --git a/tests/digraph/test_dijkstra.py b/tests/digraph/test_dijkstra.py index 614503cbeb..2a4320671e 100644 --- a/tests/digraph/test_dijkstra.py +++ b/tests/digraph/test_dijkstra.py @@ -48,11 +48,11 @@ def test_dijkstra_length_with_no_path(self): g = rustworkx.PyDiGraph() a = g.add_node("A") b = g.add_node("B") - path_lenghts = rustworkx.digraph_dijkstra_shortest_path_lengths( + path_lengths = rustworkx.digraph_dijkstra_shortest_path_lengths( g, a, edge_cost_fn=float, goal=b ) expected = {} - self.assertEqual(expected, path_lenghts) + self.assertEqual(expected, path_lengths) def test_dijkstra_path(self): paths = rustworkx.digraph_dijkstra_shortest_paths(self.graph, self.a) @@ -318,7 +318,7 @@ def all_pairs_dijkstra_with_invalid_weights(self): graph, edge_cost_fn=lambda _: invalid_weight ) - def all_pairs_dijkstra_lenghts_with_invalid_weights(self): + def all_pairs_dijkstra_lengths_with_invalid_weights(self): graph = rustworkx.generators.directed_path_graph(2) for invalid_weight in [float("nan"), -1]: with self.subTest(invalid_weight=invalid_weight): diff --git a/tests/digraph/test_hits.py b/tests/digraph/test_hits.py index 6fb0ec8b6f..a8d5bc9a29 100644 --- a/tests/digraph/test_hits.py +++ b/tests/digraph/test_hits.py @@ -46,7 +46,7 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # -# These tests are adapated from the networkx test cases: +# These tests are adapted from the networkx test cases: # https://github.com/networkx/networkx/blob/cea310f9066efc0d5ff76f63d33dbc3eefe61f6b/networkx/algorithms/link_analysis/tests/test_pagerank.py import unittest diff --git a/tests/digraph/test_k_shortest_path.py b/tests/digraph/test_k_shortest_path.py index cbc40695e1..f3f19ddc52 100644 --- a/tests/digraph/test_k_shortest_path.py +++ b/tests/digraph/test_k_shortest_path.py @@ -89,8 +89,8 @@ def test_k_shortest_path_with_no_path(self): g = rustworkx.PyDiGraph() a = g.add_node("A") b = g.add_node("B") - path_lenghts = rustworkx.digraph_k_shortest_path_lengths( + path_lengths = rustworkx.digraph_k_shortest_path_lengths( g, start=a, k=1, edge_cost=float, goal=b ) expected = {} - self.assertEqual(expected, path_lenghts) + self.assertEqual(expected, path_lengths) diff --git a/tests/digraph/test_nodes.py b/tests/digraph/test_nodes.py index 773713b227..73876710be 100644 --- a/tests/digraph/test_nodes.py +++ b/tests/digraph/test_nodes.py @@ -211,7 +211,7 @@ def test_remove_nodes_retain_edges_by_id_parallel(self): for weight in weights: dag.add_edge(nodes[0], nodes[1], weight) dag.add_edge(nodes[1], nodes[2], weight) - # The middle node has three precessor edges and three successor edges, where each set has + # The middle node has three predecessor edges and three successor edges, where each set has # one edge each of three weights. Edges should be paired up in bijection during the removal. dag.remove_node_retain_edges_by_id(nodes[1]) self.assertEqual(set(dag.node_indices()), {nodes[0], nodes[2]}) diff --git a/tests/digraph/test_pagerank.py b/tests/digraph/test_pagerank.py index 3682611b9e..eea2c91afc 100644 --- a/tests/digraph/test_pagerank.py +++ b/tests/digraph/test_pagerank.py @@ -46,7 +46,7 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # -# These tests are adapated from the networkx test cases: +# These tests are adapted from the networkx test cases: # https://github.com/networkx/networkx/blob/cea310f9066efc0d5ff76f63d33dbc3eefe61f6b/networkx/algorithms/link_analysis/tests/test_pagerank.py import unittest diff --git a/tests/digraph/test_pred_succ.py b/tests/digraph/test_pred_succ.py index 10d0a62d35..4ebf5924e4 100644 --- a/tests/digraph/test_pred_succ.py +++ b/tests/digraph/test_pred_succ.py @@ -274,7 +274,7 @@ def test_many_children(self): res, ) - def test_bfs_succesors(self): + def test_bfs_successors(self): dag = rustworkx.PyDAG() node_a = dag.add_node(0) node_b = dag.add_child(node_a, 1, {}) diff --git a/tests/graph/test_bellman_ford.py b/tests/graph/test_bellman_ford.py index d0dd3f1068..1eb54d69a3 100644 --- a/tests/graph/test_bellman_ford.py +++ b/tests/graph/test_bellman_ford.py @@ -81,19 +81,19 @@ def test_bellman_ford_length_with_no_path_and_goal(self): g = rustworkx.PyGraph() a = g.add_node("A") b = g.add_node("B") - path_lenghts = rustworkx.graph_bellman_ford_shortest_path_lengths( + path_lengths = rustworkx.graph_bellman_ford_shortest_path_lengths( g, a, edge_cost_fn=float, goal=b ) expected = rustworkx.graph_dijkstra_shortest_path_lengths(g, a, edge_cost_fn=float, goal=b) - self.assertEqual(expected, path_lenghts) + self.assertEqual(expected, path_lengths) def test_bellman_ford_length_with_no_path(self): g = rustworkx.PyGraph() a = g.add_node("A") g.add_node("B") - path_lenghts = rustworkx.graph_bellman_ford_shortest_path_lengths(g, a, edge_cost_fn=float) + path_lengths = rustworkx.graph_bellman_ford_shortest_path_lengths(g, a, edge_cost_fn=float) expected = {} - self.assertEqual(expected, path_lenghts) + self.assertEqual(expected, path_lengths) def test_bellman_ford_path_with_no_goal_set(self): path = rustworkx.graph_bellman_ford_shortest_paths(self.graph, self.a) @@ -175,7 +175,7 @@ def test_raises_negative_cycle_bellman_ford_paths(self): with self.assertRaises(rustworkx.NegativeCycle): rustworkx.bellman_ford_shortest_paths(graph, 0, weight_fn=float) - def test_raises_negative_cycle_bellman_ford_path_lenghts(self): + def test_raises_negative_cycle_bellman_ford_path_lengths(self): graph = rustworkx.PyGraph() graph.add_nodes_from(list(range(4))) graph.add_edges_from( @@ -292,7 +292,7 @@ def test_raises_negative_cycle_all_pairs_bellman_ford_paths(self): with self.assertRaises(rustworkx.NegativeCycle): rustworkx.all_pairs_bellman_ford_shortest_paths(graph, float) - def test_raises_negative_cycle_all_pairs_bellman_ford_path_lenghts(self): + def test_raises_negative_cycle_all_pairs_bellman_ford_path_lengths(self): graph = rustworkx.PyGraph() graph.add_nodes_from(list(range(4))) graph.add_edges_from( @@ -313,7 +313,7 @@ def test_raises_index_error_bellman_ford_paths(self): self.graph, len(self.graph.node_indices()) + 1, weight_fn=lambda x: float(x) ) - def test_raises_index_error_bellman_ford_path_lenghts(self): + def test_raises_index_error_bellman_ford_path_lengths(self): with self.assertRaises(IndexError): rustworkx.graph_bellman_ford_shortest_path_lengths( self.graph, len(self.graph.node_indices()) + 1, edge_cost_fn=lambda x: float(x) diff --git a/tests/graph/test_dijkstra.py b/tests/graph/test_dijkstra.py index 7455027616..d6f7121ec5 100644 --- a/tests/graph/test_dijkstra.py +++ b/tests/graph/test_dijkstra.py @@ -74,11 +74,11 @@ def test_dijkstra_length_with_no_path(self): g = rustworkx.PyGraph() a = g.add_node("A") b = g.add_node("B") - path_lenghts = rustworkx.graph_dijkstra_shortest_path_lengths( + path_lengths = rustworkx.graph_dijkstra_shortest_path_lengths( g, a, edge_cost_fn=float, goal=b ) expected = {} - self.assertEqual(expected, path_lenghts) + self.assertEqual(expected, path_lengths) def test_dijkstra_path_with_no_goal_set(self): path = rustworkx.graph_dijkstra_shortest_paths(self.graph, self.a) @@ -253,7 +253,7 @@ def all_pairs_dijkstra_with_invalid_weights(self): graph, edge_cost_fn=lambda _: invalid_weight ) - def all_pairs_dijkstra_lenghts_with_invalid_weights(self): + def all_pairs_dijkstra_lengths_with_invalid_weights(self): graph = rustworkx.generators.path_graph(2) for invalid_weight in [float("nan"), -1]: with self.subTest(invalid_weight=invalid_weight): diff --git a/tests/graph/test_k_shortest_path.py b/tests/graph/test_k_shortest_path.py index 6497de38ac..8ff1a1a4ad 100644 --- a/tests/graph/test_k_shortest_path.py +++ b/tests/graph/test_k_shortest_path.py @@ -68,8 +68,8 @@ def test_k_shortest_path_with_no_path(self): g = rustworkx.PyGraph() a = g.add_node("A") b = g.add_node("B") - path_lenghts = rustworkx.graph_k_shortest_path_lengths( + path_lengths = rustworkx.graph_k_shortest_path_lengths( g, start=a, k=1, edge_cost=float, goal=b ) expected = {} - self.assertEqual(expected, path_lenghts) + self.assertEqual(expected, path_lengths) diff --git a/tests/graph/test_max_weight_matching.py b/tests/graph/test_max_weight_matching.py index 06fbbde5eb..7a2bb83d49 100644 --- a/tests/graph/test_max_weight_matching.py +++ b/tests/graph/test_max_weight_matching.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -# These tests are adapated from the networkx test cases: +# These tests are adapted from the networkx test cases: # https://github.com/networkx/networkx/blob/3351206a3ce5b3a39bb2fc451e93ef545b96c95b/networkx/algorithms/tests/test_matching.py import random diff --git a/tests/test_custom_return_types.py b/tests/test_custom_return_types.py index 725cf73ed4..4795e45f41 100644 --- a/tests/test_custom_return_types.py +++ b/tests/test_custom_return_types.py @@ -1200,7 +1200,7 @@ def test_pickle(self): def test_str(self): res = rustworkx.all_pairs_dijkstra_path_lengths(self.dag, lambda _: 3.14) # Since all_pairs_dijkstra_path_lengths() is parallel the order of the - # output is non-determinisitic + # output is non-deterministic valid_values = [ "AllPairsPathLengthMapping{1: PathLengthMapping{}, " "0: PathLengthMapping{1: 3.14}}", "AllPairsPathLengthMapping{" diff --git a/tests/test_token_swapper.py b/tests/test_token_swapper.py index 2fa9b22de5..17232498c2 100644 --- a/tests/test_token_swapper.py +++ b/tests/test_token_swapper.py @@ -57,7 +57,7 @@ def test_small(self) -> None: self.assertEqual({i: i for i in range(8)}, permutation) def test_bug1(self) -> None: - """Tests for a bug that occured in happy swap chains of length >2.""" + """Tests for a bug that occurred in happy swap chains of length >2.""" graph = rx.PyGraph() graph.extend_from_edge_list( [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4), (3, 6)] diff --git a/tox.ini b/tox.ini index 712139c017..618baf3927 100644 --- a/tox.ini +++ b/tox.ini @@ -27,7 +27,7 @@ passenv = changedir = {toxinidir}/tests commands = stestr run {posargs} - python -c "print('\nrustworkx no longer supports tox. Please run the equivalent comand with nox:\n\n\tnox -e test\n')" + python -c "print('\nrustworkx no longer supports tox. Please run the equivalent command with nox:\n\n\tnox -e test\n')" [testenv:lint] basepython = python3 @@ -41,7 +41,7 @@ commands = ruff check ../rustworkx ../retworkx . ../setup.py cargo fmt --all -- --check python {toxinidir}/tools/find_stray_release_notes.py - python -c "print('\nrustworkx no longer supports tox. Please run the equivalent comand with nox:\n\n\tnox -e lint\n')" + python -c "print('\nrustworkx no longer supports tox. Please run the equivalent command with nox:\n\n\tnox -e lint\n')" [testenv:docs] @@ -61,7 +61,7 @@ commands = python -m ipykernel install --user jupyter kernelspec list sphinx-build -W -d {toxinidir}/docs/build/.doctrees -b html source build/html {posargs} - python -c "print('\nrustworkx no longer supports tox. Please run the equivalent comand with nox:\n\n\tnox -e docs\n')" + python -c "print('\nrustworkx no longer supports tox. Please run the equivalent command with nox:\n\n\tnox -e docs\n')" [testenv:docs-clean] skip_install = true @@ -69,7 +69,7 @@ deps = allowlist_externals = rm commands = rm -rf {toxinidir}/docs/build {toxinidir}/docs/source/apiref - python -c "print('\nrustworkx no longer supports tox. Please run the equivalent comand with nox:\n\n\tnox -e docs_clean\n')" + python -c "print('\nrustworkx no longer supports tox. Please run the equivalent command with nox:\n\n\tnox -e docs_clean\n')" [testenv:black] basepython = python3 @@ -78,7 +78,7 @@ deps = black~=24.8 commands = black {posargs} '../rustworkx' '../tests' '../retworkx' - python -c "print('\nrustworkx no longer supports tox. Please run the equivalent comand with nox:\n\tnox -e black\n')" + python -c "print('\nrustworkx no longer supports tox. Please run the equivalent command with nox:\n\tnox -e black\n')" [testenv:stubs] basepython = python3 @@ -90,4 +90,4 @@ extras = graphviz commands = python -m mypy.stubtest --concise rustworkx - python -c "print('\nrustworkx no longer supports tox. Please run the equivalent comand with nox:\n\n\tnox -e stubs\n')" + python -c "print('\nrustworkx no longer supports tox. Please run the equivalent command with nox:\n\n\tnox -e stubs\n')" From 73288c3629306dd9cf8d5d86fc8145f52ac19b81 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 10:32:09 -0500 Subject: [PATCH 33/38] Bump indexmap from 2.6.0 to 2.7.0 (#1334) Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.6.0 to 2.7.0. - [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md) - [Commits](https://github.com/indexmap-rs/indexmap/compare/2.6.0...2.7.0) --- updated-dependencies: - dependency-name: indexmap dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 457b25b0be..5cfc8a3777 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,9 +149,9 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown 0.15.0", From 5dbc246de95cc46584de3af7a33ce28af0ac83b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 12:00:45 -0500 Subject: [PATCH 34/38] Bump serde from 1.0.215 to 1.0.216 (#1338) Bumps [serde](https://github.com/serde-rs/serde) from 1.0.215 to 1.0.216. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.215...v1.0.216) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5cfc8a3777..1d30e42502 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -611,18 +611,18 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", From 25077aa8517361d6873b68fe5ffb144177625339 Mon Sep 17 00:00:00 2001 From: Lars Esser <80355619+larsesser@users.noreply.github.com> Date: Sat, 14 Dec 2024 18:19:49 +0100 Subject: [PATCH 35/38] graphviz.py: add missing comma in IMAGE_TYPES (#1339) this prevented the usage of "vmlz" and "vrml" image type --- rustworkx/visualization/graphviz.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rustworkx/visualization/graphviz.py b/rustworkx/visualization/graphviz.py index 6d614e47d9..1fae8d99f3 100644 --- a/rustworkx/visualization/graphviz.py +++ b/rustworkx/visualization/graphviz.py @@ -64,7 +64,8 @@ "svg", "svgz", "vml", - "vmlz" "vrml", + "vmlz", + "vrml", "vtx", "wbmp", "xdor", From d21fe54b97d6c293b289d55dc9212f9ee95ad94c Mon Sep 17 00:00:00 2001 From: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:53:43 -0500 Subject: [PATCH 36/38] Add missing release notes for #1292 and #1306 (#1336) * Add release notes for #1292 * Add release notes for degree centrality * Add centrality entries to the docs * Update releasenotes/notes/accept-generators-31f080871015233c.yaml Co-authored-by: Matthew Treinish * Update releasenotes/notes/accept-generators-31f080871015233c.yaml --------- Co-authored-by: Matthew Treinish --- .../api/algorithm_functions/centrality.rst | 3 ++ .../accept-generators-31f080871015233c.yaml | 11 +++++++ .../degree-centrality-e7ddf61a9a8fbafc.yaml | 33 +++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 releasenotes/notes/accept-generators-31f080871015233c.yaml create mode 100644 releasenotes/notes/degree-centrality-e7ddf61a9a8fbafc.yaml diff --git a/docs/source/api/algorithm_functions/centrality.rst b/docs/source/api/algorithm_functions/centrality.rst index 78b54514e9..dfbaaa598e 100644 --- a/docs/source/api/algorithm_functions/centrality.rst +++ b/docs/source/api/algorithm_functions/centrality.rst @@ -7,7 +7,10 @@ Centrality :toctree: ../../apiref rustworkx.betweenness_centrality + rustworkx.degree_centrality rustworkx.edge_betweenness_centrality rustworkx.eigenvector_centrality rustworkx.katz_centrality rustworkx.closeness_centrality + rustworkx.in_degree_centrality + rustworkx.out_degree_centrality \ No newline at end of file diff --git a/releasenotes/notes/accept-generators-31f080871015233c.yaml b/releasenotes/notes/accept-generators-31f080871015233c.yaml new file mode 100644 index 0000000000..de35fb3ca8 --- /dev/null +++ b/releasenotes/notes/accept-generators-31f080871015233c.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + The following methods now support sequences and generators as inputs, in addition to + the existing support for lists: + * :meth:`~rustworkx.PyGraph.add_nodes_from` and :meth:`~rustworkx.PyDiGraph.add_nodes_from` + * :meth:`~rustworkx.PyGraph.add_edges_from` and :meth:`~rustworkx.PyDiGraph.add_edges_from` + * :meth:`~rustworkx.PyGraph.add_edges_from_no_data` and :meth:`~rustworkx.PyDiGraph.add_edges_from_no_data` + * :meth:`~rustworkx.PyGraph.extend_from_edge_list` and :meth:`~rustworkx.PyDiGraph.extend_from_edge_list` + * :meth:`~rustworkx.PyGraph.extend_from_weighted_edge_list` and :meth:`~rustworkx.PyDiGraph.extend_from_weighted_edge_list` + diff --git a/releasenotes/notes/degree-centrality-e7ddf61a9a8fbafc.yaml b/releasenotes/notes/degree-centrality-e7ddf61a9a8fbafc.yaml new file mode 100644 index 0000000000..391c664fb0 --- /dev/null +++ b/releasenotes/notes/degree-centrality-e7ddf61a9a8fbafc.yaml @@ -0,0 +1,33 @@ +--- +features: + - | + Added a new function, :func:`~rustworkx.degree_centrality` which is used to + compute the degree centrality for all nodes in a given graph. For + example: + + .. jupyter-execute:: + + import rustworkx as rx + from rustworkx.visualization import mpl_draw + + graph = rx.generators.hexagonal_lattice_graph(4, 4) + centrality = rx.degree_centrality(graph) + + # Generate a color list + colors = [] + for node in graph.node_indices(): + centrality_score = centrality[node] + graph[node] = centrality_score + colors.append(centrality_score) + mpl_draw( + graph, + with_labels=True, + node_color=colors, + node_size=650, + labels=lambda x: "{0:.2f}".format(x) + ) + + - | + Added two new functions, :func:`~rustworkx.in_degree_centrality` and + :func:`~rustworkx.out_degree_centrality` to calculate two special + types of degree centrality for directed graphs. \ No newline at end of file From 6bc3933121d04e4d4ae3484625cebebfe9de7fd5 Mon Sep 17 00:00:00 2001 From: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:54:09 -0500 Subject: [PATCH 37/38] Fix find_node_by_weight type annotations (#1324) --- .../fix-find-node-by-weight-stub-94e971291e1e6c96.yaml | 7 +++++++ rustworkx/rustworkx.pyi | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/fix-find-node-by-weight-stub-94e971291e1e6c96.yaml diff --git a/releasenotes/notes/fix-find-node-by-weight-stub-94e971291e1e6c96.yaml b/releasenotes/notes/fix-find-node-by-weight-stub-94e971291e1e6c96.yaml new file mode 100644 index 0000000000..cc99652c38 --- /dev/null +++ b/releasenotes/notes/fix-find-node-by-weight-stub-94e971291e1e6c96.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixed a bug in the type hints for :meth:`~rustworkx.PyGraph.find_node_by_weight` + and :meth:`~rustworkx.PyDiGraph.find_node_by_weight`. + Refer to `issue 1243 `__ for + more information. diff --git a/rustworkx/rustworkx.pyi b/rustworkx/rustworkx.pyi index 4a18edd614..5d42197064 100644 --- a/rustworkx/rustworkx.pyi +++ b/rustworkx/rustworkx.pyi @@ -1224,7 +1224,7 @@ class PyGraph(Generic[_S, _T]): def filter_nodes(self, filter_function: Callable[[_S], bool]) -> NodeIndices: ... def find_node_by_weight( self, - obj: Callable[[_S], bool], + obj: _S, /, ) -> int | None: ... @staticmethod @@ -1378,7 +1378,7 @@ class PyDiGraph(Generic[_S, _T]): def find_adjacent_node_by_edge(self, node: int, predicate: Callable[[_T], bool], /) -> _S: ... def find_node_by_weight( self, - obj: Callable[[_S], bool], + obj: _S, /, ) -> int | None: ... def find_predecessors_by_edge( From 14be08f95d57afe44731d7c4cc322751f48db5ee Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 16 Dec 2024 22:30:15 -0500 Subject: [PATCH 38/38] Migrate Johnson's algorithm to rustworkx-core (#1318) * Migrate Johnson's algorithm to rustworkx-core This commit adds Johnson's algorithm for computing simple cycles to rustworkx-core. This implementation returns an iterator-like struct which requires a reference to the graph for each step of the iterator. This mirrors the structure of the Bfs and similar structs in petgraph. While an implementor of Iterator could be used for this, this complicates using it from Python because we can't have a shared reference in a pyclass struct. This also has the advantage of enabling mutable references while iterating over the cycles. To facilitate this 2 traits are added `EdgeFindable` and `EdgeRemovable` which add traits for `remove_edge()` and `find_edge()` to petgraph graph types. The `EdgeRemovable` trait isn't actually used currently, it was in an earlier local draft of the PR which attempted to remove self cycles from a clone before changing direction of the interface. Since the implementation was done and relatively simple, and the potential general usefulness this was left in. `EdgeFindable` is actively used by this new function however. * Simplify python side logic * Set name of pyclass to SimpleCycleIter for backwards compat * Fix rustdoc warnings --------- Co-authored-by: Ivan Carvalho <8753214+IvanIsCoding@users.noreply.github.com> Co-authored-by: Ivan Carvalho --- ...on-simple-cycle-core-ac0c09d6fce07f8a.yaml | 12 + .../src/connectivity/johnson_simple_cycles.rs | 532 ++++++++++++++++++ rustworkx-core/src/connectivity/mod.rs | 2 + rustworkx-core/src/graph_ext/mod.rs | 55 ++ src/connectivity/johnson_simple_cycles.rs | 312 ++-------- src/connectivity/mod.rs | 7 +- 6 files changed, 646 insertions(+), 274 deletions(-) create mode 100644 releasenotes/notes/add-johnson-simple-cycle-core-ac0c09d6fce07f8a.yaml create mode 100644 rustworkx-core/src/connectivity/johnson_simple_cycles.rs diff --git a/releasenotes/notes/add-johnson-simple-cycle-core-ac0c09d6fce07f8a.yaml b/releasenotes/notes/add-johnson-simple-cycle-core-ac0c09d6fce07f8a.yaml new file mode 100644 index 0000000000..3b978f4957 --- /dev/null +++ b/releasenotes/notes/add-johnson-simple-cycle-core-ac0c09d6fce07f8a.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + Added a new function, ``johnson_simple_cycles``, to the ``rustworkx-core`` + crate. This function implements `Johnson's algorithm `__ for finding all + elementary cycles in a directed graph. + - | + Added a new trait ``EdgeFindable`` to find an ``EdgeIndex`` from a graph + given a pair of node indices. + - | + Added a new trait ``EdgeRemovable`` to remove an edge from a graph by + its ``EdgeIndex``. diff --git a/rustworkx-core/src/connectivity/johnson_simple_cycles.rs b/rustworkx-core/src/connectivity/johnson_simple_cycles.rs new file mode 100644 index 0000000000..556c12a697 --- /dev/null +++ b/rustworkx-core/src/connectivity/johnson_simple_cycles.rs @@ -0,0 +1,532 @@ +// Licensed under the apache license, version 2.0 (the "license"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +use ahash::RandomState; +use hashbrown::{HashMap, HashSet}; +use indexmap::IndexSet; +use std::hash::Hash; + +use petgraph::algo::kosaraju_scc; +use petgraph::stable_graph::{NodeIndex, StableDiGraph}; +use petgraph::visit::{ + EdgeCount, EdgeRef, GraphBase, GraphProp, IntoEdgeReferences, IntoNeighbors, + IntoNeighborsDirected, IntoNodeReferences, NodeCount, NodeFiltered, NodeIndexable, NodeRef, + Visitable, +}; +use petgraph::Directed; + +use crate::graph_ext::EdgeFindable; + +fn build_subgraph( + graph: G, + nodes: &[NodeIndex], +) -> (StableDiGraph<(), ()>, HashMap) +where + G: EdgeCount + NodeCount + IntoEdgeReferences + IntoNodeReferences + GraphBase + NodeIndexable, + ::NodeId: Hash + Eq, +{ + let node_set: HashSet = nodes.iter().copied().collect(); + let mut node_map: HashMap = HashMap::with_capacity(nodes.len()); + let node_filter = + |node: G::NodeId| -> bool { node_set.contains(&NodeIndex::new(graph.to_index(node))) }; + // Overallocates edges, but not a big deal as this is temporary for the lifetime of the + // subgraph + let mut out_graph = StableDiGraph::<(), ()>::with_capacity(nodes.len(), graph.edge_count()); + let filtered = NodeFiltered::from_fn(graph, node_filter); + for node in filtered.node_references() { + let new_node = out_graph.add_node(()); + node_map.insert(NodeIndex::new(graph.to_index(node.id())), new_node); + } + for edge in filtered.edge_references() { + let new_source = *node_map + .get(&NodeIndex::new(graph.to_index(edge.source()))) + .unwrap(); + let new_target = *node_map + .get(&NodeIndex::new(graph.to_index(edge.target()))) + .unwrap(); + out_graph.add_edge( + NodeIndex::new(new_source.index()), + NodeIndex::new(new_target.index()), + (), + ); + } + (out_graph, node_map) +} + +fn unblock( + node: NodeIndex, + blocked: &mut HashSet, + block: &mut HashMap>, +) { + let mut stack: IndexSet = IndexSet::with_hasher(RandomState::default()); + stack.insert(node); + while let Some(stack_node) = stack.pop() { + if blocked.remove(&stack_node) { + match block.get_mut(&stack_node) { + // stack.update(block[stack_node]): + Some(block_set) => { + block_set.drain().for_each(|n| { + stack.insert(n); + }); + } + // If block doesn't have stack_node treat it as an empty set + // (so no updates to stack) and populate it with an empty + // set. + None => { + block.insert(stack_node, HashSet::new()); + } + } + blocked.remove(&stack_node); + } + } +} + +#[allow(clippy::too_many_arguments)] +fn process_stack( + start_node: NodeIndex, + stack: &mut Vec<(NodeIndex, IndexSet)>, + path: &mut Vec, + closed: &mut HashSet, + blocked: &mut HashSet, + block: &mut HashMap>, + subgraph: &StableDiGraph<(), ()>, + reverse_node_map: &HashMap, +) -> Option> { + while let Some((this_node, neighbors)) = stack.last_mut() { + if let Some(next_node) = neighbors.pop() { + if next_node == start_node { + // Out path in input graph basis + let mut out_path: Vec = Vec::with_capacity(path.len()); + for n in path { + out_path.push(reverse_node_map[n]); + closed.insert(*n); + } + return Some(out_path); + } else if blocked.insert(next_node) { + path.push(next_node); + stack.push(( + next_node, + subgraph + .neighbors(next_node) + .collect::>(), + )); + closed.remove(&next_node); + blocked.insert(next_node); + continue; + } + } + if neighbors.is_empty() { + if closed.contains(this_node) { + unblock(*this_node, blocked, block); + } else { + for neighbor in subgraph.neighbors(*this_node) { + let block_neighbor = block.entry(neighbor).or_insert_with(HashSet::new); + block_neighbor.insert(*this_node); + } + } + stack.pop(); + path.pop(); + } + } + None +} + +/// An iterator of simple cycles in a graph +/// +/// `SimpleCycleIter` does not itself borrow the graph, and because of this +/// you can run the algorithm while retaining mutable access to it, if you +/// use like it the following example: +/// +/// ``` +/// use rustworkx_core::petgraph::prelude::*; +/// use rustworkx_core::connectivity::johnson_simple_cycles; +/// +/// let mut graph = DiGraph::<_,()>::new(); +/// let a = graph.add_node(0); +/// +/// let mut cycle_iter = johnson_simple_cycles(&graph, None); +/// while let Some(cycle) = cycle_iter.next(&graph) { +/// // We can access `graph` mutably here still +/// graph[a] += 1; +/// } +/// +/// assert_eq!(graph[a], 0); +/// ``` +pub struct SimpleCycleIter { + scc: Vec>, + self_cycles: Option>, + path: Vec, + blocked: HashSet, + closed: HashSet, + block: HashMap>, + stack: Vec<(NodeIndex, IndexSet)>, + start_node: NodeIndex, + node_map: HashMap, + reverse_node_map: HashMap, + subgraph: StableDiGraph<(), ()>, +} + +impl SimpleCycleIter { + /// Return the next cycle found, if `None` is returned the algorithm is complete and all + /// cycles have been found. + pub fn next(&mut self, graph: G) -> Option> + where + G: IntoEdgeReferences + + IntoNodeReferences + + GraphBase + + EdgeCount + + NodeCount + + NodeIndexable, + ::NodeId: Hash + Eq, + { + if self.self_cycles.is_some() { + let self_cycles = self.self_cycles.as_mut().unwrap(); + let cycle_node = self_cycles.pop().unwrap(); + if self_cycles.is_empty() { + self.self_cycles = None; + } + return Some(vec![cycle_node]); + } + // Restore previous state if it exists + let mut stack: Vec<(NodeIndex, IndexSet)> = + std::mem::take(&mut self.stack); + let mut path: Vec = std::mem::take(&mut self.path); + let mut closed: HashSet = std::mem::take(&mut self.closed); + let mut blocked: HashSet = std::mem::take(&mut self.blocked); + let mut block: HashMap> = std::mem::take(&mut self.block); + let mut subgraph: StableDiGraph<(), ()> = std::mem::take(&mut self.subgraph); + let mut reverse_node_map: HashMap = + std::mem::take(&mut self.reverse_node_map); + let mut node_map: HashMap = std::mem::take(&mut self.node_map); + if let Some(res) = process_stack( + self.start_node, + &mut stack, + &mut path, + &mut closed, + &mut blocked, + &mut block, + &subgraph, + &reverse_node_map, + ) { + // Store internal state on yield + self.stack = stack; + self.path = path; + self.closed = closed; + self.blocked = blocked; + self.block = block; + self.subgraph = subgraph; + self.reverse_node_map = reverse_node_map; + self.node_map = node_map; + return Some(res); + } else { + subgraph.remove_node(self.start_node); + self.scc + .extend(kosaraju_scc(&subgraph).into_iter().filter_map(|scc| { + if scc.len() > 1 { + let res = scc + .iter() + .map(|n| reverse_node_map[n]) + .collect::>(); + Some(res) + } else { + None + } + })); + } + while let Some(mut scc) = self.scc.pop() { + let temp = build_subgraph(graph, &scc); + subgraph = temp.0; + node_map = temp.1; + reverse_node_map = node_map.iter().map(|(k, v)| (*v, *k)).collect(); + // start_node, path, blocked, closed, block and stack all in subgraph basis + self.start_node = node_map[&scc.pop().unwrap()]; + path = vec![self.start_node]; + blocked = path.iter().copied().collect(); + // Nodes in cycle all + closed = HashSet::new(); + block = HashMap::new(); + stack = vec![( + self.start_node, + subgraph + .neighbors(self.start_node) + .collect::>(), + )]; + if let Some(res) = process_stack( + self.start_node, + &mut stack, + &mut path, + &mut closed, + &mut blocked, + &mut block, + &subgraph, + &reverse_node_map, + ) { + // Store internal state on yield + self.stack = stack; + self.path = path; + self.closed = closed; + self.blocked = blocked; + self.block = block; + self.subgraph = subgraph; + self.reverse_node_map = reverse_node_map; + self.node_map = node_map; + return Some(res); + } + subgraph.remove_node(self.start_node); + self.scc + .extend(kosaraju_scc(&subgraph).into_iter().filter_map(|scc| { + if scc.len() > 1 { + let res = scc + .iter() + .map(|n| reverse_node_map[n]) + .collect::>(); + Some(res) + } else { + None + } + })); + } + None + } +} + +/// /// Find all simple cycles of a graph +/// +/// A "simple cycle" (called an elementary circuit in [^Johnson75]) is a cycle (or closed path) +/// where no node appears more than once. +/// +/// This function is a an implementation of Johnson's algorithm [^Johnson75] also based +/// on the non-recursive implementation found in NetworkX[^NetworkDevs24] with code available on Github[^GitHub24]. +/// +/// To handle self cycles in a manner consistent with the NetworkX implementation you should +/// use the ``self_cycles`` argument to collect manually collected self cycle and then remove +/// the edges leading to a self cycle from the graph. If you don't do this +/// the self cycle may or may not be returned by the iterator. The underlying algorithm is not +/// able to consistently detect self cycles so it is best to handle them before calling this +/// function. The example below shows a pattern for doing this. You will need to clone the graph +/// to do this detection without modifying the graph. +/// +/// # Returns +/// +/// This function returns a `SimpleCycleIter` iterator which returns a `Vec` of `NodeIndex`. +/// Note the `NodeIndex` type is not neccesarily the same as the input graph, as it's built +/// using an internal `StableGraph` used by the algorithm. If your input `graph` uses a +/// different node index type that differs from the default `NodeIndex`/`NodeIndex` +/// you will want to convert these objects to your native `NodeIndex` type. +/// +/// The return from this function is not guaranteed to have a particular order for either the +/// cycles or the indices in each cycle. +/// +/// [^Johnson75]: +/// [^NetworkDevs24]: +/// [^GitHub24]: +/// +/// # Example: +/// +/// ```rust +/// use rustworkx_core::petgraph::prelude::*; +/// use rustworkx_core::connectivity::johnson_simple_cycles; +/// +/// let mut graph = DiGraph::<(), ()>::new(); +/// graph.extend_with_edges([(0, 0), (0, 1), (0, 2), (1, 2), (2, 0), (2, 1), (2, 2)]); +/// +/// // Handle self cycles +/// let self_cycles_vec: Vec = graph +/// .node_indices() +/// .filter(|n| graph.neighbors(*n).any(|x| x == *n)) +/// .collect(); +/// for node in &self_cycles_vec { +/// while let Some(edge_index) = graph.find_edge(*node, *node) { +/// graph.remove_edge(edge_index); +/// } +/// } +/// let self_cycles = if self_cycles_vec.is_empty() { +/// None +/// } else { +/// Some(self_cycles_vec) +/// }; +/// +/// let mut cycles_iter = johnson_simple_cycles(&graph, self_cycles); +/// +/// let mut cycles = Vec::new(); +/// while let Some(mut cycle) = cycles_iter.next(&graph) { +/// cycle.sort(); +/// cycles.push(cycle); +/// } +/// +/// let expected = vec![ +/// vec![NodeIndex::new(0)], +/// vec![NodeIndex::new(2)], +/// vec![NodeIndex::new(0), NodeIndex::new(1), NodeIndex::new(2)], +/// vec![NodeIndex::new(0), NodeIndex::new(2)], +/// vec![NodeIndex::new(1), NodeIndex::new(2)], +/// ]; +/// +/// assert_eq!(expected.len(), cycles.len()); +/// for cycle in cycles { +/// assert!(expected.contains(&cycle)); +/// } +/// ``` +pub fn johnson_simple_cycles( + graph: G, + self_cycles: Option::NodeId>>, +) -> SimpleCycleIter +where + G: IntoEdgeReferences + + IntoNodeReferences + + GraphBase + + EdgeCount + + NodeCount + + NodeIndexable + + Clone + + IntoNeighbors + + IntoNeighborsDirected + + Visitable + + EdgeFindable + + GraphProp, + ::NodeId: Hash + Eq, +{ + let self_cycles = self_cycles.map(|self_cycles_vec| { + self_cycles_vec + .into_iter() + .map(|n| NodeIndex::new(graph.to_index(n))) + .collect() + }); + let strongly_connected_components: Vec> = kosaraju_scc(graph) + .into_iter() + .filter_map(|component| { + if component.len() > 1 { + Some( + component + .into_iter() + .map(|n| NodeIndex::new(graph.to_index(n))) + .collect(), + ) + } else { + None + } + }) + .collect(); + SimpleCycleIter { + scc: strongly_connected_components, + self_cycles, + path: Vec::new(), + blocked: HashSet::new(), + closed: HashSet::new(), + block: HashMap::new(), + stack: Vec::new(), + start_node: NodeIndex::new(u32::MAX as usize), + node_map: HashMap::new(), + reverse_node_map: HashMap::new(), + subgraph: StableDiGraph::new(), + } +} + +#[cfg(test)] +mod test_longest_path { + use super::*; + use petgraph::graph::DiGraph; + use petgraph::stable_graph::NodeIndex; + use petgraph::stable_graph::StableDiGraph; + + #[test] + fn test_empty_graph() { + let graph: DiGraph<(), ()> = DiGraph::new(); + let mut result: Vec<_> = Vec::new(); + let mut cycle_iter = johnson_simple_cycles(&graph, None); + while let Some(cycle) = cycle_iter.next(&graph) { + result.push(cycle); + } + let expected: Vec> = Vec::new(); + assert_eq!(expected, result) + } + + #[test] + fn test_empty_stable_graph() { + let graph: StableDiGraph<(), ()> = StableDiGraph::new(); + let mut result: Vec<_> = Vec::new(); + let mut cycle_iter = johnson_simple_cycles(&graph, None); + while let Some(cycle) = cycle_iter.next(&graph) { + result.push(cycle); + } + let expected: Vec> = Vec::new(); + assert_eq!(expected, result) + } + + #[test] + fn test_figure_1() { + for k in 3..10 { + let mut graph: DiGraph<(), ()> = DiGraph::new(); + let mut edge_list: Vec<[usize; 2]> = Vec::new(); + for n in 2..k + 2 { + edge_list.push([1, n]); + edge_list.push([n, k + 2]); + } + edge_list.push([2 * k + 1, 1]); + for n in k + 2..2 * k + 2 { + edge_list.push([n, 2 * k + 2]); + edge_list.push([n, n + 1]); + } + edge_list.push([2 * k + 3, k + 2]); + for n in 2 * k + 3..3 * k + 3 { + edge_list.push([2 * k + 2, n]); + edge_list.push([n, 3 * k + 3]); + } + edge_list.push([3 * k + 3, 2 * k + 2]); + graph.extend_with_edges( + edge_list + .into_iter() + .map(|x| (NodeIndex::new(x[0]), NodeIndex::new(x[1]))), + ); + let mut cycles_iter = johnson_simple_cycles(&graph, None); + let mut res = 0; + while let Some(_) = cycles_iter.next(&graph) { + res += 1; + } + assert_eq!(res, 3 * k); + } + } + + #[test] + fn test_figure_1_stable_graph() { + for k in 3..10 { + let mut graph: StableDiGraph<(), ()> = StableDiGraph::new(); + let mut edge_list: Vec<[usize; 2]> = Vec::new(); + for n in 2..k + 2 { + edge_list.push([1, n]); + edge_list.push([n, k + 2]); + } + edge_list.push([2 * k + 1, 1]); + for n in k + 2..2 * k + 2 { + edge_list.push([n, 2 * k + 2]); + edge_list.push([n, n + 1]); + } + edge_list.push([2 * k + 3, k + 2]); + for n in 2 * k + 3..3 * k + 3 { + edge_list.push([2 * k + 2, n]); + edge_list.push([n, 3 * k + 3]); + } + edge_list.push([3 * k + 3, 2 * k + 2]); + graph.extend_with_edges( + edge_list + .into_iter() + .map(|x| (NodeIndex::new(x[0]), NodeIndex::new(x[1]))), + ); + let mut cycles_iter = johnson_simple_cycles(&graph, None); + let mut res = 0; + while let Some(_) = cycles_iter.next(&graph) { + res += 1; + } + assert_eq!(res, 3 * k); + } + } +} diff --git a/rustworkx-core/src/connectivity/mod.rs b/rustworkx-core/src/connectivity/mod.rs index fa236d8b61..30d42d509d 100644 --- a/rustworkx-core/src/connectivity/mod.rs +++ b/rustworkx-core/src/connectivity/mod.rs @@ -20,6 +20,7 @@ mod core_number; mod cycle_basis; mod find_cycle; mod isolates; +mod johnson_simple_cycles; mod min_cut; pub use all_simple_paths::{ @@ -35,4 +36,5 @@ pub use core_number::core_number; pub use cycle_basis::cycle_basis; pub use find_cycle::find_cycle; pub use isolates::isolates; +pub use johnson_simple_cycles::{johnson_simple_cycles, SimpleCycleIter}; pub use min_cut::stoer_wagner_min_cut; diff --git a/rustworkx-core/src/graph_ext/mod.rs b/rustworkx-core/src/graph_ext/mod.rs index 256d6ac0a5..7ca5723ae5 100644 --- a/rustworkx-core/src/graph_ext/mod.rs +++ b/rustworkx-core/src/graph_ext/mod.rs @@ -61,6 +61,8 @@ //! | HasParallelEdgesDirected | x | x | x | x | x | x | //! | HasParallelEdgesUndirected | x | x | x | x | x | x | //! | NodeRemovable | x | x | x | x | | | +//! | EdgeRemovable | x | x | | | | | +//! | EdgeFindable | x | x | | | | | use petgraph::graph::IndexType; use petgraph::graphmap::{GraphMap, NodeTrait}; @@ -130,3 +132,56 @@ impl, Ix: IndexType> NodeRemovab None } } + +/// A graph whose edge may be removed by an edge id. +pub trait EdgeRemovable: Data { + type Output; + fn remove_edge(&mut self, edge: Self::EdgeId) -> Self::Output; +} + +impl EdgeRemovable for StableGraph +where + Ty: EdgeType, + Ix: IndexType, +{ + type Output = Option; + fn remove_edge(&mut self, edge: Self::EdgeId) -> Option { + self.remove_edge(edge) + } +} + +impl EdgeRemovable for Graph +where + Ty: EdgeType, + Ix: IndexType, +{ + type Output = Option; + fn remove_edge(&mut self, edge: Self::EdgeId) -> Option { + self.remove_edge(edge) + } +} + +/// A graph that can find edges by a pair of node ids. +pub trait EdgeFindable: Data { + fn edge_find(&self, a: Self::NodeId, b: Self::NodeId) -> Option; +} + +impl EdgeFindable for &StableGraph +where + Ty: EdgeType, + Ix: IndexType, +{ + fn edge_find(&self, a: Self::NodeId, b: Self::NodeId) -> Option { + self.find_edge(a, b) + } +} + +impl EdgeFindable for &Graph +where + Ty: EdgeType, + Ix: IndexType, +{ + fn edge_find(&self, a: Self::NodeId, b: Self::NodeId) -> Option { + self.find_edge(a, b) + } +} diff --git a/src/connectivity/johnson_simple_cycles.rs b/src/connectivity/johnson_simple_cycles.rs index 555df07151..8d0636f2f7 100644 --- a/src/connectivity/johnson_simple_cycles.rs +++ b/src/connectivity/johnson_simple_cycles.rs @@ -10,301 +10,69 @@ // License for the specific language governing permissions and limitations // under the License. -use ahash::RandomState; -use hashbrown::{HashMap, HashSet}; -use indexmap::IndexSet; - use crate::digraph::PyDiGraph; -use crate::StablePyGraph; -use petgraph::algo::kosaraju_scc; use petgraph::graph::NodeIndex; -use petgraph::stable_graph::StableDiGraph; -use petgraph::visit::EdgeRef; -use petgraph::visit::IntoEdgeReferences; -use petgraph::visit::IntoNodeReferences; -use petgraph::visit::NodeFiltered; -use petgraph::Directed; +use std::ops::Deref; use pyo3::prelude::*; use crate::iterators::NodeIndices; +use rustworkx_core::connectivity::{johnson_simple_cycles, SimpleCycleIter}; -fn build_subgraph( - graph: &StablePyGraph, - nodes: &[NodeIndex], -) -> (StableDiGraph<(), ()>, HashMap) { - let node_set: HashSet = nodes.iter().copied().collect(); - let mut node_map: HashMap = HashMap::with_capacity(nodes.len()); - let node_filter = |node: NodeIndex| -> bool { node_set.contains(&node) }; - // Overallocates edges, but not a big deal as this is temporary for the lifetime of the - // subgraph - let mut out_graph = StableDiGraph::<(), ()>::with_capacity(nodes.len(), graph.edge_count()); - let filtered = NodeFiltered(&graph, node_filter); - for node in filtered.node_references() { - let new_node = out_graph.add_node(()); - node_map.insert(node.0, new_node); - } - for edge in filtered.edge_references() { - let new_source = *node_map.get(&edge.source()).unwrap(); - let new_target = *node_map.get(&edge.target()).unwrap(); - out_graph.add_edge(new_source, new_target, ()); - } - (out_graph, node_map) -} - -#[pyclass(module = "rustworkx")] -pub struct SimpleCycleIter { - graph_clone: StablePyGraph, - scc: Vec>, - self_cycles: Option>, - path: Vec, - blocked: HashSet, - closed: HashSet, - block: HashMap>, - stack: Vec<(NodeIndex, IndexSet)>, - start_node: NodeIndex, - node_map: HashMap, - reverse_node_map: HashMap, - subgraph: StableDiGraph<(), ()>, +#[pyclass(module = "rustworkx", name = "SimpleCycleIter")] +pub struct PySimpleCycleIter { + graph_clone: Py, + iter: SimpleCycleIter, } -impl SimpleCycleIter { - pub fn new(graph: &PyDiGraph) -> Self { - // Copy graph to remove self edges before running johnson's algorithm - let mut graph_clone = graph.graph.clone(); - +impl PySimpleCycleIter { + pub fn new(py: Python, graph: Bound) -> PyResult { // For compatibility with networkx manually insert self cycles and filter // from Johnson's algorithm - let self_cycles_vec: Vec = graph_clone + let self_cycles_vec: Vec = graph + .borrow() + .graph .node_indices() - .filter(|n| graph_clone.neighbors(*n).any(|x| x == *n)) + .filter(|n| graph.borrow().graph.neighbors(*n).any(|x| x == *n)) .collect(); - for node in &self_cycles_vec { - while let Some(edge_index) = graph_clone.find_edge(*node, *node) { - graph_clone.remove_edge(edge_index); - } - } - let self_cycles = if self_cycles_vec.is_empty() { - None + if self_cycles_vec.is_empty() { + let iter = johnson_simple_cycles(&graph.borrow().graph, None); + let out_graph = graph.unbind(); + Ok(PySimpleCycleIter { + graph_clone: out_graph, + iter, + }) } else { - Some(self_cycles_vec) - }; - let strongly_connected_components: Vec> = kosaraju_scc(&graph_clone) - .into_iter() - .filter(|component| component.len() > 1) - .collect(); - SimpleCycleIter { - graph_clone, - scc: strongly_connected_components, - self_cycles, - path: Vec::new(), - blocked: HashSet::new(), - closed: HashSet::new(), - block: HashMap::new(), - stack: Vec::new(), - start_node: NodeIndex::new(u32::MAX as usize), - node_map: HashMap::new(), - reverse_node_map: HashMap::new(), - subgraph: StableDiGraph::new(), - } - } -} - -fn unblock( - node: NodeIndex, - blocked: &mut HashSet, - block: &mut HashMap>, -) { - let mut stack: IndexSet = IndexSet::with_hasher(RandomState::default()); - stack.insert(node); - while let Some(stack_node) = stack.pop() { - if blocked.remove(&stack_node) { - match block.get_mut(&stack_node) { - // stack.update(block[stack_node]): - Some(block_set) => { - block_set.drain().for_each(|n| { - stack.insert(n); - }); - } - // If block doesn't have stack_node treat it as an empty set - // (so no updates to stack) and populate it with an empty - // set. - None => { - block.insert(stack_node, HashSet::new()); - } - } - blocked.remove(&stack_node); - } - } -} - -#[allow(clippy::too_many_arguments)] -fn process_stack( - start_node: NodeIndex, - stack: &mut Vec<(NodeIndex, IndexSet)>, - path: &mut Vec, - closed: &mut HashSet, - blocked: &mut HashSet, - block: &mut HashMap>, - subgraph: &StableDiGraph<(), ()>, - reverse_node_map: &HashMap, -) -> Option { - while let Some((this_node, neighbors)) = stack.last_mut() { - if let Some(next_node) = neighbors.pop() { - if next_node == start_node { - // Out path in input graph basis - let mut out_path: Vec = Vec::with_capacity(path.len()); - for n in path { - out_path.push(reverse_node_map[n].index()); - closed.insert(*n); - } - return Some(NodeIndices { nodes: out_path }); - } else if blocked.insert(next_node) { - path.push(next_node); - stack.push(( - next_node, - subgraph - .neighbors(next_node) - .collect::>(), - )); - closed.remove(&next_node); - blocked.insert(next_node); - continue; - } - } - if neighbors.is_empty() { - if closed.contains(this_node) { - unblock(*this_node, blocked, block); - } else { - for neighbor in subgraph.neighbors(*this_node) { - let block_neighbor = block.entry(neighbor).or_insert_with(HashSet::new); - block_neighbor.insert(*this_node); + // Copy graph to remove self edges before running johnson's algorithm + let mut graph_clone = graph.borrow().copy(); + for node in &self_cycles_vec { + while let Some(edge_index) = graph_clone.graph.find_edge(*node, *node) { + graph_clone.graph.remove_edge(edge_index); } } - stack.pop(); - path.pop(); + let iter = johnson_simple_cycles(&graph_clone.graph, Some(self_cycles_vec)); + let out_graph = Py::new(py, graph_clone)?; + Ok(PySimpleCycleIter { + graph_clone: out_graph, + iter, + }) } } - None } #[pymethods] -impl SimpleCycleIter { - fn __iter__(slf: PyRef) -> Py { +impl PySimpleCycleIter { + fn __iter__(slf: PyRef) -> Py { slf.into() } - fn __next__(mut slf: PyRefMut) -> PyResult> { - if slf.self_cycles.is_some() { - let self_cycles = slf.self_cycles.as_mut().unwrap(); - let cycle_node = self_cycles.pop().unwrap(); - if self_cycles.is_empty() { - slf.self_cycles = None; - } - return Ok(Some(NodeIndices { - nodes: vec![cycle_node.index()], - })); - } - // Restore previous state if it exists - let mut stack: Vec<(NodeIndex, IndexSet)> = - std::mem::take(&mut slf.stack); - let mut path: Vec = std::mem::take(&mut slf.path); - let mut closed: HashSet = std::mem::take(&mut slf.closed); - let mut blocked: HashSet = std::mem::take(&mut slf.blocked); - let mut block: HashMap> = std::mem::take(&mut slf.block); - let mut subgraph: StableDiGraph<(), ()> = std::mem::take(&mut slf.subgraph); - let mut reverse_node_map: HashMap = - std::mem::take(&mut slf.reverse_node_map); - let mut node_map: HashMap = std::mem::take(&mut slf.node_map); - - if let Some(res) = process_stack( - slf.start_node, - &mut stack, - &mut path, - &mut closed, - &mut blocked, - &mut block, - &subgraph, - &reverse_node_map, - ) { - // Store internal state on yield - slf.stack = stack; - slf.path = path; - slf.closed = closed; - slf.blocked = blocked; - slf.block = block; - slf.subgraph = subgraph; - slf.reverse_node_map = reverse_node_map; - slf.node_map = node_map; - return Ok(Some(res)); - } else { - subgraph.remove_node(slf.start_node); - slf.scc - .extend(kosaraju_scc(&subgraph).into_iter().filter_map(|scc| { - if scc.len() > 1 { - let res = scc - .iter() - .map(|n| reverse_node_map[n]) - .collect::>(); - Some(res) - } else { - None - } - })); - } - while let Some(mut scc) = slf.scc.pop() { - let temp = build_subgraph(&slf.graph_clone, &scc); - subgraph = temp.0; - node_map = temp.1; - reverse_node_map = node_map.iter().map(|(k, v)| (*v, *k)).collect(); - // start_node, path, blocked, closed, block and stack all in subgraph basis - slf.start_node = node_map[&scc.pop().unwrap()]; - path = vec![slf.start_node]; - blocked = path.iter().copied().collect(); - // Nodes in cycle all - closed = HashSet::new(); - block = HashMap::new(); - stack = vec![( - slf.start_node, - subgraph - .neighbors(slf.start_node) - .collect::>(), - )]; - if let Some(res) = process_stack( - slf.start_node, - &mut stack, - &mut path, - &mut closed, - &mut blocked, - &mut block, - &subgraph, - &reverse_node_map, - ) { - // Store internal state on yield - slf.stack = stack; - slf.path = path; - slf.closed = closed; - slf.blocked = blocked; - slf.block = block; - slf.subgraph = subgraph; - slf.reverse_node_map = reverse_node_map; - slf.node_map = node_map; - return Ok(Some(res)); - } - subgraph.remove_node(slf.start_node); - slf.scc - .extend(kosaraju_scc(&subgraph).into_iter().filter_map(|scc| { - if scc.len() > 1 { - let res = scc - .iter() - .map(|n| reverse_node_map[n]) - .collect::>(); - Some(res) - } else { - None - } - })); - } - Ok(None) + fn __next__(mut slf: PyRefMut, py: Python) -> PyResult> { + let py_clone = slf.graph_clone.clone_ref(py); + let binding = py_clone.borrow(py); + let graph = binding.deref(); + let res: Option> = slf.iter.next(&graph.graph); + Ok(res.map(|cycle| NodeIndices { + nodes: cycle.into_iter().map(|x| x.index()).collect(), + })) } } diff --git a/src/connectivity/mod.rs b/src/connectivity/mod.rs index ecd2858be9..56839b2de6 100644 --- a/src/connectivity/mod.rs +++ b/src/connectivity/mod.rs @@ -92,8 +92,11 @@ pub fn cycle_basis(graph: &graph::PyGraph, root: Option) -> Vec johnson_simple_cycles::SimpleCycleIter { - johnson_simple_cycles::SimpleCycleIter::new(graph) +pub fn simple_cycles( + graph: Bound, + py: Python, +) -> PyResult { + johnson_simple_cycles::PySimpleCycleIter::new(py, graph) } /// Compute the strongly connected components for a directed graph