diff --git a/.all-contributorsrc b/.all-contributorsrc index 4cabb5284..d175b8231 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -630,6 +630,24 @@ "contributions": [ "code" ] + }, + { + "login": "BeaMarton13", + "name": "Bea Márton", + "avatar_url": "https://avatars.githubusercontent.com/u/204701577?v=4", + "profile": "https://github.com/BeaMarton13", + "contributions": [ + "code" + ] + }, + { + "login": "SKG24", + "name": "Sanat Kumar Gupta", + "avatar_url": "https://avatars.githubusercontent.com/u/123228827?v=4", + "profile": "https://github.com/SKG24", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..686944ff0 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,5 @@ + + + + +- [ ] By submitting this pull request, I assign the copyright of my contribution to _The igraph development team_. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8cba2e82e..2192d7c72 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,69 +3,59 @@ name: Build and test on: [push, pull_request] env: + CIBW_ENABLE: pypy CIBW_ENVIRONMENT_PASS_LINUX: PYTEST_TIMEOUT + CIBW_PROJECT_REQUIRES_PYTHON: ">=3.9" CIBW_TEST_COMMAND: "cd {project} && pip install --prefer-binary '.[test]' && python -m pytest -v tests" - CIBW_SKIP: "cp36-* cp37-* cp38-* pp37-* pp38-*" + # Free-threaded builds excluded for Python 3.14 because they do not support the limited API + CIBW_SKIP: "cp314t-*" PYTEST_TIMEOUT: 60 jobs: build_wheel_linux: - name: Build wheels on Linux (${{ matrix.wheel_arch }}) - runs-on: ubuntu-20.04 - strategy: - fail-fast: false - matrix: - wheel_arch: [x86_64, i686] - + name: Build wheels on Linux (x86_64) + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: true fetch-depth: 0 - - uses: actions/setup-python@v5 - name: Install Python - with: - python-version: '3.9' - - name: Build wheels (manylinux) - uses: pypa/cibuildwheel@v2.22.0 + uses: pypa/cibuildwheel@v3.3.0 env: CIBW_BEFORE_BUILD: "yum install -y flex bison libxml2-devel zlib-devel cairo-devel && pip install -U cmake pip setuptools wheel && python setup.py build_c_core" - CIBW_BUILD: "*-manylinux_${{ matrix.wheel_arch }}" - # Skip tests for Python 3.10 onwards because SciPy does not have - # 32-bit wheels for Linux. - CIBW_TEST_SKIP: "cp310-manylinux_i686 cp311-manylinux_i686 cp312-manylinux_i686 cp313-manylinux_i686" + CIBW_BUILD: "*-manylinux_x86_64" - name: Build wheels (musllinux) - uses: pypa/cibuildwheel@v2.22.0 + uses: pypa/cibuildwheel@v3.3.0 env: CIBW_BEFORE_BUILD: "apk add flex bison libxml2-dev zlib-dev cairo-dev && pip install -U cmake pip setuptools wheel && python setup.py build_c_core" - CIBW_BUILD: "*-musllinux_${{ matrix.wheel_arch }}" + CIBW_BUILD: "*-musllinux_x86_64" CIBW_TEST_COMMAND: "cd {project} && pip install --prefer-binary '.[test-musl]' && python -m pytest -v tests" - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: - name: wheels-linux-${{ matrix.wheel_arch }} + name: wheels-linux-x86_64 path: ./wheelhouse/*.whl build_wheel_linux_aarch64_manylinux: name: Build wheels on Linux (aarch64/manylinux) runs-on: ubuntu-22.04-arm steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: true fetch-depth: 0 - name: Build wheels (manylinux) - uses: pypa/cibuildwheel@v2.22.0 + uses: pypa/cibuildwheel@v3.3.0 env: CIBW_BEFORE_BUILD: "yum install -y flex bison libxml2-devel zlib-devel cairo-devel && pip install -U cmake pip setuptools wheel && python setup.py build_c_core" CIBW_ARCHS_LINUX: aarch64 CIBW_BUILD: "*-manylinux_aarch64" - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: name: wheels-linux-aarch64-manylinux path: ./wheelhouse/*.whl @@ -74,20 +64,20 @@ jobs: name: Build wheels on Linux (aarch64/musllinux) runs-on: ubuntu-22.04-arm steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: true fetch-depth: 0 - name: Build wheels (musllinux) - uses: pypa/cibuildwheel@v2.22.0 + uses: pypa/cibuildwheel@v3.3.0 env: CIBW_BEFORE_BUILD: "apk add flex bison libxml2-dev zlib-dev cairo-dev && pip install -U cmake pip setuptools wheel && python setup.py build_c_core" CIBW_ARCHS_LINUX: aarch64 CIBW_BUILD: "*-musllinux_aarch64" CIBW_TEST_COMMAND: "cd {project} && pip install --prefer-binary '.[test-musl]' && python -m pytest -v tests" - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: name: wheels-linux-aarch64-musllinux path: ./wheelhouse/*.whl @@ -97,7 +87,7 @@ jobs: runs-on: macos-latest env: LLVM_VERSION: "14.0.5" - MACOSX_DEPLOYMENT_TARGET: "10.9" + MACOSX_DEPLOYMENT_TARGET: "10.15" strategy: matrix: include: @@ -108,34 +98,28 @@ jobs: wheel_arch: arm64 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: true fetch-depth: 0 - name: Cache installed C core id: cache-c-core - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: vendor/install key: C-core-cache-${{ runner.os }}-${{ matrix.cmake_arch }}-llvm${{ env.LLVM_VERSION }}-${{ hashFiles('.git/modules/**/HEAD') }} - name: Cache C core dependencies id: cache-c-deps - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/local key: deps-cache-v2-${{ runner.os }}-${{ matrix.cmake_arch }}-llvm${{ env.LLVM_VERSION }} - - uses: actions/setup-python@v5 - name: Install Python - with: - python-version: '3.9' - - name: Install OS dependencies if: steps.cache-c-core.outputs.cache-hit != 'true' || steps.cache-c-deps.outputs.cache-hit != 'true' # Only needed when building the C core or libomp - run: - brew install ninja autoconf automake libtool + run: brew install autoconf automake libtool - name: Install OpenMP library if: steps.cache-c-deps.outputs.cache-hit != 'true' @@ -149,41 +133,40 @@ jobs: cmake --install . - name: Build wheels - uses: pypa/cibuildwheel@v2.22.0 + uses: pypa/cibuildwheel@v3.3.0 env: CIBW_ARCHS_MACOS: "${{ matrix.wheel_arch }}" CIBW_BEFORE_BUILD: "pip install -U setuptools && python setup.py build_c_core" CIBW_ENVIRONMENT: "LDFLAGS=-L$HOME/local/lib" IGRAPH_CMAKE_EXTRA_ARGS: -DCMAKE_OSX_ARCHITECTURES=${{ matrix.cmake_arch }} ${{ matrix.cmake_extra_args }} -DCMAKE_PREFIX_PATH=$HOME/local - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: name: wheels-macos-${{ matrix.wheel_arch }} path: ./wheelhouse/*.whl build_wheel_wasm: name: Build wheels for WebAssembly - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: true fetch-depth: 0 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 name: Install Python with: - python-version: '3.12.1' + python-version: "3.12.1" - name: Install OS dependencies - run: - sudo apt install ninja-build cmake flex bison + run: sudo apt install ninja-build cmake flex bison - uses: mymindstorm/setup-emsdk@v14 with: - version: '3.1.58' - actions-cache-folder: 'emsdk-cache' + version: "3.1.58" + actions-cache-folder: "emsdk-cache" - name: Build wheel run: | @@ -198,44 +181,48 @@ jobs: limit-access-to-actor: true wait-timeout-minutes: 5 - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: name: wheels-wasm path: ./dist/*.whl build_wheel_win: name: Build wheels on Windows (${{ matrix.cmake_arch }}) - runs-on: windows-2019 strategy: matrix: include: - cmake_arch: Win32 wheel_arch: win32 vcpkg_arch: x86 + os: windows-2022 + test_extra: test - cmake_arch: x64 wheel_arch: win_amd64 vcpkg_arch: x64 + os: windows-2022 + test_extra: test + - cmake_arch: ARM64 + wheel_arch: win_arm64 + vcpkg_arch: arm64 + os: windows-11-arm + test_extra: test-win-arm64 + runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: true fetch-depth: 0 - - uses: actions/setup-python@v5 - name: Install Python - with: - python-version: '3.9' - - name: Cache installed C core id: cache-c-core - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: vendor/install key: C-core-cache-${{ runner.os }}-${{ matrix.cmake_arch }}-${{ hashFiles('.git/modules/**/HEAD') }} - name: Cache VCPKG - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: C:/vcpkg/installed/ key: vcpkg-${{ runner.os }}-${{ matrix.vcpkg_arch }} @@ -247,25 +234,25 @@ jobs: - name: Install VCPKG libraries run: | %VCPKG_INSTALLATION_ROOT%\vcpkg.exe integrate install - %VCPKG_INSTALLATION_ROOT%\vcpkg.exe install libxml2:${{ matrix.vcpkg_arch }}-windows-static-md + %VCPKG_INSTALLATION_ROOT%\vcpkg.exe install liblzma:${{ matrix.vcpkg_arch }}-windows-static-md libxml2:${{ matrix.vcpkg_arch }}-windows-static-md shell: cmd - name: Build wheels - uses: pypa/cibuildwheel@v2.22.0 + uses: pypa/cibuildwheel@v3.3.0 env: CIBW_BEFORE_BUILD: "pip install -U setuptools && python setup.py build_c_core" CIBW_BUILD: "*-${{ matrix.wheel_arch }}" - CIBW_TEST_COMMAND: "cd /d {project} && pip install --prefer-binary \".[test]\" && python -m pytest tests" + CIBW_TEST_COMMAND: 'cd /d {project} && pip install --prefer-binary ".[${{ matrix.test_extra }}]" && python -m pytest tests' # Skip tests for Python 3.10 onwards because SciPy does not have - # 32-bit wheels for Linux. - CIBW_TEST_SKIP: "cp310-win32 cp311-win32 cp312-win32 cp313-win32" + # 32-bit wheels for Windows any more + CIBW_TEST_SKIP: "cp310-win32 cp311-win32 cp312-win32 cp313-win32 cp314-win32" IGRAPH_CMAKE_EXTRA_ARGS: -DCMAKE_BUILD_TYPE=RelWithDebInfo -DVCPKG_TARGET_TRIPLET=${{ matrix.vcpkg_arch }}-windows-static-md -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -A ${{ matrix.cmake_arch }} IGRAPH_EXTRA_LIBRARY_PATH: C:/vcpkg/installed/${{ matrix.vcpkg_arch }}-windows-static-md/lib/ IGRAPH_STATIC_EXTENSION: True IGRAPH_EXTRA_LIBRARIES: libxml2,lzma,zlib,iconv,charset,bcrypt IGRAPH_EXTRA_DYNAMIC_LIBRARIES: wsock32,ws2_32 - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: name: wheels-win-${{ matrix.wheel_arch }} path: ./wheelhouse/*.whl @@ -274,14 +261,14 @@ jobs: name: Build sdist and test extra dependencies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: true fetch-depth: 0 - name: Cache installed C core id: cache-c-core - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | vendor/install @@ -289,26 +276,25 @@ jobs: - name: Install OS dependencies if: steps.cache-c-core.outputs.cache-hit != 'true' # Only needed when building the C core - run: - sudo apt install ninja-build cmake flex bison + run: sudo apt install ninja-build cmake flex bison - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 name: Install Python with: - python-version: '3.9' + python-version: "3.9" - name: Build sdist run: | python setup.py build_c_core python setup.py sdist - python setup.py install + pip install . - name: Test run: | - pip install --prefer-binary cairocffi numpy scipy pandas networkx pytest pytest-timeout + pip install '.[test]' python -m pytest -v tests - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: name: sdist path: dist/*.tar.gz @@ -317,18 +303,18 @@ jobs: # for the "Test" step below. build_with_sanitizer: name: Build with sanitizers for debugging purposes - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest env: IGRAPH_CMAKE_EXTRA_ARGS: -DFORCE_COLORED_OUTPUT=ON steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: true fetch-depth: 0 - name: Cache installed C core id: cache-c-core - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | vendor/build @@ -337,30 +323,21 @@ jobs: - name: Install OS dependencies if: steps.cache-c-core.outputs.cache-hit != 'true' # Only needed when building the C core - run: - sudo apt install ninja-build cmake flex bison + run: sudo apt install ninja-build cmake flex bison - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 name: Install Python with: - python-version: '3.12' - - - name: Install test dependencies - run: | - pip install --prefer-binary pytest pytest-timeout setuptools - - - name: Build C core - env: - IGRAPH_USE_SANITIZERS: 1 - run: | - python setup.py build_c_core + python-version: "3.12" - name: Build and install Python extension env: IGRAPH_USE_SANITIZERS: 1 run: | - # NOTE: install calls "build" first - python setup.py install + # We cannot install the test dependency group because many test dependencies cause + # false positives in the sanitizer + pip install --prefer-binary networkx pytest pytest-timeout + pip install -e . # Only pytest, and nothing else should be run in this section due to the presence of LD_PRELOAD. # The ASan/UBSan library versions need to be updated when switching to a newer Ubuntu/GCC. @@ -371,4 +348,5 @@ jobs: ASAN_OPTIONS: "detect_stack_use_after_return=1" LSAN_OPTIONS: "suppressions=etc/lsan-suppr.txt:print_suppressions=false" run: | - LD_PRELOAD=/lib/x86_64-linux-gnu/libasan.so.5:/lib/x86_64-linux-gnu/libubsan.so.1 python -m pytest --capture=sys tests + sudo sysctl vm.mmap_rnd_bits=28 + LD_PRELOAD=/lib/x86_64-linux-gnu/libasan.so.8:/lib/x86_64-linux-gnu/libubsan.so.1 python -m pytest --capture=sys tests diff --git a/CHANGELOG.md b/CHANGELOG.md index dea92015e..f2424f4fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,50 @@ # igraph Python interface changelog -## [main] +## 1.0.1 - 2025-12-26 + +### Changed + +- The C core of igraph was updated to version 1.0.1. + +## [1.0.0] - 2025-10-23 + +### Added + +- Added `Graph.Nearest_Neighbor_Graph()`. + +- Added `node_in_weights` argument to `Graph.community_leiden()`. + +- Added `align_layout()` to align the principal axes of a layout nicely + with screen dimensions. + +- Added `Graph.commnity_voronoi()`. + +- Added `Graph.commnity_fluid_communities()`. + +### Changed + +- The C core of igraph was updated to version 1.0.0. + +- Most layouts are now auto-aligned using `align_layout()`. + +- Dropped support for PyPy 3.9 and PyPy 3.10 as they are now EOL. + +### Miscellaneous + +- Documentation improvements. + +- This is the last version that supports Python 3.9 as it will reach its + end of life at the end of October 2025. + +## [0.11.9] - 2025-06-11 ### Changed - Dropped support for Python 3.8 as it has now reached its end of life. -- The C core of igraph was updated to version 0.10.15. +- The C core of igraph was updated to version 0.10.16. + +- Added `Graph.simple_cycles()` to find simple cycles in the graph. ## [0.11.8] - 2024-10-25 @@ -43,31 +81,31 @@ ### Added - - Added `Graph.Hypercube()` for creating n-dimensional hypercube graphs. +- Added `Graph.Hypercube()` for creating n-dimensional hypercube graphs. - - Added `Graph.Chung_Lu()` for sampling from the Chung-Lu model as well as - several related models. +- Added `Graph.Chung_Lu()` for sampling from the Chung-Lu model as well as + several related models. - - Added `Graph.is_complete()` to test if there is a connection between all - distinct pairs of vertices. +- Added `Graph.is_complete()` to test if there is a connection between all + distinct pairs of vertices. - - Added `Graph.is_clique()` to test if a set of vertices forms a clique. +- Added `Graph.is_clique()` to test if a set of vertices forms a clique. - - Added `Graph.is_independent_vertex_set()` to test if some vertices form an - independent set. +- Added `Graph.is_independent_vertex_set()` to test if some vertices form an + independent set. - - Added `Graph.mean_degree()` for a convenient way to compute the average - degree of a graph. +- Added `Graph.mean_degree()` for a convenient way to compute the average + degree of a graph. ### Changed - - The C core of igraph was updated to version 0.10.13. +- The C core of igraph was updated to version 0.10.13. - - `Graph.rewire()` now attempts to perform edge swaps 10 times the number of - edges by default. +- `Graph.rewire()` now attempts to perform edge swaps 10 times the number of + edges by default. - - Error messages issued when an attribute is not found now mention the name - and type of that attribute. +- Error messages issued when an attribute is not found now mention the name + and type of that attribute. ## [0.11.5] - 2024-05-07 @@ -87,18 +125,18 @@ - Deprecated `PyCObject` API calls in the C code were replaced by calls to `PyCapsule`, thanks to @DavidRConnell in - https://github.com/igraph/python-igraph/pull/763 + - `get_shortest_path()` documentation was clarified by @JDPowell648 in - https://github.com/igraph/python-igraph/pull/764 + - It is now possible to link to an existing igraph C core on MSYS2, thanks to - @Kreijstal in https://github.com/igraph/python-igraph/pull/770 + @Kreijstal in ### Fixed - Bugfix in the NetworkX graph conversion code by @rmmaf in - https://github.com/igraph/python-igraph/pull/767 + ## [0.11.4] @@ -719,11 +757,12 @@ ## [0.8.3] This is the last released version of `python-igraph` without a changelog file. -Please refer to the commit logs at https://github.com/igraph/python-igraph for +Please refer to the commit logs at for a list of changes affecting versions up to 0.8.3. Notable changes after 0.8.3 are documented above. -[main]: https://github.com/igraph/python-igraph/compare/0.11.8...main +[1.0.0]: https://github.com/igraph/python-igraph/compare/0.11.9...1.0.0 +[0.11.9]: https://github.com/igraph/python-igraph/compare/0.11.8...0.11.9 [0.11.8]: https://github.com/igraph/python-igraph/compare/0.11.7...0.11.8 [0.11.7]: https://github.com/igraph/python-igraph/compare/0.11.6...0.11.7 [0.11.6]: https://github.com/igraph/python-igraph/compare/0.11.5...0.11.6 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index f43ef7b6f..d22d5bd6f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -95,6 +95,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Michael Schneider
Michael Schneider

💻 Thomas Krijnen
Thomas Krijnen

💻 Tim Bernhard
Tim Bernhard

💻 + Bea Márton
Bea Márton

💻 + + + Sanat Kumar Gupta
Sanat Kumar Gupta

💻 diff --git a/MANIFEST.in b/MANIFEST.in index 5c68b5e82..39e2ee9fe 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -8,6 +8,10 @@ include scripts/*.sh include scripts/*.py include tests/*.py +include CHANGELOG.md +include CONTRIBUTORS.md +include CITATION.cff + graft vendor/source/igraph graft doc diff --git a/doc/examples_sphinx-gallery/articulation_points.py b/doc/examples_sphinx-gallery/articulation_points.py index 0dadded36..6492f1f6f 100644 --- a/doc/examples_sphinx-gallery/articulation_points.py +++ b/doc/examples_sphinx-gallery/articulation_points.py @@ -8,6 +8,7 @@ This example shows how to compute and visualize the `articulation points `_ in a graph using :meth:`igraph.GraphBase.articulation_points`. For an example on bridges instead, see :ref:`tutorials-bridges`. """ + import igraph as ig import matplotlib.pyplot as plt @@ -30,9 +31,9 @@ vertex_size=30, vertex_color="lightblue", vertex_label=range(g.vcount()), - vertex_frame_color = ["red" if v in articulation_points else "black" for v in g.vs], - vertex_frame_width = [3 if v in articulation_points else 1 for v in g.vs], + vertex_frame_color=["red" if v in articulation_points else "black" for v in g.vs], + vertex_frame_width=[3 if v in articulation_points else 1 for v in g.vs], edge_width=0.8, - edge_color='gray' + edge_color="gray", ) plt.show() diff --git a/doc/examples_sphinx-gallery/betweenness.py b/doc/examples_sphinx-gallery/betweenness.py index 29ed9b3e3..7c52bdb08 100644 --- a/doc/examples_sphinx-gallery/betweenness.py +++ b/doc/examples_sphinx-gallery/betweenness.py @@ -24,14 +24,14 @@ # :meth:`igraph.utils.rescale` to rescale the betweennesses in the interval # ``[0, 1]``. def plot_betweenness(g, vertex_betweenness, edge_betweenness, ax, cax1, cax2): - '''Plot vertex/edge betweenness, with colorbars + """Plot vertex/edge betweenness, with colorbars Args: g: the graph to plot. ax: the Axes for the graph cax1: the Axes for the vertex betweenness colorbar cax2: the Axes for the edge betweenness colorbar - ''' + """ # Rescale betweenness to be between 0.0 and 1.0 scaled_vertex_betweenness = ig.rescale(vertex_betweenness, clamp=True) @@ -45,7 +45,7 @@ def plot_betweenness(g, vertex_betweenness, edge_betweenness, ax, cax1, cax2): # Plot graph g.vs["color"] = [cmap1(betweenness) for betweenness in scaled_vertex_betweenness] - g.vs["size"] = ig.rescale(vertex_betweenness, (10, 50)) + g.vs["size"] = ig.rescale(vertex_betweenness, (10, 50)) g.es["color"] = [cmap2(betweenness) for betweenness in scaled_edge_betweenness] g.es["width"] = ig.rescale(edge_betweenness, (0.5, 1.0)) ig.plot( @@ -58,8 +58,8 @@ def plot_betweenness(g, vertex_betweenness, edge_betweenness, ax, cax1, cax2): # Color bars norm1 = ScalarMappable(norm=Normalize(0, max(vertex_betweenness)), cmap=cmap1) norm2 = ScalarMappable(norm=Normalize(0, max(edge_betweenness)), cmap=cmap2) - plt.colorbar(norm1, cax=cax1, orientation="horizontal", label='Vertex Betweenness') - plt.colorbar(norm2, cax=cax2, orientation="horizontal", label='Edge Betweenness') + plt.colorbar(norm1, cax=cax1, orientation="horizontal", label="Vertex Betweenness") + plt.colorbar(norm2, cax=cax2, orientation="horizontal", label="Edge Betweenness") # %% diff --git a/doc/examples_sphinx-gallery/bipartite_matching.py b/doc/examples_sphinx-gallery/bipartite_matching.py index ad7538fa2..1046e1322 100644 --- a/doc/examples_sphinx-gallery/bipartite_matching.py +++ b/doc/examples_sphinx-gallery/bipartite_matching.py @@ -7,6 +7,7 @@ This example demonstrates an efficient way to find and visualise a maximum biparite matching using :meth:`igraph.Graph.maximum_bipartite_matching`. """ + import igraph as ig import matplotlib.pyplot as plt @@ -16,7 +17,7 @@ # - nodes 5-8 to the other side g = ig.Graph.Bipartite( [0, 0, 0, 0, 0, 1, 1, 1, 1], - [(0, 5), (1, 6), (1, 7), (2, 5), (2, 8), (3, 6), (4, 5), (4, 6)] + [(0, 5), (1, 6), (1, 7), (2, 5), (2, 8), (3, 6), (4, 5), (4, 6)], ) # %% diff --git a/doc/examples_sphinx-gallery/bipartite_matching_maxflow.py b/doc/examples_sphinx-gallery/bipartite_matching_maxflow.py index 28b06e0b7..eec39a81b 100644 --- a/doc/examples_sphinx-gallery/bipartite_matching_maxflow.py +++ b/doc/examples_sphinx-gallery/bipartite_matching_maxflow.py @@ -9,6 +9,7 @@ .. note:: :meth:`igraph.Graph.maximum_bipartite_matching` is usually a better way to find the maximum bipartite matching. For a demonstration on how to use that method instead, check out :ref:`tutorials-bipartite-matching`. """ + import igraph as ig import matplotlib.pyplot as plt @@ -17,7 +18,7 @@ g = ig.Graph( 9, [(0, 4), (0, 5), (1, 4), (1, 6), (1, 7), (2, 5), (2, 7), (2, 8), (3, 6), (3, 7)], - directed=True + directed=True, ) # %% @@ -62,6 +63,6 @@ vertex_size=30, vertex_label=range(g.vcount()), vertex_color=["lightblue" if i < 9 else "orange" for i in range(11)], - edge_width=[1.0 + flow.flow[i] for i in range(g.ecount())] + edge_width=[1.0 + flow.flow[i] for i in range(g.ecount())], ) plt.show() diff --git a/doc/examples_sphinx-gallery/bridges.py b/doc/examples_sphinx-gallery/bridges.py index 26b8dce6a..08dc47d48 100644 --- a/doc/examples_sphinx-gallery/bridges.py +++ b/doc/examples_sphinx-gallery/bridges.py @@ -7,6 +7,7 @@ This example shows how to compute and visualize the `bridges `_ in a graph using :meth:`igraph.GraphBase.bridges`. For an example on articulation points instead, see :ref:`tutorials-articulation-points`. """ + import igraph as ig import matplotlib.pyplot as plt @@ -37,7 +38,7 @@ target=ax, vertex_size=30, vertex_color="lightblue", - vertex_label=range(g.vcount()) + vertex_label=range(g.vcount()), ) plt.show() @@ -72,9 +73,9 @@ vertex_size=30, vertex_color="lightblue", vertex_label=range(g.vcount()), - edge_background="#FFF0", # transparent background color - edge_align_label=True, # make sure labels are aligned with the edge + edge_background="#FFF0", # transparent background color + edge_align_label=True, # make sure labels are aligned with the edge edge_label=g.es["label"], - edge_label_color="red" + edge_label_color="red", ) plt.show() diff --git a/doc/examples_sphinx-gallery/cluster_contraction.py b/doc/examples_sphinx-gallery/cluster_contraction.py index aa7cf38a7..fc02fc293 100644 --- a/doc/examples_sphinx-gallery/cluster_contraction.py +++ b/doc/examples_sphinx-gallery/cluster_contraction.py @@ -7,12 +7,13 @@ This example shows how to find the communities in a graph, then contract each community into a single node using :class:`igraph.clustering.VertexClustering`. For this tutorial, we'll use the *Donald Knuth's Les Miserables Network*, which shows the coapperances of characters in the novel *Les Miserables*. """ + import igraph as ig import matplotlib.pyplot as plt # %% # We begin by load the graph from file. The file containing this network can be -# downloaded `here `_. +# downloaded `here `_. g = ig.load("./lesmis/lesmis.gml") # %% @@ -106,7 +107,10 @@ # Finally, we can assign colors to the clusters and plot the cluster graph, # including a legend to make things clear: palette2 = ig.GradientPalette("gainsboro", "black") -g.es["color"] = [palette2.get(int(i)) for i in ig.rescale(cluster_graph.es["size"], (0, 255), clamp=True)] +g.es["color"] = [ + palette2.get(int(i)) + for i in ig.rescale(cluster_graph.es["size"], (0, 255), clamp=True) +] fig2, ax2 = plt.subplots() ig.plot( @@ -123,7 +127,8 @@ legend_handles = [] for i in range(num_communities): handle = ax2.scatter( - [], [], + [], + [], s=100, facecolor=palette1.get(i), edgecolor="k", @@ -133,7 +138,7 @@ ax2.legend( handles=legend_handles, - title='Community:', + title="Community:", bbox_to_anchor=(0, 1.0), bbox_transform=ax2.transAxes, ) diff --git a/doc/examples_sphinx-gallery/complement.py b/doc/examples_sphinx-gallery/complement.py index 4436ae372..b4f2f17a0 100644 --- a/doc/examples_sphinx-gallery/complement.py +++ b/doc/examples_sphinx-gallery/complement.py @@ -7,6 +7,7 @@ This example shows how to generate the `complement graph `_ of a graph (sometimes known as the anti-graph) using :meth:`igraph.GraphBase.complementer`. """ + import igraph as ig import matplotlib.pyplot as plt import random @@ -49,14 +50,14 @@ layout="circle", vertex_color="black", ) -axs[0, 0].set_title('Original graph') +axs[0, 0].set_title("Original graph") ig.plot( g2, target=axs[0, 1], layout="circle", vertex_color="black", ) -axs[0, 1].set_title('Complement graph') +axs[0, 1].set_title("Complement graph") ig.plot( g_full, @@ -64,12 +65,12 @@ layout="circle", vertex_color="black", ) -axs[1, 0].set_title('Union graph') +axs[1, 0].set_title("Union graph") ig.plot( g_empty, target=axs[1, 1], layout="circle", vertex_color="black", ) -axs[1, 1].set_title('Complement of union graph') +axs[1, 1].set_title("Complement of union graph") plt.show() diff --git a/doc/examples_sphinx-gallery/configuration.py b/doc/examples_sphinx-gallery/configuration.py index ab408886d..4e9c077eb 100644 --- a/doc/examples_sphinx-gallery/configuration.py +++ b/doc/examples_sphinx-gallery/configuration.py @@ -7,6 +7,7 @@ This example shows how to use igraph's :class:`configuration instance ` to set default igraph settings. This is useful for setting global settings so that they don't need to be explicitly stated at the beginning of every igraph project you work on. """ + import igraph as ig import matplotlib.pyplot as plt import random @@ -18,16 +19,16 @@ ig.config["plotting.palette"] = "rainbow" # %% -# Then, we save them. By default, ``ig.config.save()`` will save files to -# ``~/.igraphrc`` on Linux and Max OS X systems, or in -# ``%USERPROFILE%\.igraphrc`` for Windows systems: -ig.config.save() +# The updated configuration affects only the current session. Optionally, it +# can be saved using ``ig.config.save()``. By default, this function writes the +# configuration to ``~/.igraphrc`` on Linux and Max OS X systems, and in +# ``%USERPROFILE%\.igraphrc`` on Windows systems. # %% -# The code above only needs to be run once (to store the new config options -# into the ``.igraphrc`` file). Whenever you use igraph and this file exists, -# igraph will read its content and use those options as defaults. For -# example, let's create and plot a new graph to demonstrate: +# The configuration only needs to be saved to `.igraphrc` once, and it will +# be automatically used in all future sessions. Whenever you use igraph and +# this file exists, igraph will read its content and use those options as +# defaults. For example, let's create and plot a new graph to demonstrate: random.seed(1) g = ig.Graph.Barabasi(n=100, m=1) diff --git a/doc/examples_sphinx-gallery/connected_components.py b/doc/examples_sphinx-gallery/connected_components.py index 008f66cec..33c341fee 100644 --- a/doc/examples_sphinx-gallery/connected_components.py +++ b/doc/examples_sphinx-gallery/connected_components.py @@ -7,6 +7,7 @@ This example demonstrates how to visualise the connected components in a graph using :meth:`igraph.GraphBase.connected_components`. """ + import igraph as ig import matplotlib.pyplot as plt import random @@ -21,7 +22,7 @@ # %% # Now we can cluster the graph into weakly connected components, i.e. subgraphs # that have no edges connecting them to one another: -components = g.connected_components(mode='weak') +components = g.connected_components(mode="weak") # %% # Finally, we can visualize the distinct connected components of the graph: diff --git a/doc/examples_sphinx-gallery/delaunay-triangulation.py b/doc/examples_sphinx-gallery/delaunay-triangulation.py index ba6ce5a85..3a25eebe1 100644 --- a/doc/examples_sphinx-gallery/delaunay-triangulation.py +++ b/doc/examples_sphinx-gallery/delaunay-triangulation.py @@ -8,6 +8,7 @@ This example demonstrates how to calculate the `Delaunay triangulation `_ of an input graph. We start by generating a set of points on a 2D grid using random ``numpy`` arrays and a graph with those vertex coordinates and no edges. """ + import numpy as np from scipy.spatial import Delaunay import igraph as ig @@ -20,8 +21,8 @@ np.random.seed(0) x, y = np.random.rand(2, 30) g = ig.Graph(30) -g.vs['x'] = x -g.vs['y'] = y +g.vs["x"] = x +g.vs["y"] = y # %% # Because we already set the `x` and `y` vertex attributes, we can use diff --git a/doc/examples_sphinx-gallery/erdos_renyi.py b/doc/examples_sphinx-gallery/erdos_renyi.py index c4f2879f7..aeb808992 100644 --- a/doc/examples_sphinx-gallery/erdos_renyi.py +++ b/doc/examples_sphinx-gallery/erdos_renyi.py @@ -12,6 +12,7 @@ We generate two graphs of each, so we can confirm that our graph generator is truly random. """ + import igraph as ig import matplotlib.pyplot as plt import random @@ -48,33 +49,11 @@ # differences: fig, axs = plt.subplots(2, 2) # Probability -ig.plot( - g1, - target=axs[0, 0], - layout="circle", - vertex_color="lightblue" -) -ig.plot( - g2, - target=axs[0, 1], - layout="circle", - vertex_color="lightblue" -) -axs[0, 0].set_ylabel('Probability') +ig.plot(g1, target=axs[0, 0], layout="circle", vertex_color="lightblue") +ig.plot(g2, target=axs[0, 1], layout="circle", vertex_color="lightblue") +axs[0, 0].set_ylabel("Probability") # N edges -ig.plot( - g3, - target=axs[1, 0], - layout="circle", - vertex_color="lightblue", - vertex_size=15 -) -ig.plot( - g4, - target=axs[1, 1], - layout="circle", - vertex_color="lightblue", - vertex_size=15 -) -axs[1, 0].set_ylabel('N. edges') +ig.plot(g3, target=axs[1, 0], layout="circle", vertex_color="lightblue", vertex_size=15) +ig.plot(g4, target=axs[1, 1], layout="circle", vertex_color="lightblue", vertex_size=15) +axs[1, 0].set_ylabel("N. edges") plt.show() diff --git a/doc/examples_sphinx-gallery/generate_dag.py b/doc/examples_sphinx-gallery/generate_dag.py index addbc661c..1565a59ed 100644 --- a/doc/examples_sphinx-gallery/generate_dag.py +++ b/doc/examples_sphinx-gallery/generate_dag.py @@ -8,6 +8,7 @@ This example demonstrates how to create a random directed acyclic graph (DAG), which is useful in a number of contexts including for Git commit history. """ + import igraph as ig import matplotlib.pyplot as plt import random diff --git a/doc/examples_sphinx-gallery/isomorphism.py b/doc/examples_sphinx-gallery/isomorphism.py index 7f98bc053..57c6034f4 100644 --- a/doc/examples_sphinx-gallery/isomorphism.py +++ b/doc/examples_sphinx-gallery/isomorphism.py @@ -7,6 +7,7 @@ This example shows how to check for `isomorphism `_ between small graphs using :meth:`igraph.GraphBase.isomorphic`. """ + import igraph as ig import matplotlib.pyplot as plt @@ -66,6 +67,20 @@ target=axs[2], **visual_style, ) -fig.text(0.38, 0.5, '$\\simeq$' if g1.isomorphic(g2) else '$\\neq$', fontsize=15, ha='center', va='center') -fig.text(0.65, 0.5, '$\\simeq$' if g2.isomorphic(g3) else '$\\neq$', fontsize=15, ha='center', va='center') +fig.text( + 0.38, + 0.5, + "$\\simeq$" if g1.isomorphic(g2) else "$\\neq$", + fontsize=15, + ha="center", + va="center", +) +fig.text( + 0.65, + 0.5, + "$\\simeq$" if g2.isomorphic(g3) else "$\\neq$", + fontsize=15, + ha="center", + va="center", +) plt.show() diff --git a/doc/examples_sphinx-gallery/maxflow.py b/doc/examples_sphinx-gallery/maxflow.py index 246d4c451..eb76684a4 100644 --- a/doc/examples_sphinx-gallery/maxflow.py +++ b/doc/examples_sphinx-gallery/maxflow.py @@ -8,16 +8,13 @@ This example shows how to construct a max flow on a directed graph with edge capacities using :meth:`igraph.Graph.maxflow`. """ + import igraph as ig import matplotlib.pyplot as plt # %% # First, we generate a graph and assign a "capacity" to each edge: -g = ig.Graph( - 6, - [(3, 2), (3, 4), (2, 1), (4,1), (4, 5), (1, 0), (5, 0)], - directed=True -) +g = ig.Graph(6, [(3, 2), (3, 4), (2, 1), (4, 1), (4, 5), (1, 0), (5, 0)], directed=True) g.es["capacity"] = [7, 8, 1, 2, 3, 4, 5] # %% @@ -39,6 +36,6 @@ target=ax, layout="circle", vertex_label=range(g.vcount()), - vertex_color="lightblue" + vertex_color="lightblue", ) plt.show() diff --git a/doc/examples_sphinx-gallery/minimum_spanning_trees.py b/doc/examples_sphinx-gallery/minimum_spanning_trees.py index 60c320bf6..9177e976a 100644 --- a/doc/examples_sphinx-gallery/minimum_spanning_trees.py +++ b/doc/examples_sphinx-gallery/minimum_spanning_trees.py @@ -8,6 +8,7 @@ This example shows how to generate a `minimum spanning tree `_ from an input graph using :meth:`igraph.Graph.spanning_tree`. If you only need a regular spanning tree, check out :ref:`tutorials-spanning-trees`. """ + import random import igraph as ig import matplotlib.pyplot as plt diff --git a/doc/examples_sphinx-gallery/online_user_actions.py b/doc/examples_sphinx-gallery/online_user_actions.py index 1da528570..47cc00ff5 100644 --- a/doc/examples_sphinx-gallery/online_user_actions.py +++ b/doc/examples_sphinx-gallery/online_user_actions.py @@ -18,23 +18,24 @@ # indicates a certain action taken by a user (e.g. click on a button within a # website). Actual user data usually come with time stamp, but that's not # essential for this example. -action_dataframe = pd.DataFrame([ - ['dsj3239asadsa3', 'createPage', 'greatProject'], - ['2r09ej221sk2k5', 'editPage', 'greatProject'], - ['dsj3239asadsa3', 'editPage', 'greatProject'], - ['789dsadafj32jj', 'editPage', 'greatProject'], - ['oi32ncwosap399', 'editPage', 'greatProject'], - ['4r4320dkqpdokk', 'createPage', 'miniProject'], - ['320eljl3lk3239', 'editPage', 'miniProject'], - ['dsj3239asadsa3', 'editPage', 'miniProject'], - ['3203ejew332323', 'createPage', 'private'], - ['3203ejew332323', 'editPage', 'private'], - ['40m11919332msa', 'createPage', 'private2'], - ['40m11919332msa', 'editPage', 'private2'], - ['dsj3239asadsa3', 'createPage', 'anotherGreatProject'], - ['2r09ej221sk2k5', 'editPage', 'anotherGreatProject'], +action_dataframe = pd.DataFrame( + [ + ["dsj3239asadsa3", "createPage", "greatProject"], + ["2r09ej221sk2k5", "editPage", "greatProject"], + ["dsj3239asadsa3", "editPage", "greatProject"], + ["789dsadafj32jj", "editPage", "greatProject"], + ["oi32ncwosap399", "editPage", "greatProject"], + ["4r4320dkqpdokk", "createPage", "miniProject"], + ["320eljl3lk3239", "editPage", "miniProject"], + ["dsj3239asadsa3", "editPage", "miniProject"], + ["3203ejew332323", "createPage", "private"], + ["3203ejew332323", "editPage", "private"], + ["40m11919332msa", "createPage", "private2"], + ["40m11919332msa", "editPage", "private2"], + ["dsj3239asadsa3", "createPage", "anotherGreatProject"], + ["2r09ej221sk2k5", "editPage", "anotherGreatProject"], ], - columns=['userid', 'action', 'project'], + columns=["userid", "action", "project"], ) # %% @@ -42,7 +43,7 @@ # We choose to use a weighted adjacency matrix for this, i.e. a table with rows # and columns indexes by the users that has nonzero entries whenever folks # collaborate. First, let's get the users and prepare an empty matrix: -users = action_dataframe['userid'].unique() +users = action_dataframe["userid"].unique() adjacency_matrix = pd.DataFrame( np.zeros((len(users), len(users)), np.int32), index=users, @@ -51,8 +52,8 @@ # %% # Then, let's iterate over all projects one by one, and add all collaborations: -for _project, project_data in action_dataframe.groupby('project'): - project_users = project_data['userid'].values +for _project, project_data in action_dataframe.groupby("project"): + project_users = project_data["userid"].values for i1, user1 in enumerate(project_users): for user2 in project_users[:i1]: adjacency_matrix.at[user1, user2] += 1 @@ -60,12 +61,12 @@ # %% # There are many ways to achieve the above matrix, so don't be surprised if you # came up with another algorithm ;-) Now it's time to make the graph: -g = ig.Graph.Weighted_Adjacency(adjacency_matrix, mode='plus') +g = ig.Graph.Weighted_Adjacency(adjacency_matrix, mode="plus") # %% # We can take a look at the graph via plotting functions. We can first make a # layout: -layout = g.layout('circle') +layout = g.layout("circle") # %% # Then we can prepare vertex sizes based on their closeness to other vertices @@ -79,7 +80,7 @@ g, target=ax, layout=layout, - vertex_label=g.vs['name'], + vertex_label=g.vs["name"], vertex_color="lightblue", vertex_size=vertex_size, edge_width=g.es["weight"], @@ -89,14 +90,14 @@ # %% # Loops indicate "self-collaborations", which are not very meaningful. To # filter out loops without losing the edge weights, we can use: -g = g.simplify(combine_edges='first') +g = g.simplify(combine_edges="first") fig, ax = plt.subplots() ig.plot( g, target=ax, layout=layout, - vertex_label=g.vs['name'], + vertex_label=g.vs["name"], vertex_color="lightblue", vertex_size=vertex_size, edge_width=g.es["weight"], diff --git a/doc/examples_sphinx-gallery/personalized_pagerank.py b/doc/examples_sphinx-gallery/personalized_pagerank.py new file mode 100644 index 000000000..f7e2f9e2b --- /dev/null +++ b/doc/examples_sphinx-gallery/personalized_pagerank.py @@ -0,0 +1,98 @@ +""" +.. _tutorials-personalized_pagerank: + +=============================== +Personalized PageRank on a grid +=============================== + +This example demonstrates how to calculate and visualize personalized PageRank on a grid. We use the :meth:`igraph.Graph.personalized_pagerank` method, and demonstrate the effects on a grid graph. +""" + +# %% +# .. note:: +# +# The PageRank score of a vertex reflects the probability that a random walker will be at that vertex over the long run. At each step the walker has a 1 - damping chance to restart the walk and pick a starting vertex according to the probabilities defined in the reset vector. + +import igraph as ig +import matplotlib.cm as cm +import matplotlib.pyplot as plt +import numpy as np + +# %% +# We define a function that plots the graph on a Matplotlib axis, along with +# its personalized PageRank values. The function also generates a +# color bar on the side to see how the values change. +# We use `Matplotlib's Normalize class `_ +# to set the colors and ensure that our color bar range is correct. + + +def plot_pagerank(graph: ig.Graph, p_pagerank: list[float]): + """Plots personalized PageRank values on a grid graph with a colorbar. + + Parameters + ---------- + graph : ig.Graph + graph to plot + p_pagerank : list[float] + calculated personalized PageRank values + """ + # Create the axis for matplotlib + _, ax = plt.subplots(figsize=(8, 8)) + + # Create a matplotlib colormap + # coolwarm goes from blue (lowest value) to red (highest value) + cmap = cm.coolwarm + + # Normalize the PageRank values for colormap + normalized_pagerank = ig.rescale(p_pagerank) + + graph.vs["color"] = [cmap(pr) for pr in normalized_pagerank] + graph.vs["size"] = ig.rescale(p_pagerank, (20, 40)) + graph.es["color"] = "gray" + graph.es["width"] = 1.5 + + # Plot the graph + ig.plot(graph, target=ax, layout=graph.layout_grid()) + + # Add a colorbar + sm = cm.ScalarMappable( + norm=plt.Normalize(min(p_pagerank), max(p_pagerank)), cmap=cmap + ) + plt.colorbar(sm, ax=ax, label="Personalized PageRank") + + plt.title("Graph with Personalized PageRank") + plt.axis("equal") + plt.show() + + +# %% +# First, we generate a graph, e.g. a Lattice Graph, which basically is a ``dim x dim`` grid: +dim = 5 +grid_size = (dim, dim) # dim rows, dim columns +g = ig.Graph.Lattice(dim=grid_size, circular=False) + +# %% +# Then we initialize the ``reset_vector`` (it's length should be equal to the number of vertices in the graph): +reset_vector = np.zeros(g.vcount()) + +# %% +# Then we set the nodes to prioritize, for example nodes with indices ``0`` and ``18``: +reset_vector[0] = 1 +reset_vector[18] = 0.65 + +# %% +# Then we calculate the personalized PageRank: +personalized_page_rank = g.personalized_pagerank(damping=0.85, reset=reset_vector) + +# %% +# Finally, we plot the graph with the personalized PageRank values: +plot_pagerank(g, personalized_page_rank) + + +# %% +# Alternatively, we can play around with the ``damping`` parameter: +personalized_page_rank = g.personalized_pagerank(damping=0.45, reset=reset_vector) + +# %% +# Here we can see the same plot with the new damping parameter: +plot_pagerank(g, personalized_page_rank) diff --git a/doc/examples_sphinx-gallery/plot_iplotx.py b/doc/examples_sphinx-gallery/plot_iplotx.py new file mode 100644 index 000000000..5c3c12c43 --- /dev/null +++ b/doc/examples_sphinx-gallery/plot_iplotx.py @@ -0,0 +1,63 @@ +""" +.. _tutorials-iplotx: + +============================== +Visualising graphs with iplotx +============================== +``iplotx`` (https://iplotx.readthedocs.io) is a library for visualisation of graphs/networks +with direct compatibility with both igraph and NetworkX. It uses ``matplotlib`` behind the +scenes so the results are compatible with the current igraph matplotlib backend and many +additional chart types (e.g. bar charts, annotations). + +Compared to the standard visualisations shipped with igraph, ``iplotx`` offers: + +- More styling options +- More consistent behaviour across DPI resolutions and backends +- More consistent matplotlib artists for plot editing and animation + +""" + +import igraph as ig +import iplotx as ipx + +# Construct a graph with 5 vertices +n_vertices = 5 +edges = [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (3, 4)] +g = ig.Graph(n_vertices, edges) + +# Set attributes for the graph, nodes, and edges +g["title"] = "Small Social Network" +g.vs["name"] = [ + "Daniel Morillas", + "Kathy Archer", + "Kyle Ding", + "Joshua Walton", + "Jana Hoyer", +] +g.vs["gender"] = ["M", "F", "F", "M", "F"] +g.es["married"] = [False, False, False, False, False, False, False, True] + +# Set individual attributes +g.vs[1]["name"] = "Kathy Morillas" +g.es[0]["married"] = True + +# Plot using iplotx +ipx.network( + g, + layout="circle", # print nodes in a circular layout + vertex_marker="s", + vertex_size=45, + vertex_linewidth=2, + vertex_facecolor=[ + "lightblue" if gender == "M" else "deeppink" for gender in g.vs["gender"] + ], + vertex_label_color=[ + "black" if gender == "M" else "white" for gender in g.vs["gender"] + ], + vertex_edgecolor="black", + vertex_labels=[name.replace(" ", "\n") for name in g.vs["name"]], + edge_linewidth=[2 if married else 1 for married in g.es["married"]], + edge_color=["#7142cf" if married else "#AAA" for married in g.es["married"]], + edge_padding=3, + aspect=1.0, +) diff --git a/doc/examples_sphinx-gallery/quickstart.py b/doc/examples_sphinx-gallery/quickstart.py index ec6f7ddb7..0735768e5 100644 --- a/doc/examples_sphinx-gallery/quickstart.py +++ b/doc/examples_sphinx-gallery/quickstart.py @@ -15,6 +15,7 @@ To find out more features that igraph has to offer, check out the :ref:`gallery`! """ + import igraph as ig import matplotlib.pyplot as plt @@ -25,7 +26,13 @@ # Set attributes for the graph, nodes, and edges g["title"] = "Small Social Network" -g.vs["name"] = ["Daniel Morillas", "Kathy Archer", "Kyle Ding", "Joshua Walton", "Jana Hoyer"] +g.vs["name"] = [ + "Daniel Morillas", + "Kathy Archer", + "Kyle Ding", + "Joshua Walton", + "Jana Hoyer", +] g.vs["gender"] = ["M", "F", "F", "M", "F"] g.es["married"] = [False, False, False, False, False, False, False, True] @@ -35,27 +42,29 @@ # Plot in matplotlib # Note that attributes can be set globally (e.g. vertex_size), or set individually using arrays (e.g. vertex_color) -fig, ax = plt.subplots(figsize=(5,5)) +fig, ax = plt.subplots(figsize=(5, 5)) ig.plot( g, target=ax, - layout="circle", # print nodes in a circular layout + layout="circle", # print nodes in a circular layout vertex_size=30, - vertex_color=["steelblue" if gender == "M" else "salmon" for gender in g.vs["gender"]], + vertex_color=[ + "steelblue" if gender == "M" else "salmon" for gender in g.vs["gender"] + ], vertex_frame_width=4.0, vertex_frame_color="white", vertex_label=g.vs["name"], vertex_label_size=7.0, edge_width=[2 if married else 1 for married in g.es["married"]], - edge_color=["#7142cf" if married else "#AAA" for married in g.es["married"]] + edge_color=["#7142cf" if married else "#AAA" for married in g.es["married"]], ) plt.show() # Save the graph as an image file -fig.savefig('social_network.png') -fig.savefig('social_network.jpg') -fig.savefig('social_network.pdf') +fig.savefig("social_network.png") +fig.savefig("social_network.jpg") +fig.savefig("social_network.pdf") # Export and import a graph as a GML file. g.save("social_network.gml") diff --git a/doc/examples_sphinx-gallery/ring_animation.py b/doc/examples_sphinx-gallery/ring_animation.py index 25daa2918..33bd6a109 100644 --- a/doc/examples_sphinx-gallery/ring_animation.py +++ b/doc/examples_sphinx-gallery/ring_animation.py @@ -9,6 +9,7 @@ order to animate a ring graph sequentially being revealed. """ + import igraph as ig import matplotlib.pyplot as plt import matplotlib.animation as animation @@ -23,6 +24,7 @@ # Compute a 2D ring layout that looks like an actual ring layout = g.layout_circle() + # %% # Prepare an update function. This "callback" function will be run at every # frame and takes as a single argument the frame number. For simplicity, at @@ -71,6 +73,7 @@ def _update_graph(frame): handles = ax.get_children()[:nhandles] return handles + # %% # Run the animation fig, ax = plt.subplots() diff --git a/doc/examples_sphinx-gallery/shortest_path_visualisation.py b/doc/examples_sphinx-gallery/shortest_path_visualisation.py index 87fa01523..fedad160a 100644 --- a/doc/examples_sphinx-gallery/shortest_path_visualisation.py +++ b/doc/examples_sphinx-gallery/shortest_path_visualisation.py @@ -8,15 +8,13 @@ This example demonstrates how to find the shortest distance between two vertices of a weighted or an unweighted graph. """ + import igraph as ig import matplotlib.pyplot as plt # %% # To find the shortest path or distance between two nodes, we can use :meth:`igraph.GraphBase.get_shortest_paths`. If we're only interested in counting the unweighted distance, then we can do the following: -g = ig.Graph( - 6, - [(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5)] -) +g = ig.Graph(6, [(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5)]) results = g.get_shortest_paths(1, to=4, output="vpath") # results = [[1, 0, 2, 4]] @@ -25,7 +23,7 @@ # We can print the result of the computation: if len(results[0]) > 0: # The distance is the number of vertices in the shortest path minus one. - print("Shortest distance is: ", len(results[0])-1) + print("Shortest distance is: ", len(results[0]) - 1) else: print("End node could not be reached!") @@ -60,20 +58,20 @@ # %% # In case you are wondering how the visualization figure was done, here's the code: -g.es['width'] = 0.5 -g.es[results[0]]['width'] = 2.5 +g.es["width"] = 0.5 +g.es[results[0]]["width"] = 2.5 fig, ax = plt.subplots() ig.plot( g, target=ax, - layout='circle', - vertex_color='steelblue', + layout="circle", + vertex_color="steelblue", vertex_label=range(g.vcount()), - edge_width=g.es['width'], + edge_width=g.es["width"], edge_label=g.es["weight"], - edge_color='#666', + edge_color="#666", edge_align_label=True, - edge_background='white' + edge_background="white", ) plt.show() diff --git a/doc/examples_sphinx-gallery/simplify.py b/doc/examples_sphinx-gallery/simplify.py index 893b67fae..ed36b2da5 100644 --- a/doc/examples_sphinx-gallery/simplify.py +++ b/doc/examples_sphinx-gallery/simplify.py @@ -5,25 +5,28 @@ This example shows how to remove self loops and multiple edges using :meth:`igraph.GraphBase.simplify`. """ + import igraph as ig import matplotlib.pyplot as plt # %% # We start with a graph that includes loops and multiedges: -g1 = ig.Graph([ - (0, 1), - (1, 2), - (2, 3), - (3, 4), - (4, 0), - (0, 0), - (1, 4), - (1, 4), - (0, 2), - (2, 4), - (2, 4), - (2, 4), - (3, 3)], +g1 = ig.Graph( + [ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + (4, 0), + (0, 0), + (1, 4), + (1, 4), + (0, 2), + (2, 4), + (2, 4), + (2, 4), + (3, 3), + ], ) # %% @@ -59,13 +62,29 @@ target=axs[1], **visual_style, ) -axs[0].set_title('Multigraph...') -axs[1].set_title('...simplified') +axs[0].set_title("Multigraph...") +axs[1].set_title("...simplified") # Draw rectangles around axes -axs[0].add_patch(plt.Rectangle( - (0, 0), 1, 1, fc='none', ec='k', lw=4, transform=axs[0].transAxes, - )) -axs[1].add_patch(plt.Rectangle( - (0, 0), 1, 1, fc='none', ec='k', lw=4, transform=axs[1].transAxes, - )) +axs[0].add_patch( + plt.Rectangle( + (0, 0), + 1, + 1, + fc="none", + ec="k", + lw=4, + transform=axs[0].transAxes, + ) +) +axs[1].add_patch( + plt.Rectangle( + (0, 0), + 1, + 1, + fc="none", + ec="k", + lw=4, + transform=axs[1].transAxes, + ) +) plt.show() diff --git a/doc/examples_sphinx-gallery/spanning_trees.py b/doc/examples_sphinx-gallery/spanning_trees.py index 82f190a8a..be98501af 100644 --- a/doc/examples_sphinx-gallery/spanning_trees.py +++ b/doc/examples_sphinx-gallery/spanning_trees.py @@ -7,6 +7,7 @@ This example shows how to generate a spanning tree from an input graph using :meth:`igraph.Graph.spanning_tree`. For the related idea of finding a *minimum spanning tree*, see :ref:`tutorials-minimum-spanning-trees`. """ + import igraph as ig import matplotlib.pyplot as plt import random @@ -29,7 +30,7 @@ g = g.permute_vertices(permutation) new_layout = g.layout("grid") for i in range(36): - new_layout[permutation[i]] = layout[i] + new_layout[i] = layout[permutation[i]] layout = new_layout # %% @@ -47,13 +48,7 @@ g.es[spanning_tree]["width"] = 3.0 fig, ax = plt.subplots() -ig.plot( - g, - target=ax, - layout=layout, - vertex_color="lightblue", - edge_width=g.es["width"] -) +ig.plot(g, target=ax, layout=layout, vertex_color="lightblue", edge_width=g.es["width"]) plt.show() # %% diff --git a/doc/examples_sphinx-gallery/stochastic_variability.py b/doc/examples_sphinx-gallery/stochastic_variability.py new file mode 100644 index 000000000..2afc01153 --- /dev/null +++ b/doc/examples_sphinx-gallery/stochastic_variability.py @@ -0,0 +1,171 @@ +""" +.. _tutorials-stochastic-variability: + +========================================================= +Stochastic Variability in Community Detection Algorithms +========================================================= + +This example demonstrates the use of stochastic community detection methods to check whether a network possesses a strong community structure, and whether the partitionings we obtain are meaningul. Many community detection algorithms are randomized, and return somewhat different results after each run, depending on the random seed that was set. When there is a robust community structure, we expect these results to be similar to each other. When the community structure is weak or non-existent, the results may be noisy and highly variable. We will employ several partion similarity measures to analyse the consistency of the results, including the normalized mutual information (NMI), the variation of information (VI), and the Rand index (RI). + +""" + +# %% +import igraph as ig +import matplotlib.pyplot as plt +import itertools +import random + +# %% +# .. note:: +# We set a random seed to ensure that the results look exactly the same in +# the gallery. You don't need to do this when exploring randomness. +random.seed(42) + +# %% +# We will use Zachary's karate club dataset [1]_, a classic example of a network +# with a strong community structure: +karate = ig.Graph.Famous("Zachary") + +# %% +# We will compare it to an an Erdős-Rényi :math:`G(n, m)` random network having +# the same number of vertices and edges. The parameters 'n' and 'm' refer to the +# vertex and edge count, respectively. Since this is a random network, it should +# have no community structure. +random_graph = ig.Graph.Erdos_Renyi(n=karate.vcount(), m=karate.ecount()) + +# %% +# First, let us plot the two networks for a visual comparison: + +# Create subplots +fig, axes = plt.subplots(1, 2, figsize=(12, 6), subplot_kw={"aspect": "equal"}) + +# Karate club network +ig.plot( + karate, + target=axes[0], + vertex_color="lightblue", + vertex_size=30, + vertex_label=range(karate.vcount()), + vertex_label_size=10, + edge_width=1, +) +axes[0].set_title("Karate club network") + +# Random network +ig.plot( + random_graph, + target=axes[1], + vertex_color="lightcoral", + vertex_size=30, + vertex_label=range(random_graph.vcount()), + vertex_label_size=10, + edge_width=1, +) +axes[1].set_title("Erdős-Rényi random network") + +plt.show() + + +# %% +# Function to compute similarity between partitions using various methods: +def compute_pairwise_similarity(partitions, method): + similarities = [] + + for p1, p2 in itertools.combinations(partitions, 2): + similarity = ig.compare_communities(p1, p2, method=method) + similarities.append(similarity) + + return similarities + + +# %% +# The Leiden method, accessible through :meth:`igraph.Graph.community_leiden()`, +# is a modularity maximization approach for community detection. Since exact +# modularity maximization is NP-hard, the algorithm employs a greedy heuristic +# that processes vertices in a random order. This randomness leads to +# variation in the detected communities across different runs, which is why +# results may differ each time the method is applied. The following function +# runs the Leiden algorithm multiple times: +def run_experiment(graph, iterations=100): + partitions = [ + graph.community_leiden(objective_function="modularity").membership + for _ in range(iterations) + ] + nmi_scores = compute_pairwise_similarity(partitions, method="nmi") + vi_scores = compute_pairwise_similarity(partitions, method="vi") + ri_scores = compute_pairwise_similarity(partitions, method="rand") + return nmi_scores, vi_scores, ri_scores + + +# %% +# Run the experiment on both networks: +nmi_karate, vi_karate, ri_karate = run_experiment(karate) +nmi_random, vi_random, ri_random = run_experiment(random_graph) + +# %% +# Finally, let us plot histograms of the pairwise similarities of the obtained +# partitionings to understand the result: +fig, axes = plt.subplots(2, 3, figsize=(12, 6)) +measures = [ + # Normalized Mutual Information (0-1, higher = more similar) + (nmi_karate, nmi_random, "NMI", 0, 1), + # Variation of Information (0+, lower = more similar) + (vi_karate, vi_random, "VI", 0, max(vi_karate + vi_random)), + # Rand Index (0-1, higher = more similar) + (ri_karate, ri_random, "RI", 0, 1), +] +colors = ["red", "blue", "green"] + +for i, (karate_scores, random_scores, measure, lower, upper) in enumerate(measures): + # Karate club histogram + axes[0][i].hist( + karate_scores, + bins=20, + range=(lower, upper), + density=True, # Probability density + alpha=0.7, + color=colors[i], + edgecolor="black", + ) + axes[0][i].set_title(f"{measure} - Karate club network") + axes[0][i].set_xlabel(f"{measure} score") + axes[0][i].set_ylabel("PDF") + + # Random network histogram + axes[1][i].hist( + random_scores, + bins=20, + range=(lower, upper), + density=True, + alpha=0.7, + color=colors[i], + edgecolor="black", + ) + axes[1][i].set_title(f"{measure} - Random network") + axes[1][i].set_xlabel(f"{measure} score") + axes[0][i].set_ylabel("PDF") + +plt.tight_layout() +plt.show() + +# %% +# We have compared the pairwise similarities using the NMI, VI, and RI measures +# between partitonings obtained for the karate club network (strong community +# structure) and a comparable random graph (which lacks communities). +# +# The Normalized Mutual Information (NMI) and Rand Index (RI) both quantify +# similarity, and take values from :math:`[0,1]`. Higher values indicate more +# similar partitionings, with a value of 1 attained when the partitionings are +# identical. +# +# The Variation of Information (VI) is a distance measure. It takes values from +# :math:`[0,\infty]`, with lower values indicating higher similarities. Identical +# partitionings have a distance of zero. +# +# For the karate club network, NMI and RI value are concentrated near 1, while +# VI is concentrated near 0, suggesting a robust community structure. In contrast +# the values obtained for the random network are much more spread out, showing +# inconsistent partitionings due to the lack of a clear community structure. + +# %% +# .. [1] W. Zachary: "An Information Flow Model for Conflict and Fission in Small Groups". Journal of Anthropological Research 33, no. 4 (1977): 452–73. https://www.jstor.org/stable/3629752 diff --git a/doc/examples_sphinx-gallery/topological_sort.py b/doc/examples_sphinx-gallery/topological_sort.py index 7b0a05481..3c558f90b 100644 --- a/doc/examples_sphinx-gallery/topological_sort.py +++ b/doc/examples_sphinx-gallery/topological_sort.py @@ -7,6 +7,7 @@ This example demonstrates how to get a topological sorting on a directed acyclic graph (DAG). A topological sorting of a directed graph is a linear ordering based on the precedence implied by the directed edges. It exists iff the graph doesn't have any cycle. In ``igraph``, we can use :meth:`igraph.GraphBase.topological_sorting` to get a topological ordering of the vertices. """ + import igraph as ig import matplotlib.pyplot as plt @@ -26,21 +27,21 @@ # A topological sorting can be computed quite easily by calling # :meth:`igraph.GraphBase.topological_sorting`, which returns a list of vertex IDs. # If the given graph is not DAG, the error will occur. -results = g.topological_sorting(mode='out') -print('Topological sort of g (out):', *results) +results = g.topological_sorting(mode="out") +print("Topological sort of g (out):", *results) # %% # In fact, there are two modes of :meth:`igraph.GraphBase.topological_sorting`, # ``'out'`` ``'in'``. ``'out'`` is the default and starts from a node with # indegree equal to 0. Vice versa, ``'in'`` starts from a node with outdegree # equal to 0. To call the other mode, we can simply use: -results = g.topological_sorting(mode='in') -print('Topological sort of g (in):', *results) +results = g.topological_sorting(mode="in") +print("Topological sort of g (in):", *results) # %% # We can use :meth:`igraph.Vertex.indegree` to find the indegree of the node. for i in range(g.vcount()): - print('degree of {}: {}'.format(i, g.vs[i].indegree())) + print("degree of {}: {}".format(i, g.vs[i].indegree())) # % # Finally, we can plot the graph to make the situation a little clearer. @@ -51,7 +52,7 @@ ig.plot( g, target=ax, - layout='kk', + layout="kk", vertex_size=25, edge_width=4, vertex_label=range(g.vcount()), diff --git a/doc/examples_sphinx-gallery/visual_style.py b/doc/examples_sphinx-gallery/visual_style.py index 4ac7d05da..30c27d73b 100644 --- a/doc/examples_sphinx-gallery/visual_style.py +++ b/doc/examples_sphinx-gallery/visual_style.py @@ -6,6 +6,7 @@ This example shows how to change the visual style of network plots. """ + import igraph as ig import matplotlib.pyplot as plt import random @@ -17,7 +18,7 @@ "edge_width": 0.3, "vertex_size": 15, "palette": "heat", - "layout": "fruchterman_reingold" + "layout": "fruchterman_reingold", } # %% diff --git a/doc/examples_sphinx-gallery/visualize_cliques.py b/doc/examples_sphinx-gallery/visualize_cliques.py index 15f149360..c1ebd49d6 100644 --- a/doc/examples_sphinx-gallery/visualize_cliques.py +++ b/doc/examples_sphinx-gallery/visualize_cliques.py @@ -8,12 +8,13 @@ This example shows how to compute and visualize cliques of a graph using :meth:`igraph.GraphBase.cliques`. """ + import igraph as ig import matplotlib.pyplot as plt # %% # First, let's create a graph, for instance the famous karate club graph: -g = ig.Graph.Famous('Zachary') +g = ig.Graph.Famous("Zachary") # %% # Computing cliques can be done as follows: @@ -27,12 +28,13 @@ for clique, ax in zip(cliques, axs): ig.plot( ig.VertexCover(g, [clique]), - mark_groups=True, palette=ig.RainbowPalette(), + mark_groups=True, + palette=ig.RainbowPalette(), vertex_size=5, edge_width=0.5, target=ax, ) -plt.axis('off') +plt.axis("off") plt.show() @@ -45,16 +47,16 @@ axs = axs.ravel() for clique, ax in zip(cliques, axs): # Color vertices yellow/red based on whether they are in this clique - g.vs['color'] = 'yellow' - g.vs[clique]['color'] = 'red' + g.vs["color"] = "yellow" + g.vs[clique]["color"] = "red" # Color edges black/red based on whether they are in this clique clique_edges = g.es.select(_within=clique) - g.es['color'] = 'black' - clique_edges['color'] = 'red' + g.es["color"] = "black" + clique_edges["color"] = "red" # also increase thickness of clique edges - g.es['width'] = 0.3 - clique_edges['width'] = 1 + g.es["width"] = 0.3 + clique_edges["width"] = 1 ig.plot( ig.VertexCover(g, [clique]), @@ -63,5 +65,5 @@ vertex_size=5, target=ax, ) -plt.axis('off') +plt.axis("off") plt.show() diff --git a/doc/examples_sphinx-gallery/visualize_communities.py b/doc/examples_sphinx-gallery/visualize_communities.py index a13edbfa9..9ab696e8e 100644 --- a/doc/examples_sphinx-gallery/visualize_communities.py +++ b/doc/examples_sphinx-gallery/visualize_communities.py @@ -7,6 +7,7 @@ This example shows how to visualize communities or clusters of a graph. """ + import igraph as ig import matplotlib.pyplot as plt @@ -47,7 +48,8 @@ legend_handles = [] for i in range(num_communities): handle = ax.scatter( - [], [], + [], + [], s=100, facecolor=palette.get(i), edgecolor="k", @@ -56,7 +58,7 @@ legend_handles.append(handle) ax.legend( handles=legend_handles, - title='Community:', + title="Community:", bbox_to_anchor=(0, 1.0), bbox_transform=ax.transAxes, ) diff --git a/doc/source/icon.png b/doc/source/icon.png new file mode 100644 index 000000000..0735f005b Binary files /dev/null and b/doc/source/icon.png differ diff --git a/doc/source/icon@2x.png b/doc/source/icon@2x.png new file mode 100644 index 000000000..a9260d97b Binary files /dev/null and b/doc/source/icon@2x.png differ diff --git a/doc/source/install.rst b/doc/source/install.rst index 91fcf6f18..65c985bb9 100644 --- a/doc/source/install.rst +++ b/doc/source/install.rst @@ -131,7 +131,7 @@ Fourth, call ``pip`` to compile and install the package from source:: $ pip install . Alternatively, you can call ``build`` or another PEP 517-compliant build frontend -to build an installable Python wheel. Here we use `pipx `_ +to build an installable Python wheel. Here we use `pipx `_ to invoke ``build`` in a separate virtualenv:: $ pipx run build @@ -140,7 +140,7 @@ Testing your installation ------------------------- Use ``tox`` or another standard test runner tool to run all the unit tests. -Here we use `pipx `_` to invoke ``tox``:: +Here we use `pipx `_ to invoke ``tox``:: $ pipx run tox diff --git a/doc/source/requirements.txt b/doc/source/requirements.txt index 9044d840b..ef825d09f 100644 --- a/doc/source/requirements.txt +++ b/doc/source/requirements.txt @@ -6,6 +6,7 @@ sphinx==7.4.7 sphinx-gallery>=0.14.0 sphinx-rtd-theme>=1.3.0 pydoctor>=23.4.0 +iplotx>=0.6.8 numpy scipy diff --git a/doc/source/sg_execution_times.rst b/doc/source/sg_execution_times.rst deleted file mode 100644 index a63741754..000000000 --- a/doc/source/sg_execution_times.rst +++ /dev/null @@ -1,109 +0,0 @@ - -:orphan: - -.. _sphx_glr_sg_execution_times: - - -Computation times -================= -**00:10.013** total execution time for 25 files **from all galleries**: - -.. container:: - - .. raw:: html - - - - - - - - .. list-table:: - :header-rows: 1 - :class: table table-striped sg-datatable - - * - Example - - Time - - Mem (MB) - * - :ref:`sphx_glr_tutorials_visualize_cliques.py` (``../examples_sphinx-gallery/visualize_cliques.py``) - - 00:02.970 - - 0.0 - * - :ref:`sphx_glr_tutorials_ring_animation.py` (``../examples_sphinx-gallery/ring_animation.py``) - - 00:01.287 - - 0.0 - * - :ref:`sphx_glr_tutorials_cluster_contraction.py` (``../examples_sphinx-gallery/cluster_contraction.py``) - - 00:00.759 - - 0.0 - * - :ref:`sphx_glr_tutorials_betweenness.py` (``../examples_sphinx-gallery/betweenness.py``) - - 00:00.735 - - 0.0 - * - :ref:`sphx_glr_tutorials_visual_style.py` (``../examples_sphinx-gallery/visual_style.py``) - - 00:00.711 - - 0.0 - * - :ref:`sphx_glr_tutorials_delaunay-triangulation.py` (``../examples_sphinx-gallery/delaunay-triangulation.py``) - - 00:00.504 - - 0.0 - * - :ref:`sphx_glr_tutorials_configuration.py` (``../examples_sphinx-gallery/configuration.py``) - - 00:00.416 - - 0.0 - * - :ref:`sphx_glr_tutorials_online_user_actions.py` (``../examples_sphinx-gallery/online_user_actions.py``) - - 00:00.332 - - 0.0 - * - :ref:`sphx_glr_tutorials_erdos_renyi.py` (``../examples_sphinx-gallery/erdos_renyi.py``) - - 00:00.313 - - 0.0 - * - :ref:`sphx_glr_tutorials_connected_components.py` (``../examples_sphinx-gallery/connected_components.py``) - - 00:00.216 - - 0.0 - * - :ref:`sphx_glr_tutorials_complement.py` (``../examples_sphinx-gallery/complement.py``) - - 00:00.201 - - 0.0 - * - :ref:`sphx_glr_tutorials_generate_dag.py` (``../examples_sphinx-gallery/generate_dag.py``) - - 00:00.194 - - 0.0 - * - :ref:`sphx_glr_tutorials_visualize_communities.py` (``../examples_sphinx-gallery/visualize_communities.py``) - - 00:00.176 - - 0.0 - * - :ref:`sphx_glr_tutorials_bridges.py` (``../examples_sphinx-gallery/bridges.py``) - - 00:00.169 - - 0.0 - * - :ref:`sphx_glr_tutorials_spanning_trees.py` (``../examples_sphinx-gallery/spanning_trees.py``) - - 00:00.161 - - 0.0 - * - :ref:`sphx_glr_tutorials_isomorphism.py` (``../examples_sphinx-gallery/isomorphism.py``) - - 00:00.153 - - 0.0 - * - :ref:`sphx_glr_tutorials_quickstart.py` (``../examples_sphinx-gallery/quickstart.py``) - - 00:00.142 - - 0.0 - * - :ref:`sphx_glr_tutorials_minimum_spanning_trees.py` (``../examples_sphinx-gallery/minimum_spanning_trees.py``) - - 00:00.137 - - 0.0 - * - :ref:`sphx_glr_tutorials_simplify.py` (``../examples_sphinx-gallery/simplify.py``) - - 00:00.079 - - 0.0 - * - :ref:`sphx_glr_tutorials_bipartite_matching_maxflow.py` (``../examples_sphinx-gallery/bipartite_matching_maxflow.py``) - - 00:00.073 - - 0.0 - * - :ref:`sphx_glr_tutorials_articulation_points.py` (``../examples_sphinx-gallery/articulation_points.py``) - - 00:00.067 - - 0.0 - * - :ref:`sphx_glr_tutorials_topological_sort.py` (``../examples_sphinx-gallery/topological_sort.py``) - - 00:00.058 - - 0.0 - * - :ref:`sphx_glr_tutorials_bipartite_matching.py` (``../examples_sphinx-gallery/bipartite_matching.py``) - - 00:00.058 - - 0.0 - * - :ref:`sphx_glr_tutorials_shortest_path_visualisation.py` (``../examples_sphinx-gallery/shortest_path_visualisation.py``) - - 00:00.052 - - 0.0 - * - :ref:`sphx_glr_tutorials_maxflow.py` (``../examples_sphinx-gallery/maxflow.py``) - - 00:00.052 - - 0.0 diff --git a/doc/source/visualisation.rst b/doc/source/visualisation.rst index 7cad8bf9b..cf32dbd90 100644 --- a/doc/source/visualisation.rst +++ b/doc/source/visualisation.rst @@ -234,6 +234,6 @@ See the :ref:`tutorial ` for examples and a full list .. _matplotlib: https://matplotlib.org .. _Jupyter: https://jupyter.org/ .. _Cairo: https://www.cairographics.org -.. _graphviz: http://www.graphviz.org +.. _graphviz: https://www.graphviz.org .. _networkx: https://networkx.org/ .. _graph-tool: https://graph-tool.skewed.de/ diff --git a/scripts/fix_pyodide_build.py b/scripts/fix_pyodide_build.py index 46d190518..9e239f973 100644 --- a/scripts/fix_pyodide_build.py +++ b/scripts/fix_pyodide_build.py @@ -4,7 +4,9 @@ from pathlib import Path from urllib.request import urlretrieve -target_dir = Path(pyodide_build.__file__).parent / "tools" / "cmake" / "Modules" / "Platform" +target_dir = ( + Path(pyodide_build.__file__).parent / "tools" / "cmake" / "Modules" / "Platform" +) target_dir.mkdir(exist_ok=True, parents=True) target_file = target_dir / "Emscripten.cmake" diff --git a/scripts/mkdoc.sh b/scripts/mkdoc.sh index db6487d98..9309987f6 100755 --- a/scripts/mkdoc.sh +++ b/scripts/mkdoc.sh @@ -15,29 +15,33 @@ DOC2DASH=0 LINKCHECK=0 CLEAN=0 -while getopts ":scjdl" OPTION; do +while getopts ":cdl" OPTION; do case $OPTION in - c) - CLEAN=1 - ;; - d) - DOC2DASH=1 - ;; - l) - LINKCHECK=1 - ;; - \?) - echo "Usage: $0 [-sjd]" - exit 1 - ;; - esac + c) + CLEAN=1 + ;; + d) + DOC2DASH=1 + ;; + l) + LINKCHECK=1 + ;; + \?) + echo "Usage: $0 [-cdl]" + echo "" + echo "-c: clean and force a full rebuild of the documentation" + echo "-d: generate Dash docset with doc2dash" + echo "-l: check the generated documentation for broken links" + exit 1 + ;; + esac done -shift $((OPTIND -1)) +shift $((OPTIND - 1)) -SCRIPTS_FOLDER=`dirname $0` +SCRIPTS_FOLDER=$(dirname $0) cd ${SCRIPTS_FOLDER}/.. -ROOT_FOLDER=`pwd` +ROOT_FOLDER=$(pwd) DOC_SOURCE_FOLDER=${ROOT_FOLDER}/doc/source DOC_HTML_FOLDER=${ROOT_FOLDER}/doc/html DOC_LINKCHECK_FOLDER=${ROOT_FOLDER}/doc/linkcheck @@ -47,21 +51,27 @@ cd ${ROOT_FOLDER} # Create a virtual environment if [ ! -d ".venv" ]; then - python3 -m venv .venv + echo "Creating virtualenv..." + ${PYTHON:-python3} -m venv .venv - # Install sphinx, matplotlib, pandas, scipy, wheel and pydoctor into the venv. + # Install documentation dependencies into the venv. # doc2dash is optional; it will be installed when -d is given - .venv/bin/pip install -U pip wheel sphinx==7.4.7 matplotlib pandas scipy pydoctor sphinx-rtd-theme + .venv/bin/pip install -q -U pip wheel sphinx==7.4.7 matplotlib pandas scipy pydoctor sphinx-rtd-theme iplotx +else + # Upgrade pip in the virtualenv + echo "Upgrading pip in virtualenv..." + .venv/bin/pip install -q -U pip wheel fi -# Make sure that Sphinx, PyDoctor (and maybe doc2dash) are up-to-date in the virtualenv -.venv/bin/pip install -U sphinx==7.4.7 pydoctor sphinx-gallery sphinxcontrib-jquery sphinx-rtd-theme +# Make sure that documentation dependencies are up-to-date in the virtualenv +echo "Making sure that all dependencies are up-to-date..." +.venv/bin/pip install -q -U sphinx==7.4.7 pydoctor sphinx-gallery sphinxcontrib-jquery sphinx-rtd-theme iplotx if [ x$DOC2DASH = x1 ]; then - .venv/bin/pip install -U doc2dash + .venv/bin/pip install -U doc2dash fi echo "Removing existing igraph and python-igraph eggs from virtualenv..." -SITE_PACKAGES_DIR=`.venv/bin/python3 -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])'` +SITE_PACKAGES_DIR=$(.venv/bin/python3 -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])') rm -rf "${SITE_PACKAGES_DIR}"/igraph*.egg rm -rf "${SITE_PACKAGES_DIR}"/igraph*.egg-link rm -rf "${SITE_PACKAGES_DIR}"/python_igraph*.egg @@ -84,47 +94,47 @@ fi if [ "x$LINKCHECK" = "x1" ]; then echo "Check for broken links" .venv/bin/python -m sphinx \ - -T \ - -b linkcheck \ - -Dtemplates_path='' \ - -Dhtml_theme='alabaster' \ - ${DOC_SOURCE_FOLDER} ${DOC_LINKCHECK_FOLDER} + -T \ + -b linkcheck \ + -Dtemplates_path='' \ + -Dhtml_theme='alabaster' \ + ${DOC_SOURCE_FOLDER} ${DOC_LINKCHECK_FOLDER} fi - echo "Generating HTML documentation..." -.venv/bin/pip install -U sphinx-rtd-theme +.venv/bin/pip install -q -U sphinx-rtd-theme .venv/bin/python -m sphinx -T -b html ${DOC_SOURCE_FOLDER} ${DOC_HTML_FOLDER} echo "HTML documentation generated in ${DOC_HTML_FOLDER}" - # doc2dash if [ "x$DOC2DASH" = "x1" ]; then - PWD=`pwd` + PWD=$(pwd) # Output folder of sphinx (before Jekyll if requested) DOC_API_FOLDER=${ROOT_FOLDER}/doc/html/api - DOC2DASH=`which doc2dash 2>/dev/null || true` + DOC2DASH=.venv/bin/doc2dash DASH_FOLDER=${ROOT_FOLDER}/doc/dash if [ "x$DOC2DASH" != x ]; then - echo "Generating Dash docset..." - "$DOC2DASH" \ - --online-redirect-url "https://igraph.org/python/api/latest" \ - --name "python-igraph" \ - -d "${DASH_FOLDER}" \ - -f \ - -j \ - -I "index.html" \ - "${DOC_API_FOLDER}" - DASH_READY=1 + echo "Generating Dash docset..." + "$DOC2DASH" \ + --online-redirect-url "https://python.igraph.org/en/latest/api/" \ + --name "python-igraph" \ + -d "${DASH_FOLDER}" \ + -f \ + -j \ + -I "index.html" \ + --icon ${ROOT_FOLDER}/doc/source/icon.png \ + --icon-2x ${ROOT_FOLDER}/doc/source/icon@2x.png \ + "${DOC_API_FOLDER}" + DASH_READY=1 else - echo "WARNING: doc2dash not installed, skipping Dash docset generation." - DASH_READY=0 + echo "WARNING: doc2dash not installed, skipping Dash docset generation." + DASH_READY=0 fi echo "" if [ "x${DASH_READY}" = x1 ]; then - echo "Dash docset generated in ${DASH_FOLDER}/python-igraph.docset" + echo "Dash docset generated in ${DASH_FOLDER}/python-igraph.docset" fi cd "$PWD" diff --git a/scripts/patch_modularized_graph_methods.py b/scripts/patch_modularized_graph_methods.py index 9e188b6b5..081ab73a5 100644 --- a/scripts/patch_modularized_graph_methods.py +++ b/scripts/patch_modularized_graph_methods.py @@ -13,7 +13,6 @@ def main(): - # Get instance and classmethods g = igraph.Graph() methods = inspect.getmembers(g, predicate=inspect.ismethod) @@ -43,7 +42,7 @@ def main(): newmodule = igraph.__file__ + ".new" with open(newmodule, "wt") as fout: # FIXME: whitelisting all cases is not great, try to improve - for (origin, value) in auxiliary_imports: + for origin, value in auxiliary_imports: fout.write(f"from {origin} import {value}\n") with open(igraph.__file__, "rt") as f: diff --git a/setup.py b/setup.py index bbd5dbfb1..c919d3507 100644 --- a/setup.py +++ b/setup.py @@ -280,7 +280,7 @@ def _compile_in( args.append("-DIGRAPH_GRAPHML_SUPPORT:BOOL=OFF") # Build the Python interface with vendored libraries - for deps in "ARPACK BLAS GLPK GMP LAPACK".split(): + for deps in "ARPACK BLAS GLPK GMP LAPACK PLFIT".split(): args.append("-DIGRAPH_USE_INTERNAL_" + deps + "=ON") # Use link-time optinization if available @@ -953,7 +953,7 @@ def get_tag(self): Graph plotting functionality is provided by the Cairo library, so make sure you install the Python bindings of Cairo if you want to generate publication-quality graph plots. You can try either `pycairo -`_ or `cairocffi `_, +`_ or `cairocffi `_, ``cairocffi`` is recommended because there were bug reports affecting igraph graph plots in Jupyter notebooks when using ``pycairo`` (but not with ``cairocffi``). @@ -983,7 +983,7 @@ def get_tag(self): "Bug Tracker": "https://github.com/igraph/python-igraph/issues", "Changelog": "https://github.com/igraph/python-igraph/blob/main/CHANGELOG.md", "CI": "https://github.com/igraph/python-igraph/actions", - "Documentation": "https://igraph.readthedocs.io", + "Documentation": "https://python.igraph.org", "Source Code": "https://github.com/igraph/python-igraph", }, "ext_modules": [igraph_extension], @@ -1000,9 +1000,7 @@ def get_tag(self): # Dependencies needed for plotting with Cairo "cairo": ["cairocffi>=1.2.0"], # Dependencies needed for plotting with Matplotlib - "matplotlib": [ - "matplotlib>=3.6.0; platform_python_implementation != 'PyPy'" - ], + "matplotlib": ["matplotlib>=3.6.0; platform_python_implementation != 'PyPy'"], # Dependencies needed for plotting with Plotly "plotly": ["plotly>=5.3.0"], # Compatibility alias to 'cairo' for python-igraph <= 0.9.6 @@ -1020,6 +1018,15 @@ def get_tag(self): "plotly>=5.3.0", "Pillow>=9; platform_python_implementation != 'PyPy'", ], + # Dependencies needed for testing on Windows ARM64; only those that are either + # pure Python or have Windows ARM64 wheels as we don't want to compile wheels + # in CI + "test-win-arm64": [ + "cairocffi>=1.2.0", + "networkx>=2.5", + "pytest>=7.0.1", + "pytest-timeout>=2.1.0", + ], # Dependencies needed for testing on musllinux; only those that are either # pure Python or have musllinux wheels as we don't want to compile wheels # in CI diff --git a/src/_igraph/arpackobject.c b/src/_igraph/arpackobject.c index 86de32363..69d8841ab 100644 --- a/src/_igraph/arpackobject.c +++ b/src/_igraph/arpackobject.c @@ -115,7 +115,7 @@ static PyObject* igraphmodule_ARPACKOptions_getattr( static int igraphmodule_ARPACKOptions_setattr( igraphmodule_ARPACKOptionsObject* self, char* attrname, PyObject* value) { - igraph_integer_t igraph_int; + igraph_int_t igraph_int; if (value == 0) { PyErr_SetString(PyExc_TypeError, "attribute can not be deleted"); diff --git a/src/_igraph/attributes.c b/src/_igraph/attributes.c index d91eb84b5..1bc39a0eb 100644 --- a/src/_igraph/attributes.c +++ b/src/_igraph/attributes.c @@ -183,7 +183,7 @@ int igraphmodule_PyObject_matches_attribute_record(PyObject* object, igraph_attr return 0; } -int igraphmodule_get_vertex_id_by_name(igraph_t *graph, PyObject* o, igraph_integer_t* vid) { +int igraphmodule_get_vertex_id_by_name(igraph_t *graph, PyObject* o, igraph_int_t* vid) { igraphmodule_i_attribute_struct* attrs = ATTR_STRUCT(graph); PyObject* o_vid = NULL; @@ -347,9 +347,11 @@ PyObject* igraphmodule_create_or_get_edge_attribute_values(const igraph_t* graph /* Attribute handlers for the Python interface */ /* Initialization */ -static igraph_error_t igraphmodule_i_attribute_init(igraph_t *graph, igraph_vector_ptr_t *attr) { +static igraph_error_t igraphmodule_i_attribute_init( + igraph_t *graph, const igraph_attribute_record_list_t *attr +) { igraphmodule_i_attribute_struct* attrs; - igraph_integer_t i, n; + igraph_int_t i, n; attrs = (igraphmodule_i_attribute_struct*)calloc(1, sizeof(igraphmodule_i_attribute_struct)); if (!attrs) { @@ -368,27 +370,26 @@ static igraph_error_t igraphmodule_i_attribute_init(igraph_t *graph, igraph_vect PyObject *dict = attrs->attrs[0], *value; const char *s; - n = igraph_vector_ptr_size(attr); + n = igraph_attribute_record_list_size(attr); for (i = 0; i < n; i++) { - igraph_attribute_record_t *attr_rec; - attr_rec = VECTOR(*attr)[i]; + igraph_attribute_record_t *attr_rec = igraph_attribute_record_list_get_ptr(attr, i); switch (attr_rec->type) { case IGRAPH_ATTRIBUTE_NUMERIC: - value = PyFloat_FromDouble((double)VECTOR(*(igraph_vector_t*)attr_rec->value)[0]); + value = PyFloat_FromDouble((double)VECTOR(*attr_rec->value.as_vector)[0]); if (!value) { PyErr_PrintEx(0); } break; case IGRAPH_ATTRIBUTE_STRING: - s = igraph_strvector_get((igraph_strvector_t*)attr_rec->value, 0); + s = igraph_strvector_get(attr_rec->value.as_strvector, 0); value = PyUnicode_FromString(s ? s : ""); if (!value) { PyErr_PrintEx(0); } break; case IGRAPH_ATTRIBUTE_BOOLEAN: - value = VECTOR(*(igraph_vector_bool_t*)attr_rec->value)[0] ? Py_True : Py_False; + value = VECTOR(*attr_rec->value.as_vector_bool)[0] ? Py_True : Py_False; Py_INCREF(value); break; default: @@ -515,10 +516,12 @@ static igraph_error_t igraphmodule_i_attribute_copy(igraph_t *to, const igraph_t } /* Adding vertices */ -static igraph_error_t igraphmodule_i_attribute_add_vertices(igraph_t *graph, igraph_integer_t nv, igraph_vector_ptr_t *attr) { +static igraph_error_t igraphmodule_i_attribute_add_vertices( + igraph_t *graph, igraph_int_t nv, const igraph_attribute_record_list_t *attr +) { /* Extend the end of every value in the vertex hash with nv pieces of None */ PyObject *key, *value, *dict; - igraph_integer_t i, j, k, num_attr_entries; + igraph_int_t i, j, k, num_attr_entries; igraph_attribute_record_t *attr_rec; igraph_vector_bool_t added_attrs; Py_ssize_t pos = 0; @@ -531,7 +534,7 @@ static igraph_error_t igraphmodule_i_attribute_add_vertices(igraph_t *graph, igr return IGRAPH_SUCCESS; } - num_attr_entries = attr ? igraph_vector_ptr_size(attr) : 0; + num_attr_entries = attr ? igraph_attribute_record_list_size(attr) : 0; IGRAPH_VECTOR_BOOL_INIT_FINALLY(&added_attrs, num_attr_entries); dict = ATTR_STRUCT_DICT(graph)[ATTRHASH_IDX_VERTEX]; @@ -547,7 +550,7 @@ static igraph_error_t igraphmodule_i_attribute_add_vertices(igraph_t *graph, igr /* Check if we have specific values for the given attribute */ attr_rec = NULL; for (i = 0; i < num_attr_entries; i++) { - attr_rec = VECTOR(*attr)[i]; + attr_rec = igraph_attribute_record_list_get_ptr(attr, i); if (igraphmodule_PyObject_matches_attribute_record(key, attr_rec)) { VECTOR(added_attrs)[i] = 1; break; @@ -564,14 +567,14 @@ static igraph_error_t igraphmodule_i_attribute_add_vertices(igraph_t *graph, igr switch (attr_rec->type) { case IGRAPH_ATTRIBUTE_NUMERIC: - o = PyFloat_FromDouble((double)VECTOR(*(igraph_vector_t*)attr_rec->value)[i]); + o = PyFloat_FromDouble((double)VECTOR(*attr_rec->value.as_vector)[i]); break; case IGRAPH_ATTRIBUTE_STRING: - s = igraph_strvector_get((igraph_strvector_t*)attr_rec->value, i); + s = igraph_strvector_get(attr_rec->value.as_strvector, i); o = PyUnicode_FromString(s); break; case IGRAPH_ATTRIBUTE_BOOLEAN: - o = VECTOR(*(igraph_vector_bool_t*)attr_rec->value)[i] ? Py_True : Py_False; + o = VECTOR(*attr_rec->value.as_vector_bool)[i] ? Py_True : Py_False; Py_INCREF(o); break; default: @@ -620,7 +623,7 @@ static igraph_error_t igraphmodule_i_attribute_add_vertices(igraph_t *graph, igr continue; } - attr_rec = (igraph_attribute_record_t*)VECTOR(*attr)[k]; + attr_rec = igraph_attribute_record_list_get_ptr(attr, k); value = PyList_New(j + nv); if (!value) { @@ -637,14 +640,14 @@ static igraph_error_t igraphmodule_i_attribute_add_vertices(igraph_t *graph, igr PyObject *o = NULL; switch (attr_rec->type) { case IGRAPH_ATTRIBUTE_NUMERIC: - o = PyFloat_FromDouble((double)VECTOR(*(igraph_vector_t*)attr_rec->value)[i]); + o = PyFloat_FromDouble((double)VECTOR(*attr_rec->value.as_vector)[i]); break; case IGRAPH_ATTRIBUTE_STRING: - s = igraph_strvector_get((igraph_strvector_t*)attr_rec->value, i); + s = igraph_strvector_get(attr_rec->value.as_strvector, i); o = PyUnicode_FromString(s); break; case IGRAPH_ATTRIBUTE_BOOLEAN: - o = VECTOR(*(igraph_vector_bool_t*)attr_rec->value)[i] ? Py_True : Py_False; + o = VECTOR(*attr_rec->value.as_vector_bool)[i] ? Py_True : Py_False; Py_INCREF(o); break; default: @@ -687,7 +690,7 @@ static igraph_error_t igraphmodule_i_attribute_add_vertices(igraph_t *graph, igr /* Permuting vertices */ static igraph_error_t igraphmodule_i_attribute_permute_vertices(const igraph_t *graph, igraph_t *newgraph, const igraph_vector_int_t *idx) { - igraph_integer_t i, n; + igraph_int_t i, n; PyObject *key, *value, *dict, *newdict, *newlist, *o; Py_ssize_t pos = 0; @@ -739,11 +742,13 @@ static igraph_error_t igraphmodule_i_attribute_permute_vertices(const igraph_t * } /* Adding edges */ -static igraph_error_t igraphmodule_i_attribute_add_edges(igraph_t *graph, const igraph_vector_int_t *edges, igraph_vector_ptr_t *attr) { +static igraph_error_t igraphmodule_i_attribute_add_edges( + igraph_t *graph, const igraph_vector_int_t *edges, const igraph_attribute_record_list_t* attr +) { /* Extend the end of every value in the edge hash with ne pieces of None */ PyObject *key, *value, *dict; Py_ssize_t pos = 0; - igraph_integer_t i, j, k, ne, num_attr_entries; + igraph_int_t i, j, k, ne, num_attr_entries; igraph_vector_bool_t added_attrs; igraph_attribute_record_t *attr_rec; @@ -756,7 +761,7 @@ static igraph_error_t igraphmodule_i_attribute_add_edges(igraph_t *graph, const return IGRAPH_SUCCESS; } - num_attr_entries = attr ? igraph_vector_ptr_size(attr) : 0; + num_attr_entries = attr ? igraph_attribute_record_list_size(attr) : 0; IGRAPH_VECTOR_BOOL_INIT_FINALLY(&added_attrs, num_attr_entries); dict = ATTR_STRUCT_DICT(graph)[ATTRHASH_IDX_EDGE]; @@ -772,7 +777,7 @@ static igraph_error_t igraphmodule_i_attribute_add_edges(igraph_t *graph, const /* Check if we have specific values for the given attribute */ attr_rec = NULL; for (i = 0; i < num_attr_entries; i++) { - attr_rec = VECTOR(*attr)[i]; + attr_rec = igraph_attribute_record_list_get_ptr(attr, i); if (igraphmodule_PyObject_matches_attribute_record(key, attr_rec)) { VECTOR(added_attrs)[i] = 1; break; @@ -788,14 +793,14 @@ static igraph_error_t igraphmodule_i_attribute_add_edges(igraph_t *graph, const PyObject *o; switch (attr_rec->type) { case IGRAPH_ATTRIBUTE_NUMERIC: - o = PyFloat_FromDouble((double)VECTOR(*(igraph_vector_t*)attr_rec->value)[i]); + o = PyFloat_FromDouble((double)VECTOR(*attr_rec->value.as_vector)[i]); break; case IGRAPH_ATTRIBUTE_STRING: - s = igraph_strvector_get((igraph_strvector_t*)attr_rec->value, i); + s = igraph_strvector_get(attr_rec->value.as_strvector, i); o = PyUnicode_FromString(s); break; case IGRAPH_ATTRIBUTE_BOOLEAN: - o = VECTOR(*(igraph_vector_bool_t*)attr_rec->value)[i] ? Py_True : Py_False; + o = VECTOR(*attr_rec->value.as_vector_bool)[i] ? Py_True : Py_False; Py_INCREF(o); break; default: @@ -837,7 +842,7 @@ static igraph_error_t igraphmodule_i_attribute_add_edges(igraph_t *graph, const if (VECTOR(added_attrs)[k]) { continue; } - attr_rec=(igraph_attribute_record_t*)VECTOR(*attr)[k]; + attr_rec = igraph_attribute_record_list_get_ptr(attr, k); value = PyList_New(j + ne); if (!value) { @@ -854,14 +859,14 @@ static igraph_error_t igraphmodule_i_attribute_add_edges(igraph_t *graph, const PyObject *o; switch (attr_rec->type) { case IGRAPH_ATTRIBUTE_NUMERIC: - o = PyFloat_FromDouble((double)VECTOR(*(igraph_vector_t*)attr_rec->value)[i]); + o = PyFloat_FromDouble((double)VECTOR(*attr_rec->value.as_vector)[i]); break; case IGRAPH_ATTRIBUTE_STRING: - s = igraph_strvector_get((igraph_strvector_t*)attr_rec->value, i); + s = igraph_strvector_get(attr_rec->value.as_strvector, i); o = PyUnicode_FromString(s); break; case IGRAPH_ATTRIBUTE_BOOLEAN: - o = VECTOR(*(igraph_vector_bool_t*)attr_rec->value)[i] ? Py_True : Py_False; + o = VECTOR(*attr_rec->value.as_vector_bool)[i] ? Py_True : Py_False; Py_INCREF(o); break; default: @@ -899,7 +904,7 @@ static igraph_error_t igraphmodule_i_attribute_add_edges(igraph_t *graph, const /* Permuting edges */ static igraph_error_t igraphmodule_i_attribute_permute_edges(const igraph_t *graph, igraph_t *newgraph, const igraph_vector_int_t *idx) { - igraph_integer_t i, n; + igraph_int_t i, n; PyObject *key, *value, *dict, *newdict, *newlist, *o; Py_ssize_t pos=0; @@ -956,14 +961,14 @@ static igraph_error_t igraphmodule_i_attribute_permute_edges(const igraph_t *gra */ static PyObject* igraphmodule_i_ac_func(PyObject* values, const igraph_vector_int_list_t *merges, PyObject* func) { - igraph_integer_t i, len = igraph_vector_int_list_size(merges); + igraph_int_t i, len = igraph_vector_int_list_size(merges); PyObject *res, *list, *item; res = PyList_New(len); for (i = 0; i < len; i++) { igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(merges, i); - igraph_integer_t j, n = igraph_vector_int_size(v); + igraph_int_t j, n = igraph_vector_int_size(v); list = PyList_New(n); for (j = 0; j < n; j++) { @@ -1039,14 +1044,14 @@ static PyObject* igraphmodule_i_ac_builtin_func(PyObject* values, */ static PyObject* igraphmodule_i_ac_sum(PyObject* values, const igraph_vector_int_list_t *merges) { - igraph_integer_t i, len = igraph_vector_int_list_size(merges); + igraph_int_t i, len = igraph_vector_int_list_size(merges); PyObject *res, *item; res = PyList_New(len); for (i = 0; i < len; i++) { igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(merges, i); igraph_real_t num = 0.0, sum = 0.0; - igraph_integer_t j, n = igraph_vector_int_size(v); + igraph_int_t j, n = igraph_vector_int_size(v); for (j = 0; j < n; j++) { item = PyList_GetItem(values, (Py_ssize_t)VECTOR(*v)[j]); @@ -1082,14 +1087,14 @@ static PyObject* igraphmodule_i_ac_sum(PyObject* values, */ static PyObject* igraphmodule_i_ac_prod(PyObject* values, const igraph_vector_int_list_t *merges) { - igraph_integer_t i, len = igraph_vector_int_list_size(merges); + igraph_int_t i, len = igraph_vector_int_list_size(merges); PyObject *res, *item; res = PyList_New(len); for (i = 0; i < len; i++) { igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(merges, i); igraph_real_t num = 1.0, prod = 1.0; - igraph_integer_t j, n = igraph_vector_int_size(v); + igraph_int_t j, n = igraph_vector_int_size(v); for (j = 0; j < n; j++) { item = PyList_GetItem(values, (Py_ssize_t)VECTOR(*v)[j]); @@ -1126,13 +1131,13 @@ static PyObject* igraphmodule_i_ac_prod(PyObject* values, */ static PyObject* igraphmodule_i_ac_first(PyObject* values, const igraph_vector_int_list_t *merges) { - igraph_integer_t i, len = igraph_vector_int_list_size(merges); + igraph_int_t i, len = igraph_vector_int_list_size(merges); PyObject *res, *item; res = PyList_New(len); for (i = 0; i < len; i++) { igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(merges, i); - igraph_integer_t n = igraph_vector_int_size(v); + igraph_int_t n = igraph_vector_int_size(v); item = n > 0 ? PyList_GetItem(values, (Py_ssize_t)VECTOR(*v)[0]) : Py_None; if (item == 0) { @@ -1159,7 +1164,7 @@ static PyObject* igraphmodule_i_ac_first(PyObject* values, */ static PyObject* igraphmodule_i_ac_random(PyObject* values, const igraph_vector_int_list_t *merges) { - igraph_integer_t i, len = igraph_vector_int_list_size(merges); + igraph_int_t i, len = igraph_vector_int_list_size(merges); PyObject *res, *item, *num; PyObject *random_module = PyImport_ImportModule("random"); PyObject *random_func; @@ -1176,7 +1181,7 @@ static PyObject* igraphmodule_i_ac_random(PyObject* values, res = PyList_New(len); for (i = 0; i < len; i++) { igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(merges, i); - igraph_integer_t n = igraph_vector_int_size(v); + igraph_int_t n = igraph_vector_int_size(v); if (n > 0) { num = PyObject_CallObject(random_func, 0); @@ -1187,7 +1192,7 @@ static PyObject* igraphmodule_i_ac_random(PyObject* values, } item = PyList_GetItem( - values, VECTOR(*v)[(igraph_integer_t)(n * PyFloat_AsDouble(num))] + values, VECTOR(*v)[(igraph_int_t)(n * PyFloat_AsDouble(num))] ); if (item == 0) { Py_DECREF(random_func); @@ -1222,13 +1227,13 @@ static PyObject* igraphmodule_i_ac_random(PyObject* values, */ static PyObject* igraphmodule_i_ac_last(PyObject* values, const igraph_vector_int_list_t *merges) { - igraph_integer_t i, len = igraph_vector_int_list_size(merges); + igraph_int_t i, len = igraph_vector_int_list_size(merges); PyObject *res, *item; res = PyList_New(len); for (i = 0; i < len; i++) { igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(merges, i); - igraph_integer_t n = igraph_vector_int_size(v); + igraph_int_t n = igraph_vector_int_size(v); item = (n > 0) ? PyList_GetItem(values, (Py_ssize_t)VECTOR(*v)[n-1]) : Py_None; if (item == 0) { @@ -1256,14 +1261,14 @@ static PyObject* igraphmodule_i_ac_last(PyObject* values, */ static PyObject* igraphmodule_i_ac_mean(PyObject* values, const igraph_vector_int_list_t *merges) { - igraph_integer_t i, len = igraph_vector_int_list_size(merges); + igraph_int_t i, len = igraph_vector_int_list_size(merges); PyObject *res, *item; res = PyList_New(len); for (i = 0; i < len; i++) { igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(merges, i); igraph_real_t num = 0.0, mean = 0.0; - igraph_integer_t j, n = igraph_vector_int_size(v); + igraph_int_t j, n = igraph_vector_int_size(v); for (j = 0; j < n; ) { item = PyList_GetItem(values, (Py_ssize_t)VECTOR(*v)[j]); @@ -1302,13 +1307,13 @@ static PyObject* igraphmodule_i_ac_mean(PyObject* values, */ static PyObject* igraphmodule_i_ac_median(PyObject* values, const igraph_vector_int_list_t *merges) { - igraph_integer_t i, len = igraph_vector_int_list_size(merges); + igraph_int_t i, len = igraph_vector_int_list_size(merges); PyObject *res, *list, *item; res = PyList_New(len); for (i = 0; i < len; i++) { igraph_vector_int_t *v = igraph_vector_int_list_get_ptr(merges, i); - igraph_integer_t j, n = igraph_vector_int_size(v); + igraph_int_t j, n = igraph_vector_int_size(v); list = PyList_New(n); for (j = 0; j < n; j++) { item = PyList_GetItem(values, (Py_ssize_t)VECTOR(*v)[j]); @@ -1768,15 +1773,15 @@ igraph_error_t igraphmodule_i_get_boolean_graph_attr(const igraph_t *graph, if (!o) { IGRAPH_ERRORF("No boolean graph attribute named \"%s\" exists.", IGRAPH_EINVAL, name); } - IGRAPH_CHECK(igraph_vector_bool_resize(value, 1)); - VECTOR(*value)[0] = PyObject_IsTrue(o); - return IGRAPH_SUCCESS; + return igraph_vector_bool_push_back(value, PyObject_IsTrue(o)); } /* Getting numeric graph attributes */ igraph_error_t igraphmodule_i_get_numeric_graph_attr(const igraph_t *graph, const char *name, igraph_vector_t *value) { PyObject *dict, *o, *result; + double num; + dict = ATTR_STRUCT_DICT(graph)[ATTRHASH_IDX_GRAPH]; /* No error checking, if we get here, the type has already been checked by previous attribute handler calls... hopefully :) Same applies for the other handlers. */ @@ -1784,18 +1789,20 @@ igraph_error_t igraphmodule_i_get_numeric_graph_attr(const igraph_t *graph, if (!o) { IGRAPH_ERRORF("No numeric graph attribute named \"%s\" exists.", IGRAPH_EINVAL, name); } - IGRAPH_CHECK(igraph_vector_resize(value, 1)); + if (o == Py_None) { - VECTOR(*value)[0] = IGRAPH_NAN; - return IGRAPH_SUCCESS; + num = IGRAPH_NAN; + } else { + result = PyNumber_Float(o); + if (result) { + num = PyFloat_AsDouble(o); + Py_DECREF(result); + } else { + IGRAPH_ERROR("Internal error in PyNumber_Float", IGRAPH_EINVAL); + } } - result = PyNumber_Float(o); - if (result) { - VECTOR(*value)[0] = PyFloat_AsDouble(o); - Py_DECREF(result); - } else IGRAPH_ERROR("Internal error in PyFloat_AsDouble", IGRAPH_EINVAL); - return IGRAPH_SUCCESS; + return igraph_vector_push_back(value, num); } /* Getting string graph attributes */ @@ -1809,7 +1816,6 @@ igraph_error_t igraphmodule_i_get_string_graph_attr(const igraph_t *graph, if (!o) { IGRAPH_ERRORF("No string graph attribute named \"%s\" exists.", IGRAPH_EINVAL, name); } - IGRAPH_CHECK(igraph_strvector_resize(value, 1)); /* For Python 3.x, we simply call PyObject_Str, which produces a * Unicode string, then encode it into UTF-8, except when we @@ -1837,7 +1843,7 @@ igraph_error_t igraphmodule_i_get_string_graph_attr(const igraph_t *graph, IGRAPH_ERROR("Internal error in PyBytes_AsString", IGRAPH_EINVAL); } - IGRAPH_CHECK(igraph_strvector_set(value, 0, c_str)); + IGRAPH_CHECK(igraph_strvector_push_back(value, c_str)); Py_XDECREF(str); @@ -1859,16 +1865,19 @@ igraph_error_t igraphmodule_i_get_numeric_vertex_attr(const igraph_t *graph, } if (igraph_vs_is_all(&vs)) { - if (igraphmodule_PyObject_float_to_vector_t(list, &newvalue)) + if (igraphmodule_PyObject_float_to_vector_t(list, &newvalue)) { IGRAPH_ERROR("Internal error", IGRAPH_EINVAL); - igraph_vector_update(value, &newvalue); + } + IGRAPH_FINALLY(igraph_vector_destroy, &newvalue); + IGRAPH_CHECK(igraph_vector_append(value, &newvalue)); + IGRAPH_FINALLY_CLEAN(1); igraph_vector_destroy(&newvalue); } else { igraph_vit_t it; - igraph_integer_t i = 0; + igraph_int_t i = igraph_vector_size(value); IGRAPH_CHECK(igraph_vit_create(graph, vs, &it)); IGRAPH_FINALLY(igraph_vit_destroy, &it); - IGRAPH_CHECK(igraph_vector_resize(value, IGRAPH_VIT_SIZE(it))); + IGRAPH_CHECK(igraph_vector_resize(value, i + IGRAPH_VIT_SIZE(it))); while (!IGRAPH_VIT_END(it)) { o = PyList_GetItem(list, (Py_ssize_t)IGRAPH_VIT_GET(it)); if (o != Py_None) { @@ -1903,18 +1912,21 @@ igraph_error_t igraphmodule_i_get_string_vertex_attr(const igraph_t *graph, } if (igraph_vs_is_all(&vs)) { - if (igraphmodule_PyList_to_strvector_t(list, &newvalue)) + if (igraphmodule_PyList_to_strvector_t(list, &newvalue)) { IGRAPH_ERROR("Internal error", IGRAPH_EINVAL); - igraph_strvector_destroy(value); - *value=newvalue; + } + IGRAPH_FINALLY(igraph_strvector_destroy, &newvalue); + IGRAPH_CHECK(igraph_strvector_append(value, &newvalue)); + IGRAPH_FINALLY_CLEAN(1); + igraph_strvector_destroy(&newvalue); } else { igraph_vit_t it; - igraph_integer_t i = 0; + igraph_int_t i = igraph_strvector_size(value); IGRAPH_CHECK(igraph_vit_create(graph, vs, &it)); IGRAPH_FINALLY(igraph_vit_destroy, &it); - IGRAPH_CHECK(igraph_strvector_resize(value, IGRAPH_VIT_SIZE(it))); + IGRAPH_CHECK(igraph_strvector_resize(value, i + IGRAPH_VIT_SIZE(it))); while (!IGRAPH_VIT_END(it)) { - igraph_integer_t v = IGRAPH_VIT_GET(it); + igraph_int_t v = IGRAPH_VIT_GET(it); char* str; result = PyList_GetItem(list, v); @@ -1922,16 +1934,19 @@ igraph_error_t igraphmodule_i_get_string_vertex_attr(const igraph_t *graph, IGRAPH_ERROR("null element in PyList", IGRAPH_EINVAL); str = igraphmodule_PyObject_ConvertToCString(result); - if (str == 0) + if (str == 0) { IGRAPH_ERROR("error while calling igraphmodule_PyObject_ConvertToCString", IGRAPH_EINVAL); + } + IGRAPH_FINALLY(free, str); /* Note: this is a bit inefficient here, igraphmodule_PyObject_ConvertToCString * allocates a new string which could be copied into the string * vector straight away. Instead of that, the string vector makes * another copy. Probably the performance hit is not too severe. */ - igraph_strvector_set(value, i, str); + IGRAPH_CHECK(igraph_strvector_set(value, i, str)); free(str); + IGRAPH_FINALLY_CLEAN(1); IGRAPH_VIT_NEXT(it); i++; @@ -1958,16 +1973,19 @@ igraph_error_t igraphmodule_i_get_boolean_vertex_attr(const igraph_t *graph, } if (igraph_vs_is_all(&vs)) { - if (igraphmodule_PyObject_to_vector_bool_t(list, &newvalue)) + if (igraphmodule_PyObject_to_vector_bool_t(list, &newvalue)) { IGRAPH_ERROR("Internal error", IGRAPH_EINVAL); - igraph_vector_bool_update(value, &newvalue); + } + IGRAPH_FINALLY(igraph_vector_bool_destroy, &newvalue); + IGRAPH_CHECK(igraph_vector_bool_append(value, &newvalue)); + IGRAPH_FINALLY_CLEAN(1); igraph_vector_bool_destroy(&newvalue); } else { igraph_vit_t it; - igraph_integer_t i = 0; + igraph_int_t i = igraph_vector_bool_size(value); IGRAPH_CHECK(igraph_vit_create(graph, vs, &it)); IGRAPH_FINALLY(igraph_vit_destroy, &it); - IGRAPH_CHECK(igraph_vector_bool_resize(value, IGRAPH_VIT_SIZE(it))); + IGRAPH_CHECK(igraph_vector_bool_resize(value, i + IGRAPH_VIT_SIZE(it))); while (!IGRAPH_VIT_END(it)) { o = PyList_GetItem(list, (Py_ssize_t)IGRAPH_VIT_GET(it)); VECTOR(*value)[i] = PyObject_IsTrue(o); @@ -1996,23 +2014,32 @@ igraph_error_t igraphmodule_i_get_numeric_edge_attr(const igraph_t *graph, } if (igraph_es_is_all(&es)) { - if (igraphmodule_PyObject_float_to_vector_t(list, &newvalue)) + if (igraphmodule_PyObject_float_to_vector_t(list, &newvalue)) { IGRAPH_ERROR("Internal error", IGRAPH_EINVAL); - igraph_vector_update(value, &newvalue); + } + IGRAPH_FINALLY(igraph_vector_destroy, &newvalue); + IGRAPH_CHECK(igraph_vector_append(value, &newvalue)); + IGRAPH_FINALLY_CLEAN(1); igraph_vector_destroy(&newvalue); } else { igraph_eit_t it; - igraph_integer_t i = 0; + igraph_int_t i = igraph_vector_size(value); IGRAPH_CHECK(igraph_eit_create(graph, es, &it)); IGRAPH_FINALLY(igraph_eit_destroy, &it); - IGRAPH_CHECK(igraph_vector_resize(value, IGRAPH_EIT_SIZE(it))); + IGRAPH_CHECK(igraph_vector_resize(value, i + IGRAPH_EIT_SIZE(it))); while (!IGRAPH_EIT_END(it)) { o = PyList_GetItem(list, (Py_ssize_t)IGRAPH_EIT_GET(it)); if (o != Py_None) { result = PyNumber_Float(o); - VECTOR(*value)[i] = PyFloat_AsDouble(result); - Py_XDECREF(result); - } else VECTOR(*value)[i] = IGRAPH_NAN; + if (result) { + VECTOR(*value)[i] = PyFloat_AsDouble(result); + Py_XDECREF(result); + } else { + IGRAPH_ERROR("Internal error in PyNumber_Float", IGRAPH_EINVAL); + } + } else { + VECTOR(*value)[i] = IGRAPH_NAN; + } IGRAPH_EIT_NEXT(it); i++; } @@ -2038,13 +2065,16 @@ igraph_error_t igraphmodule_i_get_string_edge_attr(const igraph_t *graph, } if (igraph_es_is_all(&es)) { - if (igraphmodule_PyList_to_strvector_t(list, &newvalue)) + if (igraphmodule_PyList_to_strvector_t(list, &newvalue)) { IGRAPH_ERROR("Internal error", IGRAPH_EINVAL); - igraph_strvector_destroy(value); - *value=newvalue; + } + IGRAPH_FINALLY(igraph_strvector_destroy, &newvalue); + IGRAPH_CHECK(igraph_strvector_append(value, &newvalue)); + IGRAPH_FINALLY_CLEAN(1); + igraph_strvector_destroy(&newvalue); } else { igraph_eit_t it; - igraph_integer_t i = 0; + igraph_int_t i = igraph_strvector_size(value); IGRAPH_CHECK(igraph_eit_create(graph, es, &it)); IGRAPH_FINALLY(igraph_eit_destroy, &it); IGRAPH_CHECK(igraph_strvector_resize(value, IGRAPH_EIT_SIZE(it))); @@ -2064,7 +2094,9 @@ igraph_error_t igraphmodule_i_get_string_edge_attr(const igraph_t *graph, * vector straight away. Instead of that, the string vector makes * another copy. Probably the performance hit is not too severe. */ - igraph_strvector_set(value, i, str); + IGRAPH_FINALLY(free, str); + IGRAPH_CHECK(igraph_strvector_set(value, i, str)); + IGRAPH_FINALLY_CLEAN(1); free(str); IGRAPH_EIT_NEXT(it); @@ -2092,16 +2124,19 @@ igraph_error_t igraphmodule_i_get_boolean_edge_attr(const igraph_t *graph, } if (igraph_es_is_all(&es)) { - if (igraphmodule_PyObject_to_vector_bool_t(list, &newvalue)) + if (igraphmodule_PyObject_to_vector_bool_t(list, &newvalue)) { IGRAPH_ERROR("Internal error", IGRAPH_EINVAL); - igraph_vector_bool_update(value, &newvalue); + } + IGRAPH_FINALLY(igraph_vector_bool_destroy, &newvalue); + IGRAPH_CHECK(igraph_vector_bool_append(value, &newvalue)); + IGRAPH_FINALLY_CLEAN(1); igraph_vector_bool_destroy(&newvalue); } else { igraph_eit_t it; - igraph_integer_t i=0; + igraph_int_t i = igraph_vector_bool_size(value); IGRAPH_CHECK(igraph_eit_create(graph, es, &it)); IGRAPH_FINALLY(igraph_eit_destroy, &it); - IGRAPH_CHECK(igraph_vector_bool_resize(value, IGRAPH_EIT_SIZE(it))); + IGRAPH_CHECK(igraph_vector_bool_resize(value, i + IGRAPH_EIT_SIZE(it))); while (!IGRAPH_EIT_END(it)) { o = PyList_GetItem(list, (Py_ssize_t)IGRAPH_EIT_GET(it)); VECTOR(*value)[i] = PyObject_IsTrue(o); diff --git a/src/_igraph/attributes.h b/src/_igraph/attributes.h index 6d3da9c90..d02d55239 100644 --- a/src/_igraph/attributes.h +++ b/src/_igraph/attributes.h @@ -83,7 +83,7 @@ int igraphmodule_attribute_name_check(PyObject* obj); void igraphmodule_initialize_attribute_handler(void); void igraphmodule_index_vertex_names(igraph_t *graph, igraph_bool_t force); void igraphmodule_invalidate_vertex_name_index(igraph_t *graph); -int igraphmodule_get_vertex_id_by_name(igraph_t *graph, PyObject* o, igraph_integer_t* id); +int igraphmodule_get_vertex_id_by_name(igraph_t *graph, PyObject* o, igraph_int_t* id); PyObject* igraphmodule_create_or_get_edge_attribute_values(const igraph_t* graph, const char* name); diff --git a/src/_igraph/bfsiter.c b/src/_igraph/bfsiter.c index e0c976245..c8b814f88 100644 --- a/src/_igraph/bfsiter.c +++ b/src/_igraph/bfsiter.c @@ -44,7 +44,7 @@ PyTypeObject* igraphmodule_BFSIterType; */ PyObject* igraphmodule_BFSIter_new(igraphmodule_GraphObject *g, PyObject *root, igraph_neimode_t mode, igraph_bool_t advanced) { igraphmodule_BFSIterObject* self; - igraph_integer_t no_of_nodes, r; + igraph_int_t no_of_nodes, r; self = (igraphmodule_BFSIterObject*) PyType_GenericNew(igraphmodule_BFSIterType, 0, 0); if (!self) { @@ -162,19 +162,19 @@ static PyObject* igraphmodule_BFSIter_iter(igraphmodule_BFSIterObject* self) { static PyObject* igraphmodule_BFSIter_iternext(igraphmodule_BFSIterObject* self) { if (!igraph_dqueue_int_empty(&self->queue)) { - igraph_integer_t vid = igraph_dqueue_int_pop(&self->queue); - igraph_integer_t dist = igraph_dqueue_int_pop(&self->queue); - igraph_integer_t parent = igraph_dqueue_int_pop(&self->queue); - igraph_integer_t i, n; + igraph_int_t vid = igraph_dqueue_int_pop(&self->queue); + igraph_int_t dist = igraph_dqueue_int_pop(&self->queue); + igraph_int_t parent = igraph_dqueue_int_pop(&self->queue); + igraph_int_t i, n; - if (igraph_neighbors(self->graph, &self->neis, vid, self->mode)) { + if (igraph_neighbors(self->graph, &self->neis, vid, self->mode, /* loops = */ 0, /* multiple = */ 0)) { igraphmodule_handle_igraph_error(); return NULL; } n = igraph_vector_int_size(&self->neis); for (i = 0; i < n; i++) { - igraph_integer_t neighbor = VECTOR(self->neis)[i]; + igraph_int_t neighbor = VECTOR(self->neis)[i]; if (self->visited[neighbor] == 0) { self->visited[neighbor] = 1; if (igraph_dqueue_int_push(&self->queue, neighbor) || diff --git a/src/_igraph/common.c b/src/_igraph/common.c index bf888adfc..a099ef030 100644 --- a/src/_igraph/common.c +++ b/src/_igraph/common.c @@ -46,27 +46,3 @@ PyObject* igraphmodule_unimplemented(PyObject* self, PyObject* args, PyObject* k PyErr_SetString(PyExc_NotImplementedError, "This method is unimplemented."); return NULL; } - -/** - * \ingroup python_interface - * \brief Resolves a weak reference to an \c igraph.Graph - * \return the \c igraph.Graph object or NULL if the weak reference is dead. - * Sets an exception in the latter case. - */ -PyObject* igraphmodule_resolve_graph_weakref(PyObject* ref) { - PyObject *o; - -#ifndef PYPY_VERSION - /* PyWeakref_Check is not implemented in PyPy yet */ - if (!PyWeakref_Check(ref)) { - PyErr_SetString(PyExc_TypeError, "weak reference expected"); - return NULL; - } -#endif /* PYPY_VERSION */ - o=PyWeakref_GetObject(ref); - if (o == Py_None) { - PyErr_SetString(PyExc_TypeError, "underlying graph has already been destroyed"); - return NULL; - } - return o; -} diff --git a/src/_igraph/common.h b/src/_igraph/common.h index 77514e2fd..fda85c948 100644 --- a/src/_igraph/common.h +++ b/src/_igraph/common.h @@ -51,5 +51,4 @@ #define ATTRIBUTE_TYPE_EDGE 2 PyObject* igraphmodule_unimplemented(PyObject* self, PyObject* args, PyObject* kwds); -PyObject* igraphmodule_resolve_graph_weakref(PyObject* ref); #endif diff --git a/src/_igraph/convert.c b/src/_igraph/convert.c index 1964d40e1..a36d0558d 100644 --- a/src/_igraph/convert.c +++ b/src/_igraph/convert.c @@ -374,7 +374,7 @@ int igraphmodule_PyObject_to_eigen_which_t(PyObject *o, w->pos = IGRAPH_EIGEN_LM; w->howmany = 1; w->il = w->iu = -1; - w->vl = IGRAPH_NEGINFINITY; + w->vl = -IGRAPH_INFINITY; w->vu = IGRAPH_INFINITY; w->vestimate = 0; w->balance = IGRAPH_LAPACK_DGEEVX_BALANCE_NONE; @@ -710,6 +710,36 @@ int igraphmodule_PyObject_to_loops_t(PyObject *o, igraph_loops_t *result) { TRANSLATE_ENUM_WITH(loops_tt); } +/** + * \brief Converts a Python object to an igraph \c igraph_lpa_variant_t + */ +int igraphmodule_PyObject_to_lpa_variant_t(PyObject *o, igraph_lpa_variant_t *result) { + static igraphmodule_enum_translation_table_entry_t lpa_variant_tt[] = { + {"dominance", IGRAPH_LPA_DOMINANCE}, + {"retention", IGRAPH_LPA_RETENTION}, + {"fast", IGRAPH_LPA_FAST}, + {0,0} + }; + + TRANSLATE_ENUM_WITH(lpa_variant_tt); +} + +/** + * \brief Converts a Python object to an igraph \c igraph_mst_algorithm_t + */ +int igraphmodule_PyObject_to_mst_algorithm_t(PyObject *o, igraph_mst_algorithm_t *result) { + static igraphmodule_enum_translation_table_entry_t mst_algorithm_tt[] = { + {"auto", IGRAPH_MST_AUTOMATIC}, + {"automatic", IGRAPH_MST_AUTOMATIC}, + {"unweighted", IGRAPH_MST_UNWEIGHTED}, + {"prim", IGRAPH_MST_PRIM}, + {"kruskal", IGRAPH_MST_KRUSKAL}, + {0,0} + }; + + TRANSLATE_ENUM_WITH(mst_algorithm_tt); +} + /** * \ingroup python_interface_conversion * \brief Converts a Python object to an igraph \c igraph_random_walk_stuck_t @@ -736,19 +766,6 @@ int igraphmodule_PyObject_to_reciprocity_t(PyObject *o, igraph_reciprocity_t *re TRANSLATE_ENUM_WITH(reciprocity_tt); } -/** - * \brief Converts a Python object to an igraph \c igraph_rewiring_t - */ -int igraphmodule_PyObject_to_rewiring_t(PyObject *o, igraph_rewiring_t *result) { - static igraphmodule_enum_translation_table_entry_t rewiring_tt[] = { - {"simple", IGRAPH_REWIRING_SIMPLE}, - {"simple_loops", IGRAPH_REWIRING_SIMPLE_LOOPS}, - {"loops", IGRAPH_REWIRING_SIMPLE_LOOPS}, - {0,0} - }; - TRANSLATE_ENUM_WITH(rewiring_tt); -} - /** * \brief Converts a Python object to an igraph \c igraphmodule_shortest_path_algorithm_t */ @@ -934,7 +951,7 @@ int igraphmodule_PyObject_to_igraph_t(PyObject *o, igraph_t **result) { } /** - * \brief Converts a PyLong to an igraph \c igraph_integer_t + * \brief Converts a PyLong to an igraph \c igraph_int_t * * Raises suitable Python exceptions when needed. * @@ -945,7 +962,7 @@ int igraphmodule_PyObject_to_igraph_t(PyObject *o, igraph_t **result) { * \param v the result is stored here * \return 0 if everything was OK, 1 otherwise */ -int PyLong_to_integer_t(PyObject* obj, igraph_integer_t* v) { +int PyLong_to_integer_t(PyObject* obj, igraph_int_t* v) { if (IGRAPH_INTEGER_SIZE == 64) { /* here the assumption is that sizeof(long long) == 64 bits; anyhow, this * is the widest integer type that we can convert a PyLong to so we cannot @@ -970,7 +987,7 @@ int PyLong_to_integer_t(PyObject* obj, igraph_integer_t* v) { } /** - * \brief Converts a Python object to an igraph \c igraph_integer_t + * \brief Converts a Python object to an igraph \c igraph_int_t * * Raises suitable Python exceptions when needed. * @@ -978,9 +995,9 @@ int PyLong_to_integer_t(PyObject* obj, igraph_integer_t* v) { * \param v the result is stored here * \return 0 if everything was OK, 1 otherwise */ -int igraphmodule_PyObject_to_integer_t(PyObject *object, igraph_integer_t *v) { +int igraphmodule_PyObject_to_integer_t(PyObject *object, igraph_int_t *v) { int retval; - igraph_integer_t num; + igraph_int_t num; if (object == NULL) { } else if (PyLong_Check(object)) { @@ -1006,6 +1023,60 @@ int igraphmodule_PyObject_to_integer_t(PyObject *object, igraph_integer_t *v) { return 1; } +/** + * \brief Converts a Python object to an igraph \c igraph_int_t when it is + * used as a limit on the number of results for some function. + * + * This is different from \ref igraphmodule_PyObject_to_integer_t such that it + * converts None and positive infinity to \c IGRAPH_UNLIMITED, and it does not + * accept negative values. + * + * Raises suitable Python exceptions when needed. + * + * \param object the Python object to be converted + * \param v the result is stored here + * \return 0 if everything was OK, 1 otherwise + */ +int igraphmodule_PyObject_to_max_results_t(PyObject *object, igraph_int_t *v) { + int retval; + igraph_int_t num; + + if (object != NULL) { + if (object == Py_None) { + *v = IGRAPH_UNLIMITED; + return 0; + } + + if (PyNumber_Check(object)) { + PyObject *flt = PyNumber_Float(object); + if (flt == NULL) { + return 1; + } + + if (PyFloat_AsDouble(flt) == IGRAPH_INFINITY) { + Py_DECREF(flt); + *v = IGRAPH_UNLIMITED; + return 0; + } + + Py_DECREF(flt); + } + } + + retval = igraphmodule_PyObject_to_integer_t(object, &num); + if (retval) { + return retval; + } + + if (num < 0) { + PyErr_SetString(PyExc_ValueError, "expected non-negative integer, None or infinity"); + return 1; + } + + *v = num; + return 0; +} + /** * \brief Converts a Python object to an igraph \c igraph_real_t * @@ -1060,7 +1131,7 @@ int igraphmodule_PyObject_to_vector_t(PyObject *list, igraph_vector_t *v, igraph PyObject *item, *it; Py_ssize_t size_hint; int ok; - igraph_integer_t number; + igraph_int_t number; if (PyBaseString_Check(list)) { /* It is highly unlikely that a string (although it is a sequence) will @@ -1259,7 +1330,7 @@ int igraphmodule_PyObject_float_to_vector_t(PyObject *list, igraph_vector_t *v) */ int igraphmodule_PyObject_to_vector_int_t(PyObject *list, igraph_vector_int_t *v) { PyObject *it = 0, *item; - igraph_integer_t value = 0; + igraph_int_t value = 0; Py_ssize_t i, j, k; int ok; @@ -1421,13 +1492,13 @@ int igraphmodule_PyObject_to_vector_bool_t(PyObject *list, /** * \ingroup python_interface_conversion - * \brief Converts an igraph \c igraph_integer_t to a Python integer + * \brief Converts an igraph \c igraph_int_t to a Python integer * - * \param value the \c igraph_integer_t value to be converted + * \param value the \c igraph_int_t value to be converted * \return the Python integer as a \c PyObject*, or \c NULL if an * error occurred */ -PyObject* igraphmodule_integer_t_to_PyObject(igraph_integer_t value) { +PyObject* igraphmodule_integer_t_to_PyObject(igraph_int_t value) { #if IGRAPH_INTEGER_SIZE == 32 /* minimum size of a long is 32 bits so we are okay */ return PyLong_FromLong(value); @@ -1435,7 +1506,7 @@ PyObject* igraphmodule_integer_t_to_PyObject(igraph_integer_t value) { /* minimum size of a long long is 64 bits so we are okay */ return PyLong_FromLongLong(value); #else -# error "Unknown igraph_integer_t size" +# error "Unknown igraph_int_t size" #endif } @@ -1574,10 +1645,10 @@ PyObject* igraphmodule_vector_int_t_to_PyList(const igraph_vector_int_t *v) { * \param v the \c igraph_vector_int_t containing the vector to be converted * \return the Python integer list as a \c PyObject*, or \c NULL if an error occurred */ -PyObject* igraphmodule_vector_int_t_to_PyList_with_nan(const igraph_vector_int_t *v, const igraph_integer_t nanvalue) { +PyObject* igraphmodule_vector_int_t_to_PyList_with_nan(const igraph_vector_int_t *v, const igraph_int_t nanvalue) { PyObject *list, *item; Py_ssize_t n, i; - igraph_integer_t val; + igraph_int_t val; n = igraph_vector_int_size(v); if (n < 0) { @@ -1758,7 +1829,7 @@ int igraphmodule_PyObject_to_edgelist( ) { PyObject *item, *i1, *i2, *it, *expected; int ok; - igraph_integer_t idx1=0, idx2=0; + igraph_int_t idx1=0, idx2=0; if (PyBaseString_Check(list)) { /* It is highly unlikely that a string (although it is a sequence) will @@ -1773,13 +1844,13 @@ int igraphmodule_PyObject_to_edgelist( * detail that we don't want to commit ourselves to */ if (PyMemoryView_Check(list)) { item = PyObject_GetAttrString(list, "itemsize"); - expected = PyLong_FromSize_t(sizeof(igraph_integer_t)); + expected = PyLong_FromSize_t(sizeof(igraph_int_t)); ok = item && PyObject_RichCompareBool(item, expected, Py_EQ); Py_XDECREF(expected); Py_XDECREF(item); if (!ok) { PyErr_SetString( - PyExc_TypeError, "item size of buffer must match the size of igraph_integer_t" + PyExc_TypeError, "item size of buffer must match the size of igraph_int_t" ); return 1; } @@ -1933,7 +2004,7 @@ int igraphmodule_attrib_to_vector_t(PyObject *o, igraphmodule_GraphObject *self, /* Check whether the attribute exists and is numeric */ igraph_attribute_type_t at; igraph_attribute_elemtype_t et; - igraph_integer_t n; + igraph_int_t n; char *name = PyUnicode_CopyAsString(o); if (attr_type == ATTRIBUTE_TYPE_VERTEX) { @@ -1962,7 +2033,19 @@ int igraphmodule_attrib_to_vector_t(PyObject *o, igraphmodule_GraphObject *self, free(name); return 1; } - igraph_vector_init(result, n); + if (igraph_vector_init(result, 0)) { + igraphmodule_handle_igraph_error(); + free(name); + free(result); + return 1; + } + if (igraph_vector_reserve(result, n)) { + igraphmodule_handle_igraph_error(); + igraph_vector_destroy(result); + free(name); + free(result); + return 1; + } if (attr_type == ATTRIBUTE_TYPE_VERTEX) { if (igraphmodule_i_get_numeric_vertex_attr(&self->g, name, igraph_vss_all(), result)) { @@ -2034,7 +2117,7 @@ int igraphmodule_attrib_to_vector_int_t(PyObject *o, igraphmodule_GraphObject *s if (PyUnicode_Check(o)) { igraph_vector_t* dummy = 0; - igraph_integer_t i, n; + igraph_int_t i, n; if (igraphmodule_attrib_to_vector_t(o, self, &dummy, attr_type)) { return 1; @@ -2062,7 +2145,7 @@ int igraphmodule_attrib_to_vector_int_t(PyObject *o, igraphmodule_GraphObject *s } for (i = 0; i < n; i++) { - VECTOR(*result)[i] = (igraph_integer_t) VECTOR(*dummy)[i]; + VECTOR(*result)[i] = (igraph_int_t) VECTOR(*dummy)[i]; } igraph_vector_destroy(dummy); free(dummy); @@ -2127,7 +2210,7 @@ int igraphmodule_attrib_to_vector_bool_t(PyObject *o, igraphmodule_GraphObject * return 0; if (PyUnicode_Check(o)) { - igraph_integer_t i, n; + igraph_int_t i, n; /* First, check if the attribute is a "real" boolean */ igraph_attribute_type_t at; @@ -2157,7 +2240,19 @@ int igraphmodule_attrib_to_vector_bool_t(PyObject *o, igraphmodule_GraphObject * free(name); return 1; } - igraph_vector_bool_init(result, n); + if (igraph_vector_bool_init(result, 0)) { + igraphmodule_handle_igraph_error(); + free(name); + free(result); + return 1; + } + if (igraph_vector_bool_reserve(result, n)) { + igraph_vector_bool_destroy(result); + igraphmodule_handle_igraph_error(); + free(name); + free(result); + return 1; + } if (attr_type == ATTRIBUTE_TYPE_VERTEX) { if (igraphmodule_i_get_boolean_vertex_attr(&self->g, name, igraph_vss_all(), result)) { @@ -2193,12 +2288,17 @@ int igraphmodule_attrib_to_vector_bool_t(PyObject *o, igraphmodule_GraphObject * n = igraph_vector_size(dummy); result = (igraph_vector_bool_t*)calloc(1, sizeof(igraph_vector_bool_t)); - igraph_vector_bool_init(result, n); if (result == 0) { igraph_vector_destroy(dummy); free(dummy); PyErr_NoMemory(); return 1; } + if (igraph_vector_bool_init(result, n)) { + igraphmodule_handle_igraph_error(); + igraph_vector_destroy(dummy); free(dummy); + igraph_vector_bool_destroy(result); free(result); + return 1; + } for (i = 0; i < n; i++) { VECTOR(*result)[i] = (VECTOR(*dummy)[i] != 0 && VECTOR(*dummy)[i] == VECTOR(*dummy)[i]); @@ -2390,6 +2490,40 @@ PyObject* igraphmodule_matrix_int_t_to_PyList(const igraph_matrix_int_t *m) { return list; } +/** + * \ingroup python_interface_conversion + * \brief Converts an igraph \c igraph_matrix_list_t to a Python list of lists of lists + * + * \param v the \c igraph_matrix_list_t containing the matrix list to be converted + * \return the Python list as a \c PyObject*, or \c NULL if an error occurred + */ +PyObject* igraphmodule_matrix_list_t_to_PyList(const igraph_matrix_list_t *m) { + PyObject *list, *item; + Py_ssize_t n, i; + + n = igraph_matrix_list_size(m); + if (n < 0) { + return igraphmodule_handle_igraph_error(); + } + + list = PyList_New(n); + if (!list) { + return NULL; + } + + for (i = 0; i < n; i++) { + item = igraphmodule_matrix_t_to_PyList(igraph_matrix_list_get_ptr(m, i), + IGRAPHMODULE_TYPE_FLOAT); + if (item == NULL) { + Py_DECREF(list); + return NULL; + } + PyList_SetItem(list, i, item); /* will not fail */ + } + + return list; +} + /** * \ingroup python_interface_conversion * \brief Converts an igraph \c igraph_vector_ptr_t to a Python list of lists @@ -2750,7 +2884,7 @@ int igraphmodule_PyObject_to_matrix_int_t_with_minimum_column_count( ) { Py_ssize_t nr, nc, n, i, j; PyObject *row, *item; - igraph_integer_t value; + igraph_int_t value; /* calculate the matrix dimensions */ if (!PySequence_Check(o) || PyUnicode_Check(o)) { @@ -3269,7 +3403,7 @@ int igraphmodule_append_PyIter_of_graphs_to_vector_ptr_t_with_type(PyObject *it, * if we don't need name lookups. * \return 0 if everything was OK, 1 otherwise */ -int igraphmodule_PyObject_to_vid(PyObject *o, igraph_integer_t *vid, igraph_t *graph) { +int igraphmodule_PyObject_to_vid(PyObject *o, igraph_int_t *vid, igraph_t *graph) { if (o == 0) { PyErr_SetString(PyExc_TypeError, "only non-negative integers, strings or igraph.Vertex objects can be converted to vertex IDs"); return 1; @@ -3328,7 +3462,7 @@ int igraphmodule_PyObject_to_vid(PyObject *o, igraph_integer_t *vid, igraph_t *g * if we don't need name lookups. * \return 0 if everything was OK, 1 otherwise */ -int igraphmodule_PyObject_to_optional_vid(PyObject *o, igraph_integer_t *vid, igraph_t *graph) { +int igraphmodule_PyObject_to_optional_vid(PyObject *o, igraph_int_t *vid, igraph_t *graph) { if (o == 0 || o == Py_None) { return 0; } else { @@ -3350,7 +3484,7 @@ int igraphmodule_PyObject_to_optional_vid(PyObject *o, igraph_integer_t *vid, ig int igraphmodule_PyObject_to_vid_list(PyObject* o, igraph_vector_int_t* result, igraph_t* graph) { PyObject *iterator; PyObject *item; - igraph_integer_t vid; + igraph_int_t vid; if (PyBaseString_Check(o)) { /* exclude strings; they are iterable but cannot yield meaningful vertex IDs */ @@ -3414,8 +3548,8 @@ int igraphmodule_PyObject_to_vid_list(PyObject* o, igraph_vector_int_t* result, * \return 0 if everything was OK, 1 otherwise */ int igraphmodule_PyObject_to_vs_t(PyObject *o, igraph_vs_t *vs, - igraph_t *graph, igraph_bool_t *return_single, igraph_integer_t *single_vid) { - igraph_integer_t vid; + igraph_t *graph, igraph_bool_t *return_single, igraph_int_t *single_vid) { + igraph_int_t vid; igraph_vector_int_t vector; if (o == 0 || o == Py_None) { @@ -3532,9 +3666,9 @@ int igraphmodule_PyObject_to_vs_t(PyObject *o, igraph_vs_t *vs, * if we don't want to handle tuples. * \return 0 if everything was OK, 1 otherwise */ -int igraphmodule_PyObject_to_eid(PyObject *o, igraph_integer_t *eid, igraph_t *graph) { +int igraphmodule_PyObject_to_eid(PyObject *o, igraph_int_t *eid, igraph_t *graph) { int retval; - igraph_integer_t vid1, vid2; + igraph_int_t vid1, vid2; if (!o) { PyErr_SetString(PyExc_TypeError, @@ -3636,7 +3770,7 @@ int igraphmodule_PyObject_to_eid(PyObject *o, igraph_integer_t *eid, igraph_t *g */ int igraphmodule_PyObject_to_es_t(PyObject *o, igraph_es_t *es, igraph_t *graph, igraph_bool_t *return_single) { - igraph_integer_t eid; + igraph_int_t eid; igraph_vector_int_t vector; if (o == 0 || o == Py_None) { @@ -4023,6 +4157,21 @@ int igraphmodule_PyObject_to_pagerank_algo_t(PyObject *o, igraph_pagerank_algo_t TRANSLATE_ENUM_WITH(pagerank_algo_tt); } +/** + * \ingroup python_interface_conversion + * \brief Converts a Python object to an igraph \c igraph_metric_t + */ +int igraphmodule_PyObject_to_metric_t(PyObject *o, igraph_metric_t *result) { + static igraphmodule_enum_translation_table_entry_t metric_tt[] = { + {"euclidean", IGRAPH_METRIC_EUCLIDEAN}, + {"l2", IGRAPH_METRIC_L2}, /* alias to the previous */ + {"manhattan", IGRAPH_METRIC_MANHATTAN}, + {"l1", IGRAPH_METRIC_L1}, /* alias to the previous */ + {0,0} + }; + TRANSLATE_ENUM_WITH(metric_tt); +} + /** * \ingroup python_interface_conversion * \brief Converts a Python object to an igraph \c igraph_edge_type_sw_t diff --git a/src/_igraph/convert.h b/src/_igraph/convert.h index 873be1dc0..39fbf5619 100644 --- a/src/_igraph/convert.h +++ b/src/_igraph/convert.h @@ -76,7 +76,10 @@ int igraphmodule_PyObject_to_fvs_algorithm_t(PyObject *o, igraph_fvs_algorithm_t int igraphmodule_PyObject_to_get_adjacency_t(PyObject *o, igraph_get_adjacency_t *result); int igraphmodule_PyObject_to_laplacian_normalization_t(PyObject *o, igraph_laplacian_normalization_t *result); int igraphmodule_PyObject_to_layout_grid_t(PyObject *o, igraph_layout_grid_t *result); +int igraphmodule_PyObject_to_lpa_variant_t(PyObject *o, igraph_lpa_variant_t *result); int igraphmodule_PyObject_to_loops_t(PyObject *o, igraph_loops_t *result); +int igraphmodule_PyObject_to_metric_t(PyObject *o, igraph_metric_t *result); +int igraphmodule_PyObject_to_mst_algorithm_t(PyObject *o, igraph_mst_algorithm_t *result); int igraphmodule_PyObject_to_neimode_t(PyObject *o, igraph_neimode_t *result); int igraphmodule_PyObject_to_pagerank_algo_t(PyObject *o, igraph_pagerank_algo_t *result); int igraphmodule_PyObject_to_edge_type_sw_t(PyObject *o, igraph_edge_type_sw_t *result); @@ -84,7 +87,6 @@ int igraphmodule_PyObject_to_realize_degseq_t(PyObject *o, igraph_realize_degseq int igraphmodule_PyObject_to_random_tree_t(PyObject *o, igraph_random_tree_t *result); int igraphmodule_PyObject_to_random_walk_stuck_t(PyObject *o, igraph_random_walk_stuck_t *result); int igraphmodule_PyObject_to_reciprocity_t(PyObject *o, igraph_reciprocity_t *result); -int igraphmodule_PyObject_to_rewiring_t(PyObject *o, igraph_rewiring_t *result); int igraphmodule_PyObject_to_shortest_path_algorithm_t(PyObject *o, igraphmodule_shortest_path_algorithm_t *result); int igraphmodule_PyObject_to_spinglass_implementation_t(PyObject *o, igraph_spinglass_implementation_t *result); int igraphmodule_PyObject_to_spincomm_update_t(PyObject *o, igraph_spincomm_update_t *result); @@ -98,10 +100,12 @@ int igraphmodule_PyObject_to_vconn_nei_t(PyObject *o, igraph_vconn_nei_t *result /* Conversion from PyObject to igraph types */ -int igraphmodule_PyObject_to_integer_t(PyObject *object, igraph_integer_t *v); +int igraphmodule_PyObject_to_integer_t(PyObject *object, igraph_int_t *v); int igraphmodule_PyObject_to_real_t(PyObject *object, igraph_real_t *v); int igraphmodule_PyObject_to_igraph_t(PyObject *o, igraph_t **result); +int igraphmodule_PyObject_to_max_results_t(PyObject *object, igraph_int_t *v); + int igraphmodule_PyObject_to_vector_t(PyObject *list, igraph_vector_t *v, igraph_bool_t need_non_negative); int igraphmodule_PyObject_float_to_vector_t(PyObject *list, igraph_vector_t *v); @@ -134,14 +138,14 @@ int igraphmodule_append_PyIter_of_graphs_to_vector_ptr_t(PyObject *it, igraph_vector_ptr_t *v); int igraphmodule_append_PyIter_of_graphs_to_vector_ptr_t_with_type(PyObject *it, igraph_vector_ptr_t *v, PyTypeObject **g_type); -int igraphmodule_PyObject_to_vid(PyObject *o, igraph_integer_t *vid, igraph_t *graph); -int igraphmodule_PyObject_to_optional_vid(PyObject *o, igraph_integer_t *vid, igraph_t *graph); +int igraphmodule_PyObject_to_vid(PyObject *o, igraph_int_t *vid, igraph_t *graph); +int igraphmodule_PyObject_to_optional_vid(PyObject *o, igraph_int_t *vid, igraph_t *graph); int igraphmodule_PyObject_to_vid_list(PyObject *o, igraph_vector_int_t *vids, igraph_t *graph); int igraphmodule_PyObject_to_vs_t( PyObject *o, igraph_vs_t *vs, igraph_t *graph, - igraph_bool_t *return_single, igraph_integer_t *single_vid + igraph_bool_t *return_single, igraph_int_t *single_vid ); -int igraphmodule_PyObject_to_eid(PyObject *o, igraph_integer_t *eid, igraph_t *graph); +int igraphmodule_PyObject_to_eid(PyObject *o, igraph_int_t *eid, igraph_t *graph); int igraphmodule_PyObject_to_es_t(PyObject *o, igraph_es_t *es, igraph_t *graph, igraph_bool_t *return_single); int igraphmodule_PyObject_to_attribute_values(PyObject *o, @@ -170,7 +174,7 @@ int igraphmodule_attrib_to_vector_bool_t(PyObject *o, igraphmodule_GraphObject * /* Conversion from igraph types to PyObjects */ -PyObject* igraphmodule_integer_t_to_PyObject(igraph_integer_t value); +PyObject* igraphmodule_integer_t_to_PyObject(igraph_int_t value); PyObject* igraphmodule_real_t_to_PyObject(igraph_real_t value, igraphmodule_conv_t type); PyObject* igraphmodule_vector_bool_t_to_PyList(const igraph_vector_bool_t *v); @@ -181,7 +185,7 @@ PyObject* igraphmodule_vector_int_t_pair_to_PyList(const igraph_vector_int_t *v1 const igraph_vector_int_t *v2); PyObject* igraphmodule_vector_int_t_to_PyList_of_fixed_length_tuples( const igraph_vector_int_t *v, Py_ssize_t tuple_len); -PyObject* igraphmodule_vector_int_t_to_PyList_with_nan(const igraph_vector_int_t *v, const igraph_integer_t nanvalue); +PyObject* igraphmodule_vector_int_t_to_PyList_with_nan(const igraph_vector_int_t *v, const igraph_int_t nanvalue); PyObject* igraphmodule_vector_ptr_t_to_PyList(const igraph_vector_ptr_t *v, igraphmodule_conv_t type); PyObject* igraphmodule_vector_int_ptr_t_to_PyList(const igraph_vector_ptr_t *v); @@ -193,4 +197,5 @@ PyObject* igraphmodule_vector_int_t_to_PyList(const igraph_vector_int_t *v); PyObject* igraphmodule_matrix_t_to_PyList(const igraph_matrix_t *m, igraphmodule_conv_t type); PyObject* igraphmodule_matrix_int_t_to_PyList(const igraph_matrix_int_t *m); +PyObject* igraphmodule_matrix_list_t_to_PyList(const igraph_matrix_list_t *m); #endif diff --git a/src/_igraph/dfsiter.c b/src/_igraph/dfsiter.c index e8273173c..08a7fedea 100644 --- a/src/_igraph/dfsiter.c +++ b/src/_igraph/dfsiter.c @@ -44,7 +44,7 @@ PyTypeObject* igraphmodule_DFSIterType; */ PyObject* igraphmodule_DFSIter_new(igraphmodule_GraphObject *g, PyObject *root, igraph_neimode_t mode, igraph_bool_t advanced) { igraphmodule_DFSIterObject* self; - igraph_integer_t no_of_nodes, r; + igraph_int_t no_of_nodes, r; self = (igraphmodule_DFSIterObject*) PyType_GenericNew(igraphmodule_DFSIterType, 0, 0); if (!self) { @@ -162,7 +162,7 @@ static PyObject* igraphmodule_DFSIter_iter(igraphmodule_DFSIterObject* self) { static PyObject* igraphmodule_DFSIter_iternext(igraphmodule_DFSIterObject* self) { /* the design is to return the top of the stack and then proceed until * we have found an unvisited neighbor and push that on top */ - igraph_integer_t parent_out, dist_out, vid_out; + igraph_int_t parent_out, dist_out, vid_out; igraph_bool_t any = false; /* nothing on the stack, end of iterator */ @@ -181,25 +181,25 @@ static PyObject* igraphmodule_DFSIter_iternext(igraphmodule_DFSIterObject* self) /* look for neighbors until we find one or until we have exhausted the graph */ while (!any && !igraph_stack_int_empty(&self->stack)) { - igraph_integer_t parent = igraph_stack_int_pop(&self->stack); - igraph_integer_t dist = igraph_stack_int_pop(&self->stack); - igraph_integer_t vid = igraph_stack_int_pop(&self->stack); + igraph_int_t parent = igraph_stack_int_pop(&self->stack); + igraph_int_t dist = igraph_stack_int_pop(&self->stack); + igraph_int_t vid = igraph_stack_int_pop(&self->stack); igraph_stack_int_push(&self->stack, vid); igraph_stack_int_push(&self->stack, dist); igraph_stack_int_push(&self->stack, parent); - igraph_integer_t i, n; + igraph_int_t i, n; /* the values above are returned at at this stage. However, we must * prepare for the next iteration by putting the next unvisited * neighbor onto the stack */ - if (igraph_neighbors(self->graph, &self->neis, vid, self->mode)) { + if (igraph_neighbors(self->graph, &self->neis, vid, self->mode, /* loops = */ 0, /* multiple = */ 0)) { igraphmodule_handle_igraph_error(); return NULL; } n = igraph_vector_int_size(&self->neis); for (i = 0; i < n; i++) { - igraph_integer_t neighbor = VECTOR(self->neis)[i]; + igraph_int_t neighbor = VECTOR(self->neis)[i]; /* new neighbor, push the next item onto the stack */ if (self->visited[neighbor] == 0) { any = 1; diff --git a/src/_igraph/edgeobject.c b/src/_igraph/edgeobject.c index ca26c7009..0f5e54171 100644 --- a/src/_igraph/edgeobject.c +++ b/src/_igraph/edgeobject.c @@ -54,7 +54,7 @@ int igraphmodule_Edge_Check(PyObject* obj) { * exception and returns zero if the edge object is invalid. */ int igraphmodule_Edge_Validate(PyObject* obj) { - igraph_integer_t n; + igraph_int_t n; igraphmodule_EdgeObject *self; igraphmodule_GraphObject *graph; @@ -98,7 +98,7 @@ int igraphmodule_Edge_Validate(PyObject* obj) { * changes, your existing edge objects will point to elsewhere * (or they might even get invalidated). */ -PyObject* igraphmodule_Edge_New(igraphmodule_GraphObject *gref, igraph_integer_t idx) { +PyObject* igraphmodule_Edge_New(igraphmodule_GraphObject *gref, igraph_int_t idx) { return PyObject_CallFunction((PyObject*) igraphmodule_EdgeType, "On", gref, (Py_ssize_t) idx); } @@ -110,7 +110,7 @@ PyObject* igraphmodule_Edge_New(igraphmodule_GraphObject *gref, igraph_integer_t static int igraphmodule_Edge_init(igraphmodule_EdgeObject *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = { "graph", "eid", NULL }; PyObject *g, *index_o = Py_None; - igraph_integer_t eid; + igraph_int_t eid; igraph_t *graph; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|O", kwlist, @@ -400,7 +400,7 @@ int igraphmodule_Edge_set_attribute(igraphmodule_EdgeObject* self, PyObject* k, /* result is NULL, check whether there was an error */ if (!PyErr_Occurred()) { /* no, there wasn't, so we must simply add the attribute */ - igraph_integer_t i, n = igraph_ecount(&o->g); + igraph_int_t i, n = igraph_ecount(&o->g); result = PyList_New(n); for (i = 0; i < n; i++) { if (i != self->idx) { @@ -437,7 +437,7 @@ int igraphmodule_Edge_set_attribute(igraphmodule_EdgeObject* self, PyObject* k, */ PyObject* igraphmodule_Edge_get_from(igraphmodule_EdgeObject* self, void* closure) { igraphmodule_GraphObject *o = self->gref; - igraph_integer_t from, to; + igraph_int_t from, to; if (!igraphmodule_Edge_Validate((PyObject*)self)) { return NULL; @@ -456,7 +456,7 @@ PyObject* igraphmodule_Edge_get_from(igraphmodule_EdgeObject* self, void* closur */ PyObject* igraphmodule_Edge_get_source_vertex(igraphmodule_EdgeObject* self, void* closure) { igraphmodule_GraphObject *o = self->gref; - igraph_integer_t from, to; + igraph_int_t from, to; if (!igraphmodule_Edge_Validate((PyObject*)self)) return NULL; @@ -474,7 +474,7 @@ PyObject* igraphmodule_Edge_get_source_vertex(igraphmodule_EdgeObject* self, voi */ PyObject* igraphmodule_Edge_get_to(igraphmodule_EdgeObject* self, void* closure) { igraphmodule_GraphObject *o = self->gref; - igraph_integer_t from, to; + igraph_int_t from, to; if (!igraphmodule_Edge_Validate((PyObject*)self)) { return NULL; @@ -493,7 +493,7 @@ PyObject* igraphmodule_Edge_get_to(igraphmodule_EdgeObject* self, void* closure) */ PyObject* igraphmodule_Edge_get_target_vertex(igraphmodule_EdgeObject* self, void* closure) { igraphmodule_GraphObject *o = self->gref; - igraph_integer_t from, to; + igraph_int_t from, to; if (!igraphmodule_Edge_Validate((PyObject*)self)) return NULL; @@ -515,9 +515,9 @@ PyObject* igraphmodule_Edge_get_index(igraphmodule_EdgeObject* self, void* closu /** * \ingroup python_interface_edge - * Returns the edge index as an igraph_integer_t + * Returns the edge index as an igraph_int_t */ -igraph_integer_t igraphmodule_Edge_get_index_as_igraph_integer(igraphmodule_EdgeObject* self) { +igraph_int_t igraphmodule_Edge_get_index_as_igraph_integer(igraphmodule_EdgeObject* self) { return self->idx; } @@ -527,7 +527,7 @@ igraph_integer_t igraphmodule_Edge_get_index_as_igraph_integer(igraphmodule_Edge */ PyObject* igraphmodule_Edge_get_tuple(igraphmodule_EdgeObject* self, void* closure) { igraphmodule_GraphObject *o = self->gref; - igraph_integer_t from, to; + igraph_int_t from, to; PyObject *from_o, *to_o, *result; if (!igraphmodule_Edge_Validate((PyObject*)self)) { @@ -562,7 +562,7 @@ PyObject* igraphmodule_Edge_get_tuple(igraphmodule_EdgeObject* self, void* closu */ PyObject* igraphmodule_Edge_get_vertex_tuple(igraphmodule_EdgeObject* self, void* closure) { igraphmodule_GraphObject *o = self->gref; - igraph_integer_t from, to; + igraph_int_t from, to; PyObject *from_o, *to_o; if (!igraphmodule_Edge_Validate((PyObject*)self)) @@ -724,6 +724,15 @@ PyDoc_STRVAR( " >>> e[\"weight\"] = 2 #doctest: +SKIP\n" " >>> print(e[\"weight\"]) #doctest: +SKIP\n" " 2\n" + "\n" + "@ivar source: Source vertex index of this edge\n" + "@ivar source_vertex: Source vertex of this edge\n" + "@ivar target: Target vertex index of this edge\n" + "@ivar target_vertex: Target vertex of this edge\n" + "@ivar tuple: Source and target vertex index of this edge as a tuple\n" + "@ivar vertex_tuple: Source and target vertex of this edge as a tuple\n" + "@ivar index: Index of this edge\n" + "@ivar graph: The graph the edge belongs to\t" ); int igraphmodule_Edge_register_type() { diff --git a/src/_igraph/edgeobject.h b/src/_igraph/edgeobject.h index f42862fe3..039a6e20f 100644 --- a/src/_igraph/edgeobject.h +++ b/src/_igraph/edgeobject.h @@ -34,7 +34,7 @@ typedef struct { PyObject_HEAD igraphmodule_GraphObject* gref; - igraph_integer_t idx; + igraph_int_t idx; long hash; } igraphmodule_EdgeObject; @@ -43,7 +43,7 @@ extern PyTypeObject* igraphmodule_EdgeType; int igraphmodule_Edge_register_type(void); int igraphmodule_Edge_Check(PyObject* obj); -PyObject* igraphmodule_Edge_New(igraphmodule_GraphObject *gref, igraph_integer_t idx); -igraph_integer_t igraphmodule_Edge_get_index_as_igraph_integer(igraphmodule_EdgeObject* self); +PyObject* igraphmodule_Edge_New(igraphmodule_GraphObject *gref, igraph_int_t idx); +igraph_int_t igraphmodule_Edge_get_index_as_igraph_integer(igraphmodule_EdgeObject* self); #endif diff --git a/src/_igraph/edgeseqobject.c b/src/_igraph/edgeseqobject.c index fcdbce41a..13f4364f9 100644 --- a/src/_igraph/edgeseqobject.c +++ b/src/_igraph/edgeseqobject.c @@ -109,7 +109,7 @@ int igraphmodule_EdgeSeq_init(igraphmodule_EdgeSeqObject *self, PyObject *args, igraph_es_all(&es, IGRAPH_EDGEORDER_ID); } else if (PyLong_Check(esobj)) { /* We selected a single edge */ - igraph_integer_t idx; + igraph_int_t idx; if (igraphmodule_PyObject_to_integer_t(esobj, &idx)) { return -1; @@ -124,7 +124,7 @@ int igraphmodule_EdgeSeq_init(igraphmodule_EdgeSeqObject *self, PyObject *args, } else { /* We selected multiple edges */ igraph_vector_int_t v; - igraph_integer_t n = igraph_ecount(&((igraphmodule_GraphObject*)g)->g); + igraph_int_t n = igraph_ecount(&((igraphmodule_GraphObject*)g)->g); if (igraphmodule_PyObject_to_vector_int_t(esobj, &v)) { return -1; } @@ -173,7 +173,7 @@ void igraphmodule_EdgeSeq_dealloc(igraphmodule_EdgeSeqObject* self) { */ Py_ssize_t igraphmodule_EdgeSeq_sq_length(igraphmodule_EdgeSeqObject* self) { igraph_t *g; - igraph_integer_t result; + igraph_int_t result; g = &GET_GRAPH(self); @@ -192,7 +192,7 @@ Py_ssize_t igraphmodule_EdgeSeq_sq_length(igraphmodule_EdgeSeqObject* self) { PyObject* igraphmodule_EdgeSeq_sq_item(igraphmodule_EdgeSeqObject* self, Py_ssize_t i) { igraph_t *g; - igraph_integer_t idx = -1; + igraph_int_t idx = -1; if (!self->gref) { return NULL; @@ -433,7 +433,7 @@ int igraphmodule_EdgeSeq_set_attribute_values_mapping(igraphmodule_EdgeSeqObject igraphmodule_GraphObject *gr; igraph_vector_int_t es; Py_ssize_t i, j, n; - igraph_integer_t no_of_edges; + igraph_int_t no_of_edges; gr = self->gref; dict = ATTR_STRUCT_DICT(&gr->g)[ATTRHASH_IDX_EDGE]; @@ -570,7 +570,7 @@ int igraphmodule_EdgeSeq_set_attribute_values_mapping(igraphmodule_EdgeSeqObject /* We don't have attributes with the given name yet. Create an entry * in the dict, create a new list, fill with None for vertices not in the * sequence and copy the rest */ - igraph_integer_t n2 = igraph_ecount(&gr->g); + igraph_int_t n2 = igraph_ecount(&gr->g); list = PyList_New(n2); if (list == 0) { igraph_vector_int_destroy(&es); @@ -682,7 +682,7 @@ PyObject* igraphmodule_EdgeSeq_find(igraphmodule_EdgeSeqObject *self, PyObject * PyObject* igraphmodule_EdgeSeq_select(igraphmodule_EdgeSeqObject *self, PyObject *args) { igraphmodule_EdgeSeqObject *result; igraphmodule_GraphObject *gr; - igraph_integer_t igraph_idx; + igraph_int_t igraph_idx; igraph_bool_t working_on_whole_graph = igraph_es_is_all(&self->es); igraph_vector_int_t v, v2; Py_ssize_t i, j, n, m; @@ -786,7 +786,7 @@ PyObject* igraphmodule_EdgeSeq_select(igraphmodule_EdgeSeqObject *self, PyObject for (; i < n; i++) { PyObject *item2 = PyTuple_GetItem(args, i); - igraph_integer_t idx; + igraph_int_t idx; if (item2 == 0) { Py_DECREF(result); @@ -849,7 +849,7 @@ PyObject* igraphmodule_EdgeSeq_select(igraphmodule_EdgeSeqObject *self, PyObject igraph_vector_int_destroy(&v); } else { /* Iterators and everything that was not handled directly */ - PyObject *iter, *item2; + PyObject *iter = NULL, *item2 = NULL; /* Allocate stuff */ if (igraph_vector_int_init(&v, 0)) { diff --git a/src/_igraph/error.c b/src/_igraph/error.c index 41b2b879e..b6940a27e 100644 --- a/src/_igraph/error.c +++ b/src/_igraph/error.c @@ -61,7 +61,20 @@ PyObject* igraphmodule_handle_igraph_error() { void igraphmodule_igraph_warning_hook(const char *reason, const char *file, int line) { char buf[4096]; - snprintf(buf, sizeof(buf), "%s at %s:%i", reason, file, line); + char end; + size_t len = strlen(reason); + const char* separator = " "; + + if (len == 0) { + separator = ""; + } else { + end = reason[len - 1]; + if (end != '.' && end != '?' && end != '!') { + separator = ". "; + } + } + + snprintf(buf, sizeof(buf), "%s%sLocation: %s:%i", reason, separator, file, line); PY_IGRAPH_WARN(buf); } diff --git a/src/_igraph/graphobject.c b/src/_igraph/graphobject.c index 894e35d26..b6c14a340 100644 --- a/src/_igraph/graphobject.c +++ b/src/_igraph/graphobject.c @@ -33,7 +33,6 @@ #include "indexing.h" #include "memory.h" #include "pyhelpers.h" -#include "utils.h" #include "vertexseqobject.h" #include @@ -197,7 +196,7 @@ int igraphmodule_Graph_init(igraphmodule_GraphObject * self, void* ptr = 0; Py_ssize_t n = 0; igraph_vector_int_t edges_vector; - igraph_integer_t vcount; + igraph_int_t vcount; igraph_bool_t edges_vector_owned = false; int retval = 0; @@ -534,7 +533,7 @@ PyObject *igraphmodule_Graph_is_maximal_matching(igraphmodule_GraphObject* self, PyObject *igraphmodule_Graph_is_simple(igraphmodule_GraphObject* self, PyObject* Py_UNUSED(_null)) { igraph_bool_t res; - if (igraph_is_simple(&self->g, &res)) { + if (igraph_is_simple(&self->g, &res, IGRAPH_DIRECTED)) { igraphmodule_handle_igraph_error(); return NULL; } @@ -877,7 +876,7 @@ PyObject *igraphmodule_Graph_diversity(igraphmodule_GraphObject * self, igraph_vector_t res, *weights = 0; igraph_vs_t vs; igraph_bool_t return_single = false; - igraph_integer_t no_of_nodes; + igraph_int_t no_of_nodes; static char *kwlist[] = { "vertices", "weights", NULL }; @@ -1015,18 +1014,30 @@ PyObject *igraphmodule_Graph_strength(igraphmodule_GraphObject * self, PyObject *igraphmodule_Graph_density(igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { - char *kwlist[] = { "loops", NULL }; + char *kwlist[] = { "loops", "weights", NULL }; igraph_real_t res; - PyObject *loops = Py_False; + PyObject *loops = Py_False, *weights_o = Py_None; + igraph_vector_t *weights = 0; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwlist, &loops)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO", kwlist, &loops, &weights_o)) + return NULL; + + if (igraphmodule_attrib_to_vector_t(weights_o, self, &weights, ATTRIBUTE_TYPE_EDGE)) { return NULL; + } - if (igraph_density(&self->g, &res, PyObject_IsTrue(loops))) { + if (igraph_density(&self->g, weights, &res, PyObject_IsTrue(loops))) { igraphmodule_handle_igraph_error(); + if (weights) { + igraph_vector_destroy(weights); free(weights); + } return NULL; } + if (weights) { + igraph_vector_destroy(weights); free(weights); + } + return igraphmodule_real_t_to_PyObject(res, IGRAPHMODULE_TYPE_FLOAT); } @@ -1065,7 +1076,7 @@ PyObject *igraphmodule_Graph_maxdegree(igraphmodule_GraphObject * self, igraph_neimode_t dmode = IGRAPH_ALL; PyObject *dmode_o = Py_None; PyObject *loops = Py_False; - igraph_integer_t res; + igraph_int_t res; igraph_vs_t vs; igraph_bool_t return_single = false; @@ -1317,20 +1328,28 @@ PyObject *igraphmodule_Graph_count_multiple(igraphmodule_GraphObject *self, PyObject *igraphmodule_Graph_neighbors(igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { - PyObject *list, *dmode_o = Py_None, *index_o; + PyObject *list, *dmode_o = Py_None, *index_o, *loops_o = Py_True, *multiple_o = Py_True; igraph_neimode_t dmode = IGRAPH_ALL; - igraph_integer_t idx; + igraph_loops_t loops = IGRAPH_LOOPS; + igraph_bool_t multiple = 1; + igraph_int_t idx; igraph_vector_int_t res; - static char *kwlist[] = { "vertex", "mode", NULL }; + static char *kwlist[] = { "vertex", "mode", "loops", "multiple", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O", kwlist, &index_o, &dmode_o)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OOO", kwlist, &index_o, &dmode_o, &loops_o, &multiple_o)) return NULL; if (igraphmodule_PyObject_to_neimode_t(dmode_o, &dmode)) { return NULL; } + if (igraphmodule_PyObject_to_loops_t(loops_o, &loops)) { + return NULL; + } + + multiple = PyObject_IsTrue(multiple_o); + if (igraphmodule_PyObject_to_vid(index_o, &idx, &self->g)) { return NULL; } @@ -1340,7 +1359,7 @@ PyObject *igraphmodule_Graph_neighbors(igraphmodule_GraphObject * self, return NULL; } - if (igraph_neighbors(&self->g, &res, idx, dmode)) { + if (igraph_neighbors(&self->g, &res, idx, dmode, loops, multiple)) { igraphmodule_handle_igraph_error(); igraph_vector_int_destroy(&res); return NULL; @@ -1366,20 +1385,25 @@ PyObject *igraphmodule_Graph_neighbors(igraphmodule_GraphObject * self, PyObject *igraphmodule_Graph_incident(igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { - PyObject *list, *dmode_o = Py_None, *index_o; + PyObject *list, *dmode_o = Py_None, *index_o, *loops_o = Py_True; igraph_neimode_t dmode = IGRAPH_OUT; - igraph_integer_t idx; + igraph_loops_t loops = IGRAPH_LOOPS; + igraph_int_t idx; igraph_vector_int_t res; - static char *kwlist[] = { "vertex", "mode", NULL }; + static char *kwlist[] = { "vertex", "mode", "loops", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O", kwlist, &index_o, &dmode_o)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OO", kwlist, &index_o, &dmode_o, &loops_o)) return NULL; if (igraphmodule_PyObject_to_neimode_t(dmode_o, &dmode)) { return NULL; } + if (igraphmodule_PyObject_to_loops_t(loops_o, &loops)) { + return NULL; + } + if (igraphmodule_PyObject_to_vid(index_o, &idx, &self->g)) { return NULL; } @@ -1389,7 +1413,7 @@ PyObject *igraphmodule_Graph_incident(igraphmodule_GraphObject * self, return NULL; } - if (igraph_incident(&self->g, &res, idx, dmode)) { + if (igraph_incident(&self->g, &res, idx, dmode, loops)) { igraphmodule_handle_igraph_error(); igraph_vector_int_destroy(&res); return NULL; @@ -1428,80 +1452,6 @@ PyObject *igraphmodule_Graph_reciprocity(igraphmodule_GraphObject * self, return igraphmodule_real_t_to_PyObject(res, IGRAPHMODULE_TYPE_FLOAT); } -/** \ingroup python_interface_graph - * \brief The successors of a given vertex in an \c igraph.Graph - * This method accepts a single vertex ID as a parameter, and returns the - * successors of the given vertex in the form of an integer list. It - * is equivalent to calling \c igraph.Graph.neighbors with \c type=OUT - * - * \return the successor list as a Python list object - * \sa igraph_neighbors - */ -PyObject *igraphmodule_Graph_successors(igraphmodule_GraphObject * self, - PyObject * args, PyObject * kwds) -{ - PyObject *list, *index_o; - igraph_integer_t idx; - igraph_vector_int_t res; - - static char *kwlist[] = { "vertex", NULL }; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &index_o)) - return NULL; - - if (igraphmodule_PyObject_to_vid(index_o, &idx, &self->g)) - return NULL; - - igraph_vector_int_init(&res, 0); - if (igraph_neighbors(&self->g, &res, idx, IGRAPH_OUT)) { - igraphmodule_handle_igraph_error(); - igraph_vector_int_destroy(&res); - return NULL; - } - - list = igraphmodule_vector_int_t_to_PyList(&res); - igraph_vector_int_destroy(&res); - - return list; -} - -/** \ingroup python_interface_graph - * \brief The predecessors of a given vertex in an \c igraph.Graph - * This method accepts a single vertex ID as a parameter, and returns the - * predecessors of the given vertex in the form of an integer list. It - * is equivalent to calling \c igraph.Graph.neighbors with \c type=IN - * - * \return the predecessor list as a Python list object - * \sa igraph_neighbors - */ -PyObject *igraphmodule_Graph_predecessors(igraphmodule_GraphObject * self, - PyObject * args, PyObject * kwds) -{ - PyObject *list, *index_o; - igraph_integer_t idx; - igraph_vector_int_t res; - - static char *kwlist[] = { "vertex", NULL }; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &index_o)) - return NULL; - - if (igraphmodule_PyObject_to_vid(index_o, &idx, &self->g)) - return NULL; - - igraph_vector_int_init(&res, 1); - if (igraph_neighbors(&self->g, &res, idx, IGRAPH_IN)) { - igraphmodule_handle_igraph_error(); - igraph_vector_int_destroy(&res); - return NULL; - } - - list = igraphmodule_vector_int_t_to_PyList(&res); - igraph_vector_int_destroy(&res); - - return list; -} - /** \ingroup python_interface_graph * \brief Decides whether a graph is connected. * \return Py_True if the graph is connected, Py_False otherwise @@ -1562,7 +1512,7 @@ PyObject *igraphmodule_Graph_are_adjacent(igraphmodule_GraphObject * self, { static char *kwlist[] = { "v1", "v2", NULL }; PyObject *v1, *v2; - igraph_integer_t idx1, idx2; + igraph_int_t idx1, idx2; igraph_bool_t res; if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, &v1, &v2)) @@ -1593,8 +1543,8 @@ PyObject *igraphmodule_Graph_get_eid(igraphmodule_GraphObject * self, PyObject *v1, *v2; PyObject *directed = Py_True; PyObject *error = Py_True; - igraph_integer_t idx1, idx2; - igraph_integer_t res; + igraph_int_t idx1, idx2; + igraph_int_t res; if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, &v1, &v2, &directed, &error)) @@ -1689,30 +1639,24 @@ PyObject *igraphmodule_Graph_diameter(igraphmodule_GraphObject * self, if (igraphmodule_attrib_to_vector_t(weights_o, self, &weights, ATTRIBUTE_TYPE_EDGE)) return NULL; - if (weights) { - if (igraph_diameter_dijkstra(&self->g, weights, &diameter, - /* from, to, vertex_path, edge_path */ - 0, 0, 0, 0, - PyObject_IsTrue(dir), PyObject_IsTrue(vcount_if_unconnected))) { - igraphmodule_handle_igraph_error(); + if ( + igraph_diameter(&self->g, weights, &diameter, + /* from, to, vertex_path, edge_path */ + 0, 0, 0, 0, + PyObject_IsTrue(dir), PyObject_IsTrue(vcount_if_unconnected)) + ) { + igraphmodule_handle_igraph_error(); + if (weights) { igraph_vector_destroy(weights); free(weights); - return NULL; - } - igraph_vector_destroy(weights); free(weights); - return igraphmodule_real_t_to_PyObject(diameter, IGRAPHMODULE_TYPE_FLOAT); - } else { - if (igraph_diameter(&self->g, &diameter, - /* from, to, vertex_path, edge_path */ - 0, 0, 0, 0, - PyObject_IsTrue(dir), PyObject_IsTrue(vcount_if_unconnected))) { - igraphmodule_handle_igraph_error(); - return NULL; } + return NULL; + } - /* The diameter is integer in this case, except if igraph_diameter() - * returned NaN or infinity for some reason */ - return igraphmodule_real_t_to_PyObject(diameter, IGRAPHMODULE_TYPE_FLOAT_IF_FRACTIONAL_ELSE_INT); + if (weights) { + igraph_vector_destroy(weights); free(weights); } + + return igraphmodule_real_t_to_PyObject(diameter, IGRAPHMODULE_TYPE_FLOAT_IF_FRACTIONAL_ELSE_INT); } /** \ingroup python_interface_graph @@ -1737,30 +1681,33 @@ PyObject *igraphmodule_Graph_get_diameter(igraphmodule_GraphObject * self, if (igraphmodule_attrib_to_vector_t(weights_o, self, &weights, ATTRIBUTE_TYPE_EDGE)) return NULL; - igraph_vector_int_init(&res, 0); - if (weights) { - if (igraph_diameter_dijkstra(&self->g, weights, 0, - /* from, to, vertex_path, edge_path */ - 0, 0, &res, 0, - PyObject_IsTrue(dir), PyObject_IsTrue(vcount_if_unconnected))) { - igraphmodule_handle_igraph_error(); + if (igraph_vector_int_init(&res, 0)) { + igraphmodule_handle_igraph_error(); + return NULL; + } + + if ( + igraph_diameter(&self->g, weights, 0, + /* from, to, vertex_path, edge_path */ + 0, 0, &res, 0, + PyObject_IsTrue(dir), PyObject_IsTrue(vcount_if_unconnected) + ) + ) { + igraphmodule_handle_igraph_error(); + if (weights) { igraph_vector_destroy(weights); free(weights); - igraph_vector_int_destroy(&res); - return NULL; } + igraph_vector_int_destroy(&res); + return NULL; + } + + if (weights) { igraph_vector_destroy(weights); free(weights); - } else { - if (igraph_diameter(&self->g, 0, - /* from, to, vertex_path, edge_path */ - 0, 0, &res, 0, - PyObject_IsTrue(dir), PyObject_IsTrue(vcount_if_unconnected))) { - igraphmodule_handle_igraph_error(); - return NULL; - } } result_o = igraphmodule_vector_int_t_to_PyList(&res); igraph_vector_int_destroy(&res); + return result_o; } @@ -1774,7 +1721,7 @@ PyObject *igraphmodule_Graph_farthest_points(igraphmodule_GraphObject * self, PyObject *dir = Py_True, *vcount_if_unconnected = Py_True; PyObject *weights_o = Py_None; igraph_vector_t *weights = 0; - igraph_integer_t from, to; + igraph_int_t from, to; igraph_real_t len; static char *kwlist[] = { "directed", "unconn", "weights", NULL }; @@ -1787,46 +1734,29 @@ PyObject *igraphmodule_Graph_farthest_points(igraphmodule_GraphObject * self, if (igraphmodule_attrib_to_vector_t(weights_o, self, &weights, ATTRIBUTE_TYPE_EDGE)) return NULL; - if (weights) { - if (igraph_diameter_dijkstra(&self->g, weights, &len, - /* from, to, vertex_path, edge_path */ - &from, &to, 0, 0, - PyObject_IsTrue(dir), PyObject_IsTrue(vcount_if_unconnected))) { - igraphmodule_handle_igraph_error(); + if ( + igraph_diameter( + &self->g, weights, &len, + /* from, to, vertex_path, edge_path */ + &from, &to, 0, 0, + PyObject_IsTrue(dir), PyObject_IsTrue(vcount_if_unconnected) + ) + ) { + igraphmodule_handle_igraph_error(); + if (weights) { igraph_vector_destroy(weights); free(weights); - return NULL; } + return NULL; + } + + if (weights) { igraph_vector_destroy(weights); free(weights); - if (from >= 0) { - return Py_BuildValue("nnd", (Py_ssize_t)from, (Py_ssize_t)to, (double)len); - } else { - return Py_BuildValue("OOd", Py_None, Py_None, (double)len); - } - } else { - if (igraph_diameter(&self->g, &len, - /* from, to, vertex_path, edge_path */ - &from, &to, 0, 0, - PyObject_IsTrue(dir), PyObject_IsTrue(vcount_if_unconnected))) { - igraphmodule_handle_igraph_error(); - return NULL; - } + } - /* if len is finite and integer (which it typically is, unless it's - * infinite), then return a Python int as the third value; otherwise - * return a float */ - if (ceil(len) == len && isfinite(len)) { - if (from >= 0) { - return Py_BuildValue("nnn", (Py_ssize_t)from, (Py_ssize_t)to, (Py_ssize_t)len); - } else { - return Py_BuildValue("OOn", Py_None, Py_None, (Py_ssize_t)len); - } - } else { - if (from >= 0) { - return Py_BuildValue("nnd", (Py_ssize_t)from, (Py_ssize_t)to, (double)len); - } else { - return Py_BuildValue("OOd", Py_None, Py_None, (double)len); - } - } + if (from >= 0) { + return Py_BuildValue("nnd", (Py_ssize_t)from, (Py_ssize_t)to, (double)len); + } else { + return Py_BuildValue("OOd", Py_None, Py_None, (double)len); } } @@ -2019,7 +1949,7 @@ PyObject *igraphmodule_Graph_radius(igraphmodule_GraphObject * self, return NULL; } - if (igraph_radius_dijkstra(&self->g, weights, &radius, mode)) { + if (igraph_radius(&self->g, weights, &radius, mode)) { if (weights) { igraph_vector_destroy(weights); free(weights); } @@ -2149,8 +2079,9 @@ PyObject *igraphmodule_Graph_Barabasi(PyTypeObject * type, igraph_t g; Py_ssize_t n; float power = 1.0f, zero_appeal = 1.0f; - igraph_integer_t m = 1; + igraph_int_t m = 1; igraph_vector_int_t outseq; + igraph_bool_t has_outseq = false; igraph_t *start_from = 0; igraph_barabasi_algorithm_t algo = IGRAPH_BARABASI_PSUMTREE; PyObject *m_obj = 0, *outpref = Py_False, *directed = Py_False; @@ -2176,36 +2107,36 @@ PyObject *igraphmodule_Graph_Barabasi(PyTypeObject * type, CHECK_SSIZE_T_RANGE(n, "vertex count"); if (m_obj == 0) { - igraph_vector_int_init(&outseq, 0); m = 1; - } else if (m_obj != 0) { - /* let's check whether we have a constant out-degree or a list */ - if (PyLong_Check(m_obj)) { - if (igraphmodule_PyObject_to_integer_t(m_obj, &m)) { - return NULL; - } - igraph_vector_int_init(&outseq, 0); - } else if (PyList_Check(m_obj)) { - if (igraphmodule_PyObject_to_vector_int_t(m_obj, &outseq)) { - return NULL; - } - } else { - PyErr_SetString(PyExc_TypeError, "m must be an integer or a list of integers"); + } else if (PyLong_Check(m_obj)) { + if (igraphmodule_PyObject_to_integer_t(m_obj, &m)) { + return NULL; + } + } else if (PySequence_Check(m_obj)) { + if (igraphmodule_PyObject_to_vector_int_t(m_obj, &outseq)) { return NULL; } + has_outseq = true; + } else { + PyErr_SetString(PyExc_TypeError, "m must be an integer or a sequence of integers"); + return NULL; } if (igraph_barabasi_game(&g, n, power, m, - &outseq, PyObject_IsTrue(outpref), + has_outseq ? &outseq : NULL, PyObject_IsTrue(outpref), zero_appeal, PyObject_IsTrue(directed), algo, start_from)) { igraphmodule_handle_igraph_error(); - igraph_vector_int_destroy(&outseq); + if (has_outseq) { + igraph_vector_int_destroy(&outseq); + } return NULL; } - igraph_vector_int_destroy(&outseq); + if (has_outseq) { + igraph_vector_int_destroy(&outseq); + } CREATE_GRAPH_FROM_TYPE(self, g, type); @@ -2402,15 +2333,16 @@ PyObject *igraphmodule_Graph_Erdos_Renyi(PyTypeObject * type, igraph_t g; Py_ssize_t n, m = -1; double p = -1.0; - PyObject *loops = Py_False, *directed = Py_False; + PyObject *loops = Py_False, *directed = Py_False, *edge_labeled = Py_False; int retval; - static char *kwlist[] = { "n", "p", "m", "directed", "loops", NULL }; + static char *kwlist[] = { "n", "p", "m", "directed", "loops", "edge_labeled", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "n|dnOO", kwlist, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "n|dnOOO", kwlist, &n, &p, &m, &directed, - &loops)) + &loops, + &edge_labeled)) return NULL; CHECK_SSIZE_T_RANGE(n, "vertex count"); @@ -2429,10 +2361,18 @@ PyObject *igraphmodule_Graph_Erdos_Renyi(PyTypeObject * type, if (m == -1) { /* GNP model */ - retval = igraph_erdos_renyi_game_gnp(&g, n, p, PyObject_IsTrue(directed), PyObject_IsTrue(loops)); + retval = igraph_erdos_renyi_game_gnp( + &g, n, p, PyObject_IsTrue(directed), + PyObject_IsTrue(loops) ? IGRAPH_LOOPS_SW : IGRAPH_SIMPLE_SW, + PyObject_IsTrue(edge_labeled) + ); } else { /* GNM model */ - retval = igraph_erdos_renyi_game_gnm(&g, n, m, PyObject_IsTrue(directed), PyObject_IsTrue(loops)); + retval = igraph_erdos_renyi_game_gnm( + &g, n, m, PyObject_IsTrue(directed), + PyObject_IsTrue(loops) ? IGRAPH_LOOPS_SW : IGRAPH_SIMPLE_SW, + PyObject_IsTrue(edge_labeled) + ); } if (retval) { @@ -3094,7 +3034,7 @@ PyObject *igraphmodule_Graph_LCF(PyTypeObject *type, if (igraphmodule_PyObject_to_vector_int_t(shifts_o, &shifts)) return NULL; - if (igraph_lcf_vector(&g, n, &shifts, repeats)) { + if (igraph_lcf(&g, n, &shifts, repeats)) { igraph_vector_int_destroy(&shifts); igraphmodule_handle_igraph_error(); return NULL; @@ -3477,15 +3417,20 @@ PyObject *igraphmodule_Graph_Random_Bipartite(PyTypeObject * type, Py_ssize_t n1, n2, m = -1; double p = -1.0; igraph_neimode_t neimode = IGRAPH_ALL; - PyObject *directed_o = Py_False, *neimode_o = NULL; + PyObject *directed_o = Py_False, *neimode_o = NULL, *edge_labeled_o = Py_False; + PyObject *edge_types_o = Py_None; igraph_vector_bool_t vertex_types; + igraph_edge_type_sw_t allowed_edge_types = IGRAPH_SIMPLE_SW; PyObject *vertex_types_o; igraph_error_t retval; - static char *kwlist[] = { "n1", "n2", "p", "m", "directed", "neimode", NULL }; + static char *kwlist[] = { + "n1", "n2", "p", "m", "directed", "neimode", "allowed_edge_types", "edge_labeled", NULL + }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "nn|dnOO", kwlist, - &n1, &n2, &p, &m, &directed_o, &neimode_o)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "nn|dnOOOO", kwlist, + &n1, &n2, &p, &m, &directed_o, &neimode_o, + &edge_types_o, &edge_labeled_o)) return NULL; CHECK_SSIZE_T_RANGE(n1, "number of vertices in first partition"); @@ -3505,6 +3450,9 @@ PyObject *igraphmodule_Graph_Random_Bipartite(PyTypeObject * type, if (igraphmodule_PyObject_to_neimode_t(neimode_o, &neimode)) return NULL; + if (igraphmodule_PyObject_to_edge_type_sw_t(edge_types_o, &allowed_edge_types)) + return NULL; + if (igraph_vector_bool_init(&vertex_types, n1+n2)) { igraphmodule_handle_igraph_error(); return NULL; @@ -3513,12 +3461,14 @@ PyObject *igraphmodule_Graph_Random_Bipartite(PyTypeObject * type, if (m == -1) { /* GNP model */ retval = igraph_bipartite_game_gnp( - &g, &vertex_types, n1, n2, p, PyObject_IsTrue(directed_o), neimode + &g, &vertex_types, n1, n2, p, PyObject_IsTrue(directed_o), neimode, + allowed_edge_types, PyObject_IsTrue(edge_labeled_o) ); } else { /* GNM model */ retval = igraph_bipartite_game_gnm( - &g, &vertex_types, n1, n2, m, PyObject_IsTrue(directed_o), neimode + &g, &vertex_types, n1, n2, m, PyObject_IsTrue(directed_o), neimode, + allowed_edge_types, PyObject_IsTrue(edge_labeled_o) ); } @@ -3552,8 +3502,9 @@ PyObject *igraphmodule_Graph_Recent_Degree(PyTypeObject * type, igraph_t g; Py_ssize_t n, window = 0; float power = 0.0f, zero_appeal = 0.0f; - igraph_integer_t m = 0; + igraph_int_t m = 0; igraph_vector_int_t outseq; + igraph_bool_t has_outseq = false; PyObject *m_obj, *outpref = Py_False, *directed = Py_False; char *kwlist[] = @@ -3573,24 +3524,28 @@ NULL }; if (igraphmodule_PyObject_to_integer_t(m_obj, &m)) { return NULL; } - igraph_vector_int_init(&outseq, 0); } else if (PyList_Check(m_obj)) { if (igraphmodule_PyObject_to_vector_int_t(m_obj, &outseq)) { // something bad happened during conversion return NULL; } + has_outseq = true; } - if (igraph_recent_degree_game(&g, n, power, window, m, &outseq, + if (igraph_recent_degree_game(&g, n, power, window, m, has_outseq ? &outseq : NULL, PyObject_IsTrue(outpref), zero_appeal, PyObject_IsTrue(directed))) { igraphmodule_handle_igraph_error(); - igraph_vector_int_destroy(&outseq); + if (has_outseq) { + igraph_vector_int_destroy(&outseq); + } return NULL; } - igraph_vector_int_destroy(&outseq); + if (has_outseq) { + igraph_vector_int_destroy(&outseq); + } CREATE_GRAPH_FROM_TYPE(self, g, type); @@ -3638,23 +3593,22 @@ PyObject *igraphmodule_Graph_SBM(PyTypeObject * type, { igraphmodule_GraphObject *self; igraph_t g; - Py_ssize_t n; - PyObject *block_sizes_o, *pref_matrix_o; + PyObject *block_sizes_o, *pref_matrix_o, *edge_types_o = Py_None; PyObject *directed_o = Py_False; - PyObject *loops_o = Py_False; + igraph_edge_type_sw_t allowed_edge_types = IGRAPH_SIMPLE_SW; igraph_matrix_t pref_matrix; igraph_vector_int_t block_sizes; - static char *kwlist[] = { "n", "pref_matrix", "block_sizes", "directed", - "loops", NULL }; + static char *kwlist[] = { "pref_matrix", "block_sizes", "directed", "allowed_edge_types", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "nOO|OO", kwlist, - &n, &pref_matrix_o, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, + &pref_matrix_o, &block_sizes_o, - &directed_o, &loops_o)) + &directed_o, &edge_types_o)) return NULL; - CHECK_SSIZE_T_RANGE(n, "vertex count"); + if (igraphmodule_PyObject_to_edge_type_sw_t(edge_types_o, &allowed_edge_types)) + return NULL; if (igraphmodule_PyObject_to_matrix_t(pref_matrix_o, &pref_matrix, "pref_matrix")) { return NULL; @@ -3665,7 +3619,7 @@ PyObject *igraphmodule_Graph_SBM(PyTypeObject * type, return NULL; } - if (igraph_sbm_game(&g, n, &pref_matrix, &block_sizes, PyObject_IsTrue(directed_o), PyObject_IsTrue(loops_o))) { + if (igraph_sbm_game(&g, &pref_matrix, &block_sizes, PyObject_IsTrue(directed_o), allowed_edge_types)) { igraphmodule_handle_igraph_error(); igraph_matrix_destroy(&pref_matrix); igraph_vector_int_destroy(&block_sizes); @@ -3734,15 +3688,16 @@ PyObject *igraphmodule_Graph_Static_Fitness(PyTypeObject *type, Py_ssize_t m; PyObject *fitness_out_o = Py_None, *fitness_in_o = Py_None; PyObject *fitness_o = Py_None; - PyObject *multiple = Py_False, *loops = Py_False; + PyObject *edge_types_o = Py_None; + igraph_edge_type_sw_t allowed_edge_types = IGRAPH_SIMPLE_SW; igraph_vector_t fitness_out, fitness_in; static char *kwlist[] = { "m", "fitness_out", "fitness_in", - "loops", "multiple", "fitness", NULL }; + "allowed_edge_types", "fitness", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "n|OOOOO", kwlist, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "n|OOOO", kwlist, &m, &fitness_out_o, &fitness_in_o, - &loops, &multiple, &fitness_o)) + &edge_types_o, &fitness_o)) return NULL; CHECK_SSIZE_T_RANGE(m, "edge count"); @@ -3758,6 +3713,9 @@ PyObject *igraphmodule_Graph_Static_Fitness(PyTypeObject *type, return NULL; } + if (igraphmodule_PyObject_to_edge_type_sw_t(edge_types_o, &allowed_edge_types)) + return NULL; + if (igraphmodule_PyObject_float_to_vector_t(fitness_out_o, &fitness_out)) return NULL; @@ -3770,7 +3728,7 @@ PyObject *igraphmodule_Graph_Static_Fitness(PyTypeObject *type, if (igraph_static_fitness_game(&g, m, &fitness_out, fitness_in_o == Py_None ? 0 : &fitness_in, - PyObject_IsTrue(loops), PyObject_IsTrue(multiple))) { + allowed_edge_types)) { igraph_vector_destroy(&fitness_out); if (fitness_in_o != Py_None) igraph_vector_destroy(&fitness_in); @@ -3799,21 +3757,25 @@ PyObject *igraphmodule_Graph_Static_Power_Law(PyTypeObject *type, igraph_t g; Py_ssize_t n, m; float exponent_out = -1.0f, exponent_in = -1.0f, exponent = -1.0f; - PyObject *multiple = Py_False, *loops = Py_False; + PyObject *edge_types_o = Py_None; + igraph_edge_type_sw_t allowed_edge_types = IGRAPH_SIMPLE_SW; PyObject *finite_size_correction = Py_True; static char *kwlist[] = { "n", "m", "exponent_out", "exponent_in", - "loops", "multiple", "finite_size_correction", "exponent", NULL }; + "allowed_edge_types", "finite_size_correction", "exponent", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "nn|ffOOOf", kwlist, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "nn|ffOOf", kwlist, &n, &m, &exponent_out, &exponent_in, - &loops, &multiple, &finite_size_correction, + &edge_types_o, &finite_size_correction, &exponent)) return NULL; CHECK_SSIZE_T_RANGE(n, "vertex count"); CHECK_SSIZE_T_RANGE(m, "edge count"); + if (igraphmodule_PyObject_to_edge_type_sw_t(edge_types_o, &allowed_edge_types)) + return NULL; + /* This trickery allows us to use "exponent" or "exponent_out" as * keyword argument, with "exponent_out" taking precedence over * "exponent" */ @@ -3826,7 +3788,7 @@ PyObject *igraphmodule_Graph_Static_Power_Law(PyTypeObject *type, } if (igraph_static_power_law_game(&g, n, m, exponent_out, exponent_in, - PyObject_IsTrue(loops), PyObject_IsTrue(multiple), + allowed_edge_types, PyObject_IsTrue(finite_size_correction))) { igraphmodule_handle_igraph_error(); return NULL; @@ -3976,22 +3938,25 @@ PyObject *igraphmodule_Graph_Watts_Strogatz(PyTypeObject * type, { Py_ssize_t dim, size, nei; double p; - PyObject* loops = Py_False; - PyObject* multiple = Py_False; + igraph_edge_type_sw_t allowed_edge_types = IGRAPH_SIMPLE_SW; + PyObject* edge_types_o = Py_None; igraphmodule_GraphObject *self; igraph_t g; - static char *kwlist[] = { "dim", "size", "nei", "p", "loops", "multiple", NULL }; + static char *kwlist[] = { "dim", "size", "nei", "p", "allowed_edge_types", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "nnnd|OO", kwlist, - &dim, &size, &nei, &p, &loops, &multiple)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "nnnd|O", kwlist, + &dim, &size, &nei, &p, &edge_types_o)) return NULL; CHECK_SSIZE_T_RANGE(dim, "dimensionality"); CHECK_SSIZE_T_RANGE(size, "size"); CHECK_SSIZE_T_RANGE(nei, "number of neighbors"); - if (igraph_watts_strogatz_game(&g, dim, size, nei, p, PyObject_IsTrue(loops), PyObject_IsTrue(multiple))) { + if (igraphmodule_PyObject_to_edge_type_sw_t(edge_types_o, &allowed_edge_types)) + return NULL; + + if (igraph_watts_strogatz_game(&g, dim, size, nei, p, allowed_edge_types)) { igraphmodule_handle_igraph_error(); return NULL; } @@ -4104,25 +4069,35 @@ PyObject *igraphmodule_Graph_articulation_points(igraphmodule_GraphObject* self, */ PyObject *igraphmodule_Graph_assortativity_nominal(igraphmodule_GraphObject *self, PyObject *args, PyObject *kwds) { - static char *kwlist[] = { "types", "directed", "normalized", NULL }; - PyObject *types_o = Py_None, *directed = Py_True, *normalized = Py_True; + static char *kwlist[] = { "types", "directed", "normalized", "weights", NULL }; + PyObject *types_o = Py_None, *weights_o = Py_None, *directed = Py_True, *normalized = Py_True; igraph_real_t res; igraph_error_t ret; igraph_vector_int_t *types = 0; + igraph_vector_t *weights = 0; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OO", kwlist, &types_o, &directed, &normalized)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OO", kwlist, &types_o, &directed, &normalized, &weights_o)) return NULL; if (igraphmodule_attrib_to_vector_int_t(types_o, self, &types, ATTRIBUTE_TYPE_VERTEX)) return NULL; - ret = igraph_assortativity_nominal(&self->g, types, &res, PyObject_IsTrue(directed), + if (igraphmodule_attrib_to_vector_t(weights_o, self, &weights, ATTRIBUTE_TYPE_EDGE)) { + if (types) { igraph_vector_int_destroy(types); free(types); } + return NULL; + } + + ret = igraph_assortativity_nominal(&self->g, weights, types, &res, PyObject_IsTrue(directed), PyObject_IsTrue(normalized)); if (types) { igraph_vector_int_destroy(types); free(types); } + if (weights) { + igraph_vector_destroy(weights); free(weights); + } + if (ret) { igraphmodule_handle_igraph_error(); return NULL; @@ -4137,26 +4112,36 @@ PyObject *igraphmodule_Graph_assortativity_nominal(igraphmodule_GraphObject *sel */ PyObject *igraphmodule_Graph_assortativity(igraphmodule_GraphObject *self, PyObject *args, PyObject *kwds) { - static char *kwlist[] = { "types1", "types2", "directed", "normalized", NULL }; + static char *kwlist[] = { "types1", "types2", "directed", "normalized", "weights", NULL }; PyObject *types1_o = Py_None, *types2_o = Py_None, *directed = Py_True, *normalized = Py_True; + PyObject *weights_o = Py_None; igraph_real_t res; igraph_error_t ret; - igraph_vector_t *types1 = 0, *types2 = 0; + igraph_vector_t *types1 = 0, *types2 = 0, *weights = 0; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OOO", kwlist, &types1_o, &types2_o, &directed, &normalized)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OOOO", kwlist, &types1_o, &types2_o, &directed, &normalized, &weights_o)) return NULL; - if (igraphmodule_attrib_to_vector_t(types1_o, self, &types1, ATTRIBUTE_TYPE_VERTEX)) + if (igraphmodule_attrib_to_vector_t(types1_o, self, &types1, ATTRIBUTE_TYPE_VERTEX)) { return NULL; + } + if (igraphmodule_attrib_to_vector_t(types2_o, self, &types2, ATTRIBUTE_TYPE_VERTEX)) { if (types1) { igraph_vector_destroy(types1); free(types1); } return NULL; } - ret = igraph_assortativity(&self->g, types1, types2, &res, PyObject_IsTrue(directed), PyObject_IsTrue(normalized)); + if (igraphmodule_attrib_to_vector_t(weights_o, self, &weights, ATTRIBUTE_TYPE_EDGE)) { + if (types1) { igraph_vector_destroy(types1); free(types1); } + if (types2) { igraph_vector_destroy(types2); free(types2); } + return NULL; + } + + ret = igraph_assortativity(&self->g, weights, types1, types2, &res, PyObject_IsTrue(directed), PyObject_IsTrue(normalized)); if (types1) { igraph_vector_destroy(types1); free(types1); } if (types2) { igraph_vector_destroy(types2); free(types2); } + if (weights) { igraph_vector_destroy(weights); free(weights); } if (ret) { igraphmodule_handle_igraph_error(); @@ -4203,6 +4188,8 @@ PyObject *igraphmodule_Graph_authority_score( igraph_real_t value; igraph_vector_t res, *weights = 0; + /* scale is deprecated but kept for backward compatibility reasons */ + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO!O", kwlist, &weights_o, &scale_o, igraphmodule_ARPACKOptionsType, &arpack_options_o, &return_eigenvalue)) @@ -4214,7 +4201,7 @@ PyObject *igraphmodule_Graph_authority_score( ATTRIBUTE_TYPE_EDGE)) return NULL; arpack_options = (igraphmodule_ARPACKOptionsObject*)arpack_options_o; - if (igraph_hub_and_authority_scores(&self->g, NULL, &res, &value, PyObject_IsTrue(scale_o), + if (igraph_hub_and_authority_scores(&self->g, NULL, &res, &value, weights, igraphmodule_ARPACKOptions_get(arpack_options))) { igraphmodule_handle_igraph_error(); if (weights) { igraph_vector_destroy(weights); free(weights); } @@ -4262,19 +4249,16 @@ PyObject *igraphmodule_Graph_average_path_length(igraphmodule_GraphObject * if (igraphmodule_attrib_to_vector_t(weights_o, self, &weights, ATTRIBUTE_TYPE_EDGE)) return NULL; - if (weights) { - if (igraph_average_path_length_dijkstra(&self->g, &res, 0, weights, PyObject_IsTrue(directed), PyObject_IsTrue(unconn))) { + if (igraph_average_path_length(&self->g, weights, &res, 0, PyObject_IsTrue(directed), PyObject_IsTrue(unconn))) { + if (weights) { igraph_vector_destroy(weights); free(weights); - igraphmodule_handle_igraph_error(); - return NULL; } + igraphmodule_handle_igraph_error(); + return NULL; + } + if (weights) { igraph_vector_destroy(weights); free(weights); - } else { - if (igraph_average_path_length(&self->g, &res, 0, PyObject_IsTrue(directed), PyObject_IsTrue(unconn))) { - igraphmodule_handle_igraph_error(); - return NULL; - } } return PyFloat_FromDouble(res); @@ -4289,8 +4273,9 @@ PyObject *igraphmodule_Graph_betweenness(igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { static char *kwlist[] = { "vertices", "directed", "cutoff", "weights", - "sources", "targets", NULL }; + "sources", "targets", "normalized", NULL }; PyObject *directed = Py_True; + PyObject *normalized = Py_False; PyObject *vobj = Py_None, *list; PyObject *cutoff = Py_None; PyObject *weights_o = Py_None; @@ -4302,9 +4287,9 @@ PyObject *igraphmodule_Graph_betweenness(igraphmodule_GraphObject * self, igraph_vs_t vs, sources, targets; igraph_error_t retval; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOOOO", kwlist, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOOOOO", kwlist, &vobj, &directed, &cutoff, &weights_o, - &sources_o, &targets_o)) { + &sources_o, &targets_o, &normalized)) { return NULL; } @@ -4345,10 +4330,10 @@ PyObject *igraphmodule_Graph_betweenness(igraphmodule_GraphObject * self, if (cutoff == Py_None) { if (is_subsetted) { retval = igraph_betweenness_subset( - &self->g, &res, vs, PyObject_IsTrue(directed), sources, targets, weights + &self->g, weights, &res, sources, targets, vs, PyObject_IsTrue(directed), PyObject_IsTrue(normalized) ); } else { - retval = igraph_betweenness(&self->g, &res, vs, PyObject_IsTrue(directed), weights); + retval = igraph_betweenness(&self->g, weights, &res, vs, PyObject_IsTrue(directed), PyObject_IsTrue(normalized)); } if (retval) { @@ -4382,8 +4367,8 @@ PyObject *igraphmodule_Graph_betweenness(igraphmodule_GraphObject * self, return NULL; } - if (igraph_betweenness_cutoff(&self->g, &res, vs, PyObject_IsTrue(directed), - weights, PyFloat_AsDouble(cutoff_num))) { + if (igraph_betweenness_cutoff(&self->g, weights, &res, vs, PyObject_IsTrue(directed), + PyObject_IsTrue(normalized), PyFloat_AsDouble(cutoff_num))) { igraph_vs_destroy(&vs); igraph_vs_destroy(&targets); igraph_vs_destroy(&sources); @@ -4471,7 +4456,7 @@ PyObject *igraphmodule_Graph_biconnected_components(igraphmodule_GraphObject *se igraph_vector_int_list_t components; igraph_vector_int_t points; igraph_bool_t return_articulation_points; - igraph_integer_t no; + igraph_int_t no; PyObject *result_o, *aps=Py_False; static char* kwlist[] = {"return_articulation_points", NULL}; @@ -4650,7 +4635,7 @@ PyObject *igraphmodule_Graph_bipartite_projection_size(igraphmodule_GraphObject PyObject* args, PyObject* kwds) { PyObject *types_o = Py_None; igraph_vector_bool_t* types = 0; - igraph_integer_t vcount1, vcount2, ecount1, ecount2; + igraph_int_t vcount1, vcount2, ecount1, ecount2; static char* kwlist[] = {"types", NULL}; @@ -4947,7 +4932,7 @@ PyObject *igraphmodule_Graph_connected_components( static char *kwlist[] = { "mode", NULL }; igraph_connectedness_t mode = IGRAPH_STRONG; igraph_vector_int_t res1, res2; - igraph_integer_t no; + igraph_int_t no; PyObject *list, *mode_o = Py_None; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwlist, &mode_o)) @@ -5217,7 +5202,7 @@ PyObject *igraphmodule_Graph_eccentricity(igraphmodule_GraphObject* self, return NULL; } - if (igraph_eccentricity_dijkstra(&self->g, weights, &res, vs, mode)) { + if (igraph_eccentricity(&self->g, weights, &res, vs, mode)) { if (weights) { igraph_vector_destroy(weights); free(weights); } @@ -5306,9 +5291,9 @@ PyObject *igraphmodule_Graph_edge_betweenness(igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { - static char *kwlist[] = { "directed", "cutoff", "weights", "sources", "targets", NULL }; + static char *kwlist[] = { "directed", "cutoff", "weights", "sources", "targets", "normalized", NULL }; igraph_vector_t res, *weights = 0; - PyObject *list, *directed = Py_True, *cutoff = Py_None; + PyObject *list, *directed = Py_True, *cutoff = Py_None, *normalized = Py_False; PyObject *weights_o = Py_None; PyObject *sources_o = Py_None; PyObject *targets_o = Py_None; @@ -5317,8 +5302,8 @@ PyObject *igraphmodule_Graph_edge_betweenness(igraphmodule_GraphObject * self, igraph_error_t retval; igraph_bool_t is_subsetted = false; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOOO", kwlist, - &directed, &cutoff, &weights_o, &sources_o, &targets_o)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOOOO", kwlist, + &directed, &cutoff, &weights_o, &sources_o, &targets_o, &normalized)) return NULL; if (igraphmodule_attrib_to_vector_t(weights_o, self, &weights, @@ -5349,12 +5334,14 @@ PyObject *igraphmodule_Graph_edge_betweenness(igraphmodule_GraphObject * self, if (cutoff == Py_None) { if (is_subsetted) { retval = igraph_edge_betweenness_subset( - &self->g, &res, igraph_ess_all(IGRAPH_EDGEORDER_ID), - PyObject_IsTrue(directed), sources, targets, weights + &self->g, weights, &res, + sources, targets, igraph_ess_all(IGRAPH_EDGEORDER_ID), + PyObject_IsTrue(directed), PyObject_IsTrue(normalized) ); } else { retval = igraph_edge_betweenness( - &self->g, &res, PyObject_IsTrue(directed), weights + &self->g, weights, &res, igraph_ess_all(IGRAPH_EDGEORDER_ID), + PyObject_IsTrue(directed), PyObject_IsTrue(normalized) ); } if (retval) { @@ -5384,8 +5371,10 @@ PyObject *igraphmodule_Graph_edge_betweenness(igraphmodule_GraphObject * self, return NULL; } - if (igraph_edge_betweenness_cutoff(&self->g, &res, PyObject_IsTrue(directed), - weights, PyFloat_AsDouble(cutoff_num))) { + if (igraph_edge_betweenness_cutoff( + &self->g, weights, &res, igraph_ess_all(IGRAPH_EDGEORDER_ID), + PyObject_IsTrue(directed), PyObject_IsTrue(normalized), PyFloat_AsDouble(cutoff_num) + )) { igraph_vector_destroy(&res); igraph_vs_destroy(&targets); igraph_vs_destroy(&sources); @@ -5420,8 +5409,8 @@ PyObject *igraphmodule_Graph_edge_connectivity(igraphmodule_GraphObject *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = { "source", "target", "checks", NULL }; PyObject *checks = Py_True, *source_o = Py_None, *target_o = Py_None; - igraph_integer_t source = -1, target = -1; - igraph_integer_t res; + igraph_int_t source = -1, target = -1; + igraph_int_t res; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO", kwlist, &source_o, &target_o, &checks)) return NULL; @@ -5471,6 +5460,8 @@ PyObject *igraphmodule_Graph_eigenvector_centrality( igraph_real_t value; igraph_vector_t *weights=0, res; + /* scale is deprecated but kept for backward compatibility reasons */ + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOO!O", kwlist, &directed_o, &scale_o, &weights_o, igraphmodule_ARPACKOptionsType, @@ -5487,7 +5478,7 @@ PyObject *igraphmodule_Graph_eigenvector_centrality( arpack_options = (igraphmodule_ARPACKOptionsObject*)arpack_options_o; if (igraph_eigenvector_centrality(&self->g, &res, &value, - PyObject_IsTrue(directed_o), PyObject_IsTrue(scale_o), + PyObject_IsTrue(directed_o), weights, igraphmodule_ARPACKOptions_get(arpack_options))) { igraphmodule_handle_igraph_error(); if (weights) { igraph_vector_destroy(weights); free(weights); } @@ -5608,7 +5599,7 @@ PyObject *igraphmodule_Graph_get_shortest_path( static char *kwlist[] = { "v", "to", "weights", "mode", "output", "algorithm", NULL }; igraph_vector_t *weights=0; igraph_neimode_t mode = IGRAPH_OUT; - igraph_integer_t from, to; + igraph_int_t from, to; PyObject *list, *mode_o=Py_None, *weights_o=Py_None, *output_o=Py_None, *from_o = Py_None, *to_o=Py_None, *algorithm_o=Py_None; @@ -5644,14 +5635,14 @@ PyObject *igraphmodule_Graph_get_shortest_path( return NULL; } - if (algorithm == IGRAPHMODULE_SHORTEST_PATH_ALGORITHM_AUTO) { - algorithm = igraphmodule_select_shortest_path_algorithm( - &self->g, weights, NULL, mode, /* allow_johnson = */ false - ); - } - /* Call the C function */ switch (algorithm) { + case IGRAPHMODULE_SHORTEST_PATH_ALGORITHM_AUTO: + retval = igraph_get_shortest_path( + &self->g, weights, use_edges ? NULL : &vec, use_edges ? &vec : NULL, from, to, mode + ); + break; + case IGRAPHMODULE_SHORTEST_PATH_ALGORITHM_DIJKSTRA: retval = igraph_get_shortest_path_dijkstra( &self->g, use_edges ? NULL : &vec, use_edges ? &vec : NULL, from, to, weights, mode @@ -5692,7 +5683,7 @@ typedef struct { } igraphmodule_i_Graph_get_shortest_path_astar_callback_data_t; igraph_error_t igraphmodule_i_Graph_get_shortest_path_astar_callback( - igraph_real_t *result, igraph_integer_t from, igraph_integer_t to, + igraph_real_t *result, igraph_int_t from, igraph_int_t to, void *extra ) { igraphmodule_i_Graph_get_shortest_path_astar_callback_data_t* data = @@ -5744,7 +5735,7 @@ PyObject *igraphmodule_Graph_get_shortest_path_astar( static char *kwlist[] = { "v", "to", "heuristics", "weights", "mode", "output", NULL }; igraph_vector_t *weights=0; igraph_neimode_t mode = IGRAPH_OUT; - igraph_integer_t from, to; + igraph_int_t from, to; PyObject *list, *mode_o=Py_None, *weights_o=Py_None, *output_o=Py_None, *from_o = Py_None, *to_o=Py_None, *heuristics_o; @@ -5815,7 +5806,7 @@ PyObject *igraphmodule_Graph_get_shortest_paths(igraphmodule_GraphObject * igraph_vector_t *weights = NULL; igraph_neimode_t mode = IGRAPH_OUT; igraphmodule_shortest_path_algorithm_t algorithm = IGRAPHMODULE_SHORTEST_PATH_ALGORITHM_AUTO; - igraph_integer_t from, no_of_target_nodes; + igraph_int_t from, no_of_target_nodes; igraph_vs_t to; PyObject *list, *mode_o=Py_None, *weights_o=Py_None, *output_o=Py_None, *from_o = Py_None, *to_o=Py_None, @@ -5866,14 +5857,15 @@ PyObject *igraphmodule_Graph_get_shortest_paths(igraphmodule_GraphObject * return NULL; } - if (algorithm == IGRAPHMODULE_SHORTEST_PATH_ALGORITHM_AUTO) { - algorithm = igraphmodule_select_shortest_path_algorithm( - &self->g, weights, NULL, mode, /* allow_johnson = */ false - ); - } - /* Call the C function */ switch (algorithm) { + case IGRAPHMODULE_SHORTEST_PATH_ALGORITHM_AUTO: + retval = igraph_get_shortest_paths( + &self->g, weights, use_edges ? NULL : &veclist, use_edges ? &veclist : NULL, + from, to, mode, NULL, NULL + ); + break; + case IGRAPHMODULE_SHORTEST_PATH_ALGORITHM_DIJKSTRA: retval = igraph_get_shortest_paths_dijkstra( &self->g, use_edges ? NULL : &veclist, use_edges ? &veclist : NULL, @@ -5924,7 +5916,7 @@ PyObject *igraphmodule_Graph_get_all_shortest_paths(igraphmodule_GraphObject * igraph_vector_int_list_t res; igraph_vector_t *weights = 0; igraph_neimode_t mode = IGRAPH_OUT; - igraph_integer_t from; + igraph_int_t from; igraph_vs_t to; PyObject *list, *from_o, *mode_o=Py_None, *to_o=Py_None, *weights_o=Py_None; @@ -5984,9 +5976,9 @@ PyObject *igraphmodule_Graph_get_k_shortest_paths( igraph_vector_int_list_t res; igraph_vector_t *weights = 0; igraph_neimode_t mode = IGRAPH_OUT; - igraph_integer_t from; - igraph_integer_t to; - igraph_integer_t k = 1; + igraph_int_t from; + igraph_int_t to; + igraph_int_t k = 1; PyObject *list, *from_o, *to_o; PyObject *output_o = Py_None, *mode_o = Py_None, *weights_o = Py_None, *k_o = NULL; igraph_bool_t use_edges = false; @@ -6052,22 +6044,26 @@ PyObject *igraphmodule_Graph_get_all_simple_paths(igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { - static char *kwlist[] = { "v", "to", "cutoff", "mode", NULL }; - igraph_vector_int_t res; + static char *kwlist[] = { "v", "to", "minlen", "maxlen", "mode", "max_results", NULL }; + igraph_vector_int_list_t res; igraph_neimode_t mode = IGRAPH_OUT; - igraph_integer_t from; + igraph_int_t from; igraph_vs_t to; - igraph_integer_t cutoff; - PyObject *list, *from_o, *mode_o=Py_None, *to_o=Py_None, *cutoff_o=Py_None; + igraph_int_t minlen, maxlen, max_results = IGRAPH_UNLIMITED; + PyObject *list, *from_o, *mode_o = Py_None, *to_o = Py_None, *max_results_o = Py_None; + PyObject *minlen_o = Py_None, *maxlen_o = Py_None; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OOO", kwlist, &from_o, - &to_o, &cutoff_o, &mode_o)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OOOOO", kwlist, &from_o, + &to_o, &minlen_o, &maxlen_o, &mode_o, &max_results_o)) return NULL; if (igraphmodule_PyObject_to_neimode_t(mode_o, &mode)) return NULL; - if (igraphmodule_PyObject_to_integer_t(cutoff_o, &cutoff)) + if (igraphmodule_PyObject_to_integer_t(minlen_o, &minlen)) + return NULL; + + if (igraphmodule_PyObject_to_integer_t(maxlen_o, &maxlen)) return NULL; if (igraphmodule_PyObject_to_vid(from_o, &from, &self->g)) @@ -6076,24 +6072,27 @@ PyObject *igraphmodule_Graph_get_all_simple_paths(igraphmodule_GraphObject * if (igraphmodule_PyObject_to_vs_t(to_o, &to, &self->g, 0, 0)) return NULL; - if (igraph_vector_int_init(&res, 0)) { + if (igraphmodule_PyObject_to_max_results_t(max_results_o, &max_results)) + return NULL; + + if (igraph_vector_int_list_init(&res, 0)) { igraphmodule_handle_igraph_error(); igraph_vs_destroy(&to); return NULL; } - if (igraph_get_all_simple_paths(&self->g, &res, from, to, cutoff, mode)) { + if (igraph_get_all_simple_paths(&self->g, &res, from, to, mode, minlen, maxlen, max_results)) { igraphmodule_handle_igraph_error(); - igraph_vector_int_destroy(&res); + igraph_vector_int_list_destroy(&res); igraph_vs_destroy(&to); return NULL; } igraph_vs_destroy(&to); - list = igraphmodule_vector_int_t_to_PyList(&res); + list = igraphmodule_vector_int_list_t_to_PyList(&res); - igraph_vector_int_destroy(&res); + igraph_vector_int_list_destroy(&res); return list; } @@ -6114,6 +6113,8 @@ PyObject *igraphmodule_Graph_hub_score( igraph_real_t value; igraph_vector_t res, *weights = 0; + /* scale is deprecated but kept for backward compatibility reasons */ + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO!O", kwlist, &weights_o, &scale_o, igraphmodule_ARPACKOptionsType, &arpack_options, &return_eigenvalue)) @@ -6125,7 +6126,7 @@ PyObject *igraphmodule_Graph_hub_score( ATTRIBUTE_TYPE_EDGE)) return NULL; arpack_options = (igraphmodule_ARPACKOptionsObject*)arpack_options_o; - if (igraph_hub_and_authority_scores(&self->g, &res, NULL, &value, PyObject_IsTrue(scale_o), + if (igraph_hub_and_authority_scores(&self->g, &res, NULL, &value, weights, igraphmodule_ARPACKOptions_get(arpack_options))) { igraphmodule_handle_igraph_error(); if (weights) { igraph_vector_destroy(weights); free(weights); } @@ -6482,11 +6483,11 @@ PyObject *igraphmodule_Graph_personalized_pagerank(igraphmodule_GraphObject *sel } if (rvsobj != Py_None) - retval = igraph_personalized_pagerank_vs(&self->g, algo, &res, 0, vs, - PyObject_IsTrue(directed), damping, reset_vs, &weights, opts); + retval = igraph_personalized_pagerank_vs(&self->g, &weights, &res, 0, reset_vs, + damping, PyObject_IsTrue(directed), vs, algo, opts); else - retval = igraph_personalized_pagerank(&self->g, algo, &res, 0, vs, - PyObject_IsTrue(directed), damping, reset, &weights, opts); + retval = igraph_personalized_pagerank(&self->g, &weights, &res, 0, reset, + damping, PyObject_IsTrue(directed), vs, algo, opts); if (retval) { igraphmodule_handle_igraph_error(); @@ -6579,12 +6580,12 @@ PyObject *igraphmodule_Graph_permute_vertices(igraphmodule_GraphObject *self, PyObject *igraphmodule_Graph_rewire(igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { - static char *kwlist[] = { "n", "mode", NULL }; - PyObject *n_o = Py_None, *mode_o = Py_None; - igraph_integer_t n = 10 * igraph_ecount(&self->g); /* TODO overflow check */ - igraph_rewiring_t mode = IGRAPH_REWIRING_SIMPLE; + static char *kwlist[] = { "n", "allowed_edge_types", NULL }; + PyObject *n_o = Py_None, *allowed_edge_types_o = Py_None; + igraph_int_t n = 10 * igraph_ecount(&self->g); /* TODO overflow check */ + igraph_edge_type_sw_t allowed_edge_types = IGRAPH_SIMPLE_SW; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO", kwlist, &n_o, &mode_o)) { + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO", kwlist, &n_o, &allowed_edge_types_o)) { return NULL; } @@ -6594,11 +6595,11 @@ PyObject *igraphmodule_Graph_rewire(igraphmodule_GraphObject * self, } } - if (igraphmodule_PyObject_to_rewiring_t(mode_o, &mode)) { + if (igraphmodule_PyObject_to_edge_type_sw_t(allowed_edge_types_o, &allowed_edge_types)) { return NULL; } - if (igraph_rewire(&self->g, n, mode)) { + if (igraph_rewire(&self->g, n, allowed_edge_types, /* rewiring_stats = */ NULL)) { igraphmodule_handle_igraph_error(); return NULL; } @@ -6614,16 +6615,20 @@ PyObject *igraphmodule_Graph_rewire(igraphmodule_GraphObject * self, PyObject *igraphmodule_Graph_rewire_edges(igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { - static char *kwlist[] = { "prob", "loops", "multiple", NULL }; + static char *kwlist[] = { "prob", "allowed_edge_types", NULL }; double prob; - PyObject *loops_o = Py_False, *multiple_o = Py_False; + PyObject *edge_types_o = Py_None; + igraph_edge_type_sw_t allowed_edge_types = IGRAPH_SIMPLE_SW; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "d|OO", kwlist, - &prob, &loops_o, &multiple_o)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "d|O", kwlist, + &prob, &edge_types_o)) return NULL; - if (igraph_rewire_edges(&self->g, prob, PyObject_IsTrue(loops_o), - PyObject_IsTrue(multiple_o))) { + if (igraphmodule_PyObject_to_edge_type_sw_t(edge_types_o, &allowed_edge_types)) { + return NULL; + } + + if (igraph_rewire_edges(&self->g, prob, allowed_edge_types)) { igraphmodule_handle_igraph_error(); return NULL; } @@ -6686,19 +6691,12 @@ PyObject *igraphmodule_Graph_distances( return igraphmodule_handle_igraph_error(); } - if (algorithm == IGRAPHMODULE_SHORTEST_PATH_ALGORITHM_AUTO) { - algorithm = igraphmodule_select_shortest_path_algorithm( - &self->g, weights, &from_vs, mode, /* allow_johnson = */ true - ); - } - - if (algorithm == IGRAPHMODULE_SHORTEST_PATH_ALGORITHM_JOHNSON && mode != IGRAPH_OUT) { - PyErr_SetString(PyExc_ValueError, "Johnson's algorithm is supported for mode=\"out\" only"); - goto cleanup; - } - /* Call the C function */ switch (algorithm) { + case IGRAPHMODULE_SHORTEST_PATH_ALGORITHM_AUTO: + retval = igraph_distances(&self->g, weights, &res, from_vs, to_vs, mode); + break; + case IGRAPHMODULE_SHORTEST_PATH_ALGORITHM_DIJKSTRA: retval = igraph_distances_dijkstra(&self->g, &res, from_vs, to_vs, weights, mode); break; @@ -6708,7 +6706,7 @@ PyObject *igraphmodule_Graph_distances( break; case IGRAPHMODULE_SHORTEST_PATH_ALGORITHM_JOHNSON: - retval = igraph_distances_johnson(&self->g, &res, from_vs, to_vs, weights); + retval = igraph_distances_johnson(&self->g, &res, from_vs, to_vs, weights, mode); break; default: @@ -6765,25 +6763,35 @@ PyObject *igraphmodule_Graph_similarity_jaccard(igraphmodule_GraphObject * self, if (pairs_o == Py_None) { /* Case #1: vertices, returning matrix */ igraph_matrix_t res; - igraph_vs_t vs; + igraph_vs_t vs_from; + igraph_vs_t vs_to; igraph_bool_t return_single = false; - if (igraphmodule_PyObject_to_vs_t(vertices_o, &vs, &self->g, &return_single, 0)) + if (igraphmodule_PyObject_to_vs_t(vertices_o, &vs_from, &self->g, &return_single, 0)) return NULL; + /* TODO(ntamas): support separate vs_from and vs_to arguments */ + if (igraphmodule_PyObject_to_vs_t(vertices_o, &vs_to, &self->g, &return_single, 0)) { + igraph_vs_destroy(&vs_from); + return NULL; + } + if (igraph_matrix_init(&res, 0, 0)) { - igraph_vs_destroy(&vs); + igraph_vs_destroy(&vs_from); + igraph_vs_destroy(&vs_to); return igraphmodule_handle_igraph_error(); } - if (igraph_similarity_jaccard(&self->g, &res, vs, mode, PyObject_IsTrue(loops))) { + if (igraph_similarity_jaccard(&self->g, &res, vs_from, vs_to, mode, PyObject_IsTrue(loops))) { igraph_matrix_destroy(&res); - igraph_vs_destroy(&vs); + igraph_vs_destroy(&vs_from); + igraph_vs_destroy(&vs_to); igraphmodule_handle_igraph_error(); return NULL; } - igraph_vs_destroy(&vs); + igraph_vs_destroy(&vs_from); + igraph_vs_destroy(&vs_to); list = igraphmodule_matrix_t_to_PyList(&res, IGRAPHMODULE_TYPE_FLOAT); igraph_matrix_destroy(&res); @@ -6851,25 +6859,35 @@ PyObject *igraphmodule_Graph_similarity_dice(igraphmodule_GraphObject * self, if (pairs_o == Py_None) { /* Case #1: vertices, returning matrix */ igraph_matrix_t res; - igraph_vs_t vs; + igraph_vs_t vs_from; + igraph_vs_t vs_to; igraph_bool_t return_single = false; - if (igraphmodule_PyObject_to_vs_t(vertices_o, &vs, &self->g, &return_single, 0)) + if (igraphmodule_PyObject_to_vs_t(vertices_o, &vs_from, &self->g, &return_single, 0)) return NULL; + /* TODO(ntamas): support separate vs_from and vs_to arguments */ + if (igraphmodule_PyObject_to_vs_t(vertices_o, &vs_to, &self->g, &return_single, 0)) { + igraph_vs_destroy(&vs_from); + return NULL; + } + if (igraph_matrix_init(&res, 0, 0)) { - igraph_vs_destroy(&vs); + igraph_vs_destroy(&vs_from); + igraph_vs_destroy(&vs_to); return igraphmodule_handle_igraph_error(); } - if (igraph_similarity_dice(&self->g, &res, vs, mode, PyObject_IsTrue(loops))) { + if (igraph_similarity_dice(&self->g, &res, vs_from, vs_to, mode, PyObject_IsTrue(loops))) { igraph_matrix_destroy(&res); - igraph_vs_destroy(&vs); + igraph_vs_destroy(&vs_from); + igraph_vs_destroy(&vs_to); igraphmodule_handle_igraph_error(); return NULL; } - igraph_vs_destroy(&vs); + igraph_vs_destroy(&vs_from); + igraph_vs_destroy(&vs_to); list = igraphmodule_matrix_t_to_PyList(&res, IGRAPHMODULE_TYPE_FLOAT); igraph_matrix_destroy(&res); @@ -6962,12 +6980,16 @@ PyObject *igraphmodule_Graph_similarity_inverse_log_weighted( PyObject *igraphmodule_Graph_spanning_tree(igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { - static char *kwlist[] = { "weights", NULL }; + static char *kwlist[] = { "weights", "method", NULL }; igraph_vector_t* ws = 0; igraph_vector_int_t res; - PyObject *weights_o = Py_None, *result_o = NULL; + igraph_mst_algorithm_t method = IGRAPH_MST_AUTOMATIC; + PyObject *weights_o = Py_None, *result_o = NULL, *method_o = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO", kwlist, &weights_o, &method_o)) + return NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwlist, &weights_o)) + if (igraphmodule_PyObject_to_mst_algorithm_t(method_o, &method)) return NULL; if (igraph_vector_int_init(&res, 0)) { @@ -6980,14 +7002,19 @@ PyObject *igraphmodule_Graph_spanning_tree(igraphmodule_GraphObject * self, return NULL; } - if (igraph_minimum_spanning_tree(&self->g, &res, ws)) { - if (ws != 0) { igraph_vector_destroy(ws); free(ws); } + if (igraph_minimum_spanning_tree(&self->g, &res, ws, method)) { + if (ws != 0) { + igraph_vector_destroy(ws); free(ws); + } igraph_vector_int_destroy(&res); igraphmodule_handle_igraph_error(); return NULL; } - if (ws != 0) { igraph_vector_destroy(ws); free(ws); } + if (ws != 0) { + igraph_vector_destroy(ws); free(ws); + } + result_o = igraphmodule_vector_int_t_to_PyList(&res); igraph_vector_int_destroy(&res); return result_o; @@ -7036,7 +7063,7 @@ PyObject *igraphmodule_Graph_subcomponent(igraphmodule_GraphObject * self, static char *kwlist[] = { "v", "mode", NULL }; igraph_vector_int_t res; igraph_neimode_t mode = IGRAPH_ALL; - igraph_integer_t from; + igraph_int_t from; PyObject *list = NULL, *mode_o = Py_None, *from_o = Py_None; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O", kwlist, &from_o, &mode_o)) @@ -7314,7 +7341,7 @@ PyObject *igraphmodule_Graph_vertex_connectivity(igraphmodule_GraphObject *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = { "source", "target", "checks", "neighbors", NULL }; PyObject *checks = Py_True, *neis = Py_None, *source_o = Py_None, *target_o = Py_None; - igraph_integer_t source = -1, target = -1, res; + igraph_int_t source = -1, target = -1, res; igraph_vconn_nei_t neighbors = IGRAPH_VCONN_NEI_ERROR; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOO", kwlist, @@ -7478,7 +7505,7 @@ typedef struct { } igraphmodule_i_Graph_motifs_randesu_callback_data_t; igraph_error_t igraphmodule_i_Graph_motifs_randesu_callback(const igraph_t *graph, - igraph_vector_int_t *vids, igraph_integer_t isoclass, void* extra) { + const igraph_vector_int_t *vids, igraph_int_t isoclass, void* extra) { igraphmodule_i_Graph_motifs_randesu_callback_data_t* data = (igraphmodule_i_Graph_motifs_randesu_callback_data_t*)extra; PyObject* vector; @@ -7581,7 +7608,7 @@ PyObject *igraphmodule_Graph_motifs_randesu(igraphmodule_GraphObject *self, PyObject *igraphmodule_Graph_motifs_randesu_no(igraphmodule_GraphObject *self, PyObject *args, PyObject *kwds) { igraph_vector_t cut_prob; - igraph_integer_t res; + igraph_real_t res; Py_ssize_t size = 3; PyObject* cut_prob_list=Py_None; static char* kwlist[] = {"size", "cut_prob", NULL}; @@ -7608,7 +7635,7 @@ PyObject *igraphmodule_Graph_motifs_randesu_no(igraphmodule_GraphObject *self, } igraph_vector_destroy(&cut_prob); - return igraphmodule_integer_t_to_PyObject(res); + return igraphmodule_real_t_to_PyObject(res, IGRAPHMODULE_TYPE_FLOAT_IF_FRACTIONAL_ELSE_INT); } /** \ingroup python_interface_graph @@ -7619,7 +7646,7 @@ PyObject *igraphmodule_Graph_motifs_randesu_no(igraphmodule_GraphObject *self, PyObject *igraphmodule_Graph_motifs_randesu_estimate(igraphmodule_GraphObject *self, PyObject *args, PyObject *kwds) { igraph_vector_t cut_prob; - igraph_integer_t res; + igraph_real_t res; Py_ssize_t size = 3; PyObject* cut_prob_list=Py_None; PyObject *sample=Py_None; @@ -7649,7 +7676,7 @@ PyObject *igraphmodule_Graph_motifs_randesu_estimate(igraphmodule_GraphObject *s if (PyLong_Check(sample)) { /* samples chosen randomly */ - igraph_integer_t ns; + igraph_int_t ns; if (igraphmodule_PyObject_to_integer_t(sample, &ns)) { igraph_vector_destroy(&cut_prob); return NULL; @@ -7677,7 +7704,7 @@ PyObject *igraphmodule_Graph_motifs_randesu_estimate(igraphmodule_GraphObject *s } igraph_vector_destroy(&cut_prob); - return igraphmodule_integer_t_to_PyObject(res); + return igraphmodule_real_t_to_PyObject(res, IGRAPHMODULE_TYPE_FLOAT_IF_FRACTIONAL_ELSE_INT); } /** \ingroup python_interface_graph @@ -7743,13 +7770,15 @@ PyObject *igraphmodule_Graph_fundamental_cycles( ) { PyObject *cutoff_o = Py_None; PyObject *start_vid_o = Py_None; + PyObject *weights_o = Py_None; PyObject *result_o; - igraph_integer_t cutoff = -1, start_vid = -1; + igraph_int_t cutoff = -1, start_vid = -1; igraph_vector_int_list_t result; + igraph_vector_t *weights = 0; - static char *kwlist[] = { "start_vid", "cutoff", NULL }; + static char *kwlist[] = { "start_vid", "cutoff", "weights", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO", kwlist, &start_vid_o, &cutoff_o)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO", kwlist, &start_vid_o, &cutoff_o, &weights_o)) return NULL; if (igraphmodule_PyObject_to_optional_vid(start_vid_o, &start_vid, &self->g)) @@ -7758,17 +7787,31 @@ PyObject *igraphmodule_Graph_fundamental_cycles( if (cutoff_o != Py_None && igraphmodule_PyObject_to_integer_t(cutoff_o, &cutoff)) return NULL; + if (igraphmodule_attrib_to_vector_t(weights_o, self, &weights, ATTRIBUTE_TYPE_EDGE)) { + return NULL; + } + if (igraph_vector_int_list_init(&result, 0)) { + if (weights) { + igraph_vector_destroy(weights); free(weights); + } igraphmodule_handle_igraph_error(); return NULL; } - if (igraph_fundamental_cycles(&self->g, &result, start_vid, cutoff, /* weights = */ NULL)) { + if (igraph_fundamental_cycles(&self->g, weights, &result, start_vid, cutoff)) { igraph_vector_int_list_destroy(&result); + if (weights) { + igraph_vector_destroy(weights); free(weights); + } igraphmodule_handle_igraph_error(); return NULL; } + if (weights) { + igraph_vector_destroy(weights); free(weights); + } + result_o = igraphmodule_vector_int_list_t_to_PyList_of_tuples(&result); igraph_vector_int_list_destroy(&result); @@ -7781,38 +7824,134 @@ PyObject *igraphmodule_Graph_minimum_cycle_basis( PyObject *cutoff_o = Py_None; PyObject *complete_o = Py_True; PyObject *use_cycle_order_o = Py_True; + PyObject *weights_o = Py_None; PyObject *result_o; - igraph_integer_t cutoff = -1; + igraph_int_t cutoff = -1; igraph_vector_int_list_t result; + igraph_vector_t *weights; - static char *kwlist[] = { "cutoff", "complete", "use_cycle_order", NULL }; + static char *kwlist[] = { "cutoff", "complete", "use_cycle_order", "weights", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO", kwlist, &cutoff_o, &complete_o, &use_cycle_order_o)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOO", kwlist, &cutoff_o, &complete_o, &use_cycle_order_o, &weights_o)) return NULL; if (cutoff_o != Py_None && igraphmodule_PyObject_to_integer_t(cutoff_o, &cutoff)) return NULL; + if (igraphmodule_attrib_to_vector_t(weights_o, self, &weights, ATTRIBUTE_TYPE_EDGE)) { + return NULL; + } + if (igraph_vector_int_list_init(&result, 0)) { + if (weights) { + igraph_vector_destroy(weights); free(weights); + } igraphmodule_handle_igraph_error(); return NULL; } if (igraph_minimum_cycle_basis( - &self->g, &result, cutoff, PyObject_IsTrue(complete_o), - PyObject_IsTrue(use_cycle_order_o), /* weights = */ NULL + &self->g, weights, &result, cutoff, PyObject_IsTrue(complete_o), + PyObject_IsTrue(use_cycle_order_o) )) { igraph_vector_int_list_destroy(&result); + if (weights) { + igraph_vector_destroy(weights); free(weights); + } igraphmodule_handle_igraph_error(); return NULL; } + if (weights) { + igraph_vector_destroy(weights); free(weights); + } + result_o = igraphmodule_vector_int_list_t_to_PyList_of_tuples(&result); igraph_vector_int_list_destroy(&result); return result_o; } + +PyObject *igraphmodule_Graph_simple_cycles( + igraphmodule_GraphObject *self, PyObject *args, PyObject *kwds +) { + PyObject *mode_o = Py_None; + PyObject *output_o = Py_None; + PyObject *min_cycle_length_o = Py_None; + PyObject *max_cycle_length_o = Py_None; + PyObject *max_results_o = Py_None; + + // argument defaults: no cycle limits + igraph_int_t mode = IGRAPH_OUT; + igraph_int_t min_cycle_length = -1; + igraph_int_t max_cycle_length = -1; + igraph_int_t max_results = IGRAPH_UNLIMITED; + igraph_bool_t use_edges = false; + + static char *kwlist[] = { "mode", "min", "max", "output", "max_results", NULL }; + + if ( + !PyArg_ParseTupleAndKeywords( + args, kwds, "|OOOOO", kwlist, + &mode_o, &min_cycle_length_o, &max_cycle_length_o, &output_o, &max_results_o + ) + ) + return NULL; + + if (mode_o != Py_None && igraphmodule_PyObject_to_integer_t(mode_o, &mode)) + return NULL; + + if (min_cycle_length_o != Py_None && igraphmodule_PyObject_to_integer_t(min_cycle_length_o, &min_cycle_length)) + return NULL; + + if (max_cycle_length_o != Py_None && igraphmodule_PyObject_to_integer_t(max_cycle_length_o, &max_cycle_length)) + return NULL; + + if (igraphmodule_PyObject_to_max_results_t(max_results_o, &max_results)) + return NULL; + + if (igraphmodule_PyObject_to_vpath_or_epath(output_o, &use_edges)) + return NULL; + + igraph_vector_int_list_t vertices; + if (igraph_vector_int_list_init(&vertices, 0)) { + igraphmodule_handle_igraph_error(); + return NULL; + } + igraph_vector_int_list_t edges; + if (igraph_vector_int_list_init(&edges, 0)) { + igraph_vector_int_list_destroy(&vertices); + igraphmodule_handle_igraph_error(); + return NULL; + } + + if (igraph_simple_cycles( + &self->g, + use_edges ? NULL : &vertices, + use_edges ? &edges : NULL, + mode, min_cycle_length, max_cycle_length, + max_results + )) { + igraph_vector_int_list_destroy(&vertices); + igraph_vector_int_list_destroy(&edges); + igraphmodule_handle_igraph_error(); + return NULL; + } + + PyObject *result_o; + + if (use_edges) { + result_o = igraphmodule_vector_int_list_t_to_PyList_of_tuples(&edges); + } else { + result_o = igraphmodule_vector_int_list_t_to_PyList_of_tuples(&vertices); + } + igraph_vector_int_list_destroy(&edges); + igraph_vector_int_list_destroy(&vertices); + + return result_o; +} + /********************************************************************** * Graph layout algorithms * **********************************************************************/ @@ -7989,7 +8128,7 @@ PyObject *igraphmodule_Graph_layout_star(igraphmodule_GraphObject* self, igraph_matrix_t m; PyObject *result_o, *order_o = Py_None, *center_o = Py_None; - igraph_integer_t center = 0; + igraph_int_t center = 0; igraph_vector_int_t* order = 0; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO", kwlist, @@ -8049,7 +8188,7 @@ PyObject *igraphmodule_Graph_layout_kamada_kawai(igraphmodule_GraphObject * igraph_matrix_t m; igraph_bool_t use_seed = false; igraph_error_t ret; - igraph_integer_t maxiter; + igraph_int_t maxiter; Py_ssize_t dim = 2; igraph_real_t kkconst; double epsilon = 0.0; @@ -8165,14 +8304,15 @@ PyObject *igraphmodule_Graph_layout_kamada_kawai(igraphmodule_GraphObject * return NULL; } - if (dim == 2) + if (dim == 2) { ret = igraph_layout_kamada_kawai (&self->g, &m, use_seed, maxiter, epsilon, kkconst, weights, /*bounds*/ minx, maxx, miny, maxy); - else + } else { ret = igraph_layout_kamada_kawai_3d (&self->g, &m, use_seed, maxiter, epsilon, kkconst, weights, /*bounds*/ minx, maxx, miny, maxy, minz, maxz); + } DESTROY_VECTORS; @@ -8184,6 +8324,19 @@ PyObject *igraphmodule_Graph_layout_kamada_kawai(igraphmodule_GraphObject * return NULL; } + /* Align layout, but only if no bounding box was specified. */ + if (minx == NULL && maxx == NULL && + miny == NULL && maxy == NULL && + minz == NULL && maxz == NULL && + igraph_vcount(&self->g) <= 1000) { + ret = igraph_layout_align(&self->g, &m); + if (ret) { + igraph_matrix_destroy(&m); + igraphmodule_handle_igraph_error(); + return NULL; + } + } + result_o = igraphmodule_matrix_t_to_PyList(&m, IGRAPHMODULE_TYPE_FLOAT); igraph_matrix_destroy(&m); return (PyObject *) result_o; @@ -8231,7 +8384,7 @@ PyObject* igraphmodule_Graph_layout_davidson_harel(igraphmodule_GraphObject *sel } if (weight_edge_lengths < 0 || weight_edge_crossings < 0 || weight_node_edge_dist < 0) { - if (igraph_density(&self->g, &density, 0)) { + if (igraph_density(&self->g, 0, &density, 0)) { igraphmodule_handle_igraph_error(); return NULL; } @@ -8275,6 +8428,16 @@ PyObject* igraphmodule_Graph_layout_davidson_harel(igraphmodule_GraphObject *sel return NULL; } + /* Align layout */ + if (igraph_vcount(&self->g)) { + retval = igraph_layout_align(&self->g, &m); + if (retval) { + igraph_matrix_destroy(&m); + igraphmodule_handle_igraph_error(); + return NULL; + } + } + result_o = igraphmodule_matrix_t_to_PyList(&m, IGRAPHMODULE_TYPE_FLOAT); igraph_matrix_destroy(&m); return (PyObject *) result_o; @@ -8497,6 +8660,19 @@ PyObject return NULL; } + /* Align layout, but only if no bounding box was specified. */ + if (minx == NULL && maxx == NULL && + miny == NULL && maxy == NULL && + minz == NULL && maxz == NULL && + igraph_vcount(&self->g) <= 1000) { + ret = igraph_layout_align(&self->g, &m); + if (ret) { + igraph_matrix_destroy(&m); + igraphmodule_handle_igraph_error(); + return NULL; + } + } + #undef DESTROY_VECTORS result_o = igraphmodule_matrix_t_to_PyList(&m, IGRAPHMODULE_TYPE_FLOAT); @@ -8551,6 +8727,15 @@ PyObject *igraphmodule_Graph_layout_graphopt(igraphmodule_GraphObject *self, return NULL; } + /* Align layout */ + if (igraph_vcount(&self->g) <= 1000) { + if (igraph_layout_align(&self->g, &m)) { + igraph_matrix_destroy(&m); + igraphmodule_handle_igraph_error(); + return NULL; + } + } + result_o = igraphmodule_matrix_t_to_PyList(&m, IGRAPHMODULE_TYPE_FLOAT); igraph_matrix_destroy(&m); return (PyObject *) result_o; @@ -8570,7 +8755,7 @@ PyObject *igraphmodule_Graph_layout_lgl(igraphmodule_GraphObject * self, igraph_matrix_t m; PyObject *result_o, *root_o = Py_None; Py_ssize_t maxiter = 150; - igraph_integer_t proot = -1; + igraph_int_t proot = -1; double maxdelta, area, coolexp, repulserad, cellsize; maxdelta = igraph_vcount(&self->g); @@ -8609,6 +8794,15 @@ PyObject *igraphmodule_Graph_layout_lgl(igraphmodule_GraphObject * self, return NULL; } + /* Align layout */ + if (igraph_vcount(&self->g) <= 1000) { + if (igraph_layout_align(&self->g, &m)) { + igraph_matrix_destroy(&m); + igraphmodule_handle_igraph_error(); + return NULL; + } + } + result_o = igraphmodule_matrix_t_to_PyList(&m, IGRAPHMODULE_TYPE_FLOAT); igraph_matrix_destroy(&m); return (PyObject *) result_o; @@ -8674,6 +8868,15 @@ PyObject *igraphmodule_Graph_layout_mds(igraphmodule_GraphObject * self, igraph_matrix_destroy(dist); free(dist); } + /* Align layout */ + if (igraph_vcount(&self->g) <= 1000) { + if (igraph_layout_align(&self->g, &m)) { + igraph_matrix_destroy(&m); + igraphmodule_handle_igraph_error(); + return NULL; + } + } + result_o = igraphmodule_matrix_t_to_PyList(&m, IGRAPHMODULE_TYPE_FLOAT); igraph_matrix_destroy(&m); return (PyObject *) result_o; @@ -8807,40 +9010,36 @@ PyObject *igraphmodule_Graph_layout_reingold_tilford_circular( PyObject *igraphmodule_Graph_layout_sugiyama( igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { - static char *kwlist[] = { "layers", "weights", "hgap", "vgap", "maxiter", - "return_extended_graph", NULL }; + static char *kwlist[] = { "layers", "weights", "hgap", "vgap", "maxiter", NULL }; igraph_matrix_t m; - igraph_t extd_graph; - igraph_vector_int_t extd_to_orig_eids; igraph_vector_t *weights = 0; igraph_vector_int_t *layers = 0; double hgap = 1, vgap = 1; Py_ssize_t maxiter = 100; - PyObject *layers_o = Py_None, *weights_o = Py_None, *extd_to_orig_eids_o = Py_None; - PyObject *return_extended_graph = Py_False; - PyObject *result_o; - igraphmodule_GraphObject *graph_o; + PyObject *layers_o = Py_None, *weights_o = Py_None; + PyObject *layout_o, *routing_o; + igraph_matrix_list_t routing; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOddnO", kwlist, - &layers_o, &weights_o, &hgap, &vgap, &maxiter, &return_extended_graph)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOddn", kwlist, + &layers_o, &weights_o, &hgap, &vgap, &maxiter)) return NULL; CHECK_SSIZE_T_RANGE_POSITIVE(maxiter, "maximum number of iterations"); - if (igraph_vector_int_init(&extd_to_orig_eids, 0)) { + if (igraph_matrix_list_init(&routing, 0)) { igraphmodule_handle_igraph_error(); return NULL; } if (igraph_matrix_init(&m, 1, 1)) { - igraph_vector_int_destroy(&extd_to_orig_eids); + igraph_matrix_list_destroy(&routing); igraphmodule_handle_igraph_error(); return NULL; } if (igraphmodule_attrib_to_vector_int_t(layers_o, self, &layers, ATTRIBUTE_TYPE_VERTEX)) { - igraph_vector_int_destroy(&extd_to_orig_eids); + igraph_matrix_list_destroy(&routing); igraph_matrix_destroy(&m); return NULL; } @@ -8848,46 +9047,39 @@ PyObject *igraphmodule_Graph_layout_sugiyama( if (igraphmodule_attrib_to_vector_t(weights_o, self, &weights, ATTRIBUTE_TYPE_EDGE)) { if (layers != 0) { igraph_vector_int_destroy(layers); free(layers); } - igraph_vector_int_destroy(&extd_to_orig_eids); + igraph_matrix_list_destroy(&routing); igraph_matrix_destroy(&m); return NULL; } - if (igraph_layout_sugiyama(&self->g, &m, - (PyObject_IsTrue(return_extended_graph) ? &extd_graph : 0), - (PyObject_IsTrue(return_extended_graph) ? &extd_to_orig_eids : 0), + if (igraph_layout_sugiyama(&self->g, &m, &routing, layers, hgap, vgap, maxiter, weights)) { if (layers != 0) { igraph_vector_int_destroy(layers); free(layers); } if (weights != 0) { igraph_vector_destroy(weights); free(weights); } - igraph_vector_int_destroy(&extd_to_orig_eids); + igraph_matrix_list_destroy(&routing); igraph_matrix_destroy(&m); igraphmodule_handle_igraph_error(); return NULL; } - if (layers != 0) { igraph_vector_int_destroy(layers); free(layers); } - if (weights != 0) { igraph_vector_destroy(weights); free(weights); } - - result_o = igraphmodule_matrix_t_to_PyList(&m, IGRAPHMODULE_TYPE_FLOAT); - if (result_o == NULL) { - igraph_vector_int_destroy(&extd_to_orig_eids); + layout_o = igraphmodule_matrix_t_to_PyList(&m, IGRAPHMODULE_TYPE_FLOAT); + if (layout_o == NULL) { + igraph_matrix_list_destroy(&routing); igraph_matrix_destroy(&m); return NULL; } igraph_matrix_destroy(&m); - if (PyObject_IsTrue(return_extended_graph)) { - CREATE_GRAPH(graph_o, extd_graph); - if (graph_o == NULL) { - Py_DECREF(result_o); - } - extd_to_orig_eids_o = igraphmodule_vector_int_t_to_PyList(&extd_to_orig_eids); - result_o = Py_BuildValue("NNN", result_o, graph_o, extd_to_orig_eids_o); + routing_o = igraphmodule_matrix_list_t_to_PyList(&routing); + if (routing_o == NULL) { + igraph_matrix_list_destroy(&routing); + return NULL; } - igraph_vector_int_destroy(&extd_to_orig_eids); - return (PyObject *) result_o; + igraph_matrix_list_destroy(&routing); + + return Py_BuildValue("NN", layout_o, routing_o); } /** \ingroup python_interface_graph @@ -8977,7 +9169,7 @@ PyObject *igraphmodule_Graph_layout_umap( use_seed, dist, (igraph_real_t)min_dist, - (igraph_integer_t)epochs, + (igraph_int_t)epochs, distances_are_weights)) { if (dist) { igraph_vector_destroy(dist); free(dist); @@ -8991,7 +9183,7 @@ PyObject *igraphmodule_Graph_layout_umap( use_seed, dist, (igraph_real_t)min_dist, - (igraph_integer_t)epochs, + (igraph_int_t)epochs, distances_are_weights)) { if (dist) { igraph_vector_destroy(dist); free(dist); @@ -9118,13 +9310,14 @@ PyObject *igraphmodule_Graph_get_adjacency(igraphmodule_GraphObject * self, PyObject *igraphmodule_Graph_get_biadjacency(igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { - static char *kwlist[] = { "types", NULL }; + static char *kwlist[] = { "types", "weights", NULL }; igraph_matrix_t matrix; igraph_vector_int_t row_ids, col_ids; igraph_vector_bool_t *types; - PyObject *matrix_o, *row_ids_o, *col_ids_o, *types_o; + igraph_vector_t *weights = 0; + PyObject *matrix_o, *row_ids_o, *col_ids_o, *types_o, *weights_o = Py_None; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &types_o)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O", kwlist, &types_o)) return NULL; if (igraph_vector_int_init(&row_ids, 0)) @@ -9135,9 +9328,16 @@ PyObject *igraphmodule_Graph_get_biadjacency(igraphmodule_GraphObject * self, return NULL; } - if (igraphmodule_attrib_to_vector_bool_t(types_o, self, &types, ATTRIBUTE_TYPE_VERTEX)) { + if (igraphmodule_attrib_to_vector_bool_t(types_o, self, &types, ATTRIBUTE_TYPE_VERTEX)) { + igraph_vector_int_destroy(&row_ids); + igraph_vector_int_destroy(&col_ids); + return NULL; + } + + if (igraphmodule_attrib_to_vector_t(weights_o, self, &weights, ATTRIBUTE_TYPE_EDGE)) { igraph_vector_int_destroy(&row_ids); igraph_vector_int_destroy(&col_ids); + if (types) { igraph_vector_bool_destroy(types); free(types); } return NULL; } @@ -9146,19 +9346,22 @@ PyObject *igraphmodule_Graph_get_biadjacency(igraphmodule_GraphObject * self, igraph_vector_int_destroy(&row_ids); igraph_vector_int_destroy(&col_ids); if (types) { igraph_vector_bool_destroy(types); free(types); } + if (weights) { igraph_vector_destroy(weights); free(weights); } return NULL; } - if (igraph_get_biadjacency(&self->g, types, &matrix, &row_ids, &col_ids)) { + if (igraph_get_biadjacency(&self->g, types, weights, &matrix, &row_ids, &col_ids)) { igraphmodule_handle_igraph_error(); igraph_vector_int_destroy(&row_ids); igraph_vector_int_destroy(&col_ids); if (types) { igraph_vector_bool_destroy(types); free(types); } + if (weights) { igraph_vector_destroy(weights); free(weights); } igraph_matrix_destroy(&matrix); return NULL; } if (types) { igraph_vector_bool_destroy(types); free(types); } + if (weights) { igraph_vector_destroy(weights); free(weights); } matrix_o = igraphmodule_matrix_t_to_PyList(&matrix, IGRAPHMODULE_TYPE_INT); igraph_matrix_destroy(&matrix); @@ -9341,7 +9544,7 @@ PyObject *igraphmodule_Graph_Read_DIMACS(PyTypeObject * type, { igraphmodule_GraphObject *self; igraphmodule_filehandle_t fobj; - igraph_integer_t source = 0, target = 0; + igraph_int_t source = 0, target = 0; igraph_vector_t capacity; igraph_t g; PyObject *fname = NULL, *directed = Py_False, *capacity_obj; @@ -9692,7 +9895,7 @@ PyObject *igraphmodule_Graph_write_dimacs(igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { PyObject *capacity_obj = Py_None, *fname = NULL, *source_o, *target_o; - igraph_integer_t source, target; + igraph_int_t source, target; igraphmodule_filehandle_t fobj; igraph_vector_t* capacity = 0; @@ -10064,7 +10267,7 @@ PyObject *igraphmodule_Graph_automorphism_group( if (igraphmodule_attrib_to_vector_int_t(color_o, self, &color, ATTRIBUTE_TYPE_VERTEX)) return NULL; - retval = igraph_automorphism_group(&self->g, color, &generators, sh, 0); + retval = igraph_automorphism_group_bliss(&self->g, color, &generators, sh, 0); if (color) { igraph_vector_int_destroy(color); free(color); } @@ -10110,7 +10313,7 @@ PyObject *igraphmodule_Graph_canonical_permutation( if (igraphmodule_attrib_to_vector_int_t(color_o, self, &color, ATTRIBUTE_TYPE_VERTEX)) return NULL; - retval = igraph_canonical_permutation(&self->g, color, &labeling, sh, 0); + retval = igraph_canonical_permutation(&self->g, color, &labeling); if (color) { igraph_vector_int_destroy(color); free(color); } @@ -10152,7 +10355,7 @@ PyObject *igraphmodule_Graph_count_automorphisms( if (igraphmodule_attrib_to_vector_int_t(color_o, self, &color, ATTRIBUTE_TYPE_VERTEX)) return NULL; - retval = igraph_count_automorphisms(&self->g, color, sh, &info); + retval = igraph_count_automorphisms_bliss(&self->g, color, sh, &info); if (color) { igraph_vector_int_destroy(color); free(color); } @@ -10178,7 +10381,7 @@ PyObject *igraphmodule_Graph_count_automorphisms( PyObject *igraphmodule_Graph_isoclass(igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { - igraph_integer_t isoclass = 0; + igraph_int_t isoclass = 0; PyObject *vids = 0; char *kwlist[] = { "vertices", NULL }; @@ -10187,16 +10390,16 @@ PyObject *igraphmodule_Graph_isoclass(igraphmodule_GraphObject * self, return NULL; if (vids) { - igraph_vector_int_t vidsvec; - if (igraphmodule_PyObject_to_vid_list(vids, &vidsvec, &self->g)) { + igraph_vs_t vs; + if (igraphmodule_PyObject_to_vs_t(vids, &vs, &self->g, NULL, NULL)) { return NULL; } - if (igraph_isoclass_subgraph(&self->g, &vidsvec, &isoclass)) { - igraph_vector_int_destroy(&vidsvec); + if (igraph_isoclass_subgraph(&self->g, vs, &isoclass)) { + igraph_vs_destroy(&vs); igraphmodule_handle_igraph_error(); return NULL; } - igraph_vector_int_destroy(&vidsvec); + igraph_vs_destroy(&vs); } else { if (igraph_isoclass(&self->g, &isoclass)) { igraphmodule_handle_igraph_error(); @@ -10379,7 +10582,7 @@ igraph_error_t igraphmodule_i_Graph_isomorphic_vf2_callback_fn( igraph_bool_t igraphmodule_i_Graph_isomorphic_vf2_node_compat_fn( const igraph_t *graph1, const igraph_t *graph2, - const igraph_integer_t cand1, const igraph_integer_t cand2, + const igraph_int_t cand1, const igraph_int_t cand2, void* extra) { igraphmodule_i_Graph_isomorphic_vf2_callback_data_t* data = (igraphmodule_i_Graph_isomorphic_vf2_callback_data_t*)extra; @@ -10403,7 +10606,7 @@ igraph_bool_t igraphmodule_i_Graph_isomorphic_vf2_node_compat_fn( igraph_bool_t igraphmodule_i_Graph_isomorphic_vf2_edge_compat_fn( const igraph_t *graph1, const igraph_t *graph2, - const igraph_integer_t cand1, const igraph_integer_t cand2, + const igraph_int_t cand1, const igraph_int_t cand2, void* extra) { igraphmodule_i_Graph_isomorphic_vf2_callback_data_t* data = (igraphmodule_i_Graph_isomorphic_vf2_callback_data_t*)extra; @@ -10578,7 +10781,7 @@ PyObject *igraphmodule_Graph_isomorphic_vf2(igraphmodule_GraphObject * self, */ PyObject *igraphmodule_Graph_count_isomorphisms_vf2(igraphmodule_GraphObject *self, PyObject *args, PyObject *kwds) { - igraph_integer_t res = 0; + igraph_int_t res = 0; PyObject *o = Py_None; PyObject *color1_o=Py_None, *color2_o=Py_None; PyObject *edge_color1_o=Py_None, *edge_color2_o=Py_None; @@ -10916,7 +11119,7 @@ PyObject *igraphmodule_Graph_subisomorphic_vf2(igraphmodule_GraphObject * self, */ PyObject *igraphmodule_Graph_count_subisomorphisms_vf2(igraphmodule_GraphObject *self, PyObject *args, PyObject *kwds) { - igraph_integer_t res = 0; + igraph_int_t res = 0; PyObject *o = Py_None; PyObject *color1_o = Py_None, *color2_o = Py_None; PyObject *edge_color1_o=Py_None, *edge_color2_o=Py_None; @@ -11104,18 +11307,18 @@ PyObject *igraphmodule_Graph_subisomorphic_lad(igraphmodule_GraphObject * self, { igraph_bool_t res = false; PyObject *o, *return_mapping=Py_False, *domains_o=Py_None, *induced=Py_False; - float time_limit = 0; igraphmodule_GraphObject *other; igraph_vector_int_list_t domains; igraph_vector_int_list_t* p_domains = 0; igraph_vector_int_t mapping, *map=0; - static char *kwlist[] = { "pattern", "domains", "induced", "time_limit", - "return_mapping", NULL }; + static char *kwlist[] = { + "pattern", "domains", "induced", "return_mapping", NULL + }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|OOfO", kwlist, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|OOO", kwlist, igraphmodule_GraphType, &o, &domains_o, &induced, - &time_limit, &return_mapping)) + &return_mapping)) return NULL; other=(igraphmodule_GraphObject*)o; @@ -11129,8 +11332,9 @@ PyObject *igraphmodule_Graph_subisomorphic_lad(igraphmodule_GraphObject * self, if (PyObject_IsTrue(return_mapping)) { if (igraph_vector_int_init(&mapping, 0)) { - if (p_domains) + if (p_domains) { igraph_vector_int_list_destroy(p_domains); + } igraphmodule_handle_igraph_error(); return NULL; } @@ -11138,9 +11342,10 @@ PyObject *igraphmodule_Graph_subisomorphic_lad(igraphmodule_GraphObject * self, } if (igraph_subisomorphic_lad(&other->g, &self->g, p_domains, &res, - map, 0, PyObject_IsTrue(induced), (igraph_integer_t) time_limit)) { - if (p_domains) + map, 0, PyObject_IsTrue(induced))) { + if (p_domains) { igraph_vector_int_list_destroy(p_domains); + } igraphmodule_handle_igraph_error(); return NULL; } @@ -11171,16 +11376,15 @@ PyObject *igraphmodule_Graph_get_subisomorphisms_lad( igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { PyObject *o, *domains_o=Py_None, *induced=Py_False, *result_o; - float time_limit = 0; igraphmodule_GraphObject *other; igraph_vector_int_list_t domains; igraph_vector_int_list_t* p_domains = 0; igraph_vector_int_list_t mappings; - static char *kwlist[] = { "pattern", "domains", "induced", "time_limit", NULL }; + static char *kwlist[] = { "pattern", "domains", "induced", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|OOf", kwlist, - igraphmodule_GraphType, &o, &domains_o, &induced, &time_limit)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|OO", kwlist, + igraphmodule_GraphType, &o, &domains_o, &induced)) return NULL; other=(igraphmodule_GraphObject*)o; @@ -11199,8 +11403,9 @@ PyObject *igraphmodule_Graph_get_subisomorphisms_lad( return NULL; } - if (igraph_subisomorphic_lad(&other->g, &self->g, p_domains, 0, 0, &mappings, - PyObject_IsTrue(induced), (igraph_integer_t) time_limit)) { + if (igraph_subisomorphic_lad( + &other->g, &self->g, p_domains, 0, 0, &mappings, PyObject_IsTrue(induced) + )) { igraphmodule_handle_igraph_error(); igraph_vector_int_list_destroy(&mappings); if (p_domains) @@ -11516,7 +11721,7 @@ PyObject *igraphmodule_Graph_bfs(igraphmodule_GraphObject * self, { static char *kwlist[] = { "vid", "mode", NULL }; PyObject *l1, *l2, *l3, *result_o, *mode_o = Py_None, *vid_o; - igraph_integer_t vid; + igraph_int_t vid; igraph_neimode_t mode = IGRAPH_OUT; igraph_vector_int_t vids; igraph_vector_int_t layers; @@ -11682,7 +11887,7 @@ PyObject *igraphmodule_Graph_dominator(igraphmodule_GraphObject * self, { static char *kwlist[] = { "vid", "mode", NULL }; PyObject *list = Py_None, *mode_o = Py_None, *root_o; - igraph_integer_t root; + igraph_int_t root; igraph_vector_int_t dom; igraph_neimode_t mode = IGRAPH_OUT; igraph_error_t res; @@ -11736,7 +11941,7 @@ PyObject *igraphmodule_Graph_maxflow_value(igraphmodule_GraphObject * self, PyObject *capacity_object = Py_None, *v1_o, *v2_o; igraph_vector_t capacity_vector; igraph_real_t res; - igraph_integer_t v1, v2; + igraph_int_t v1, v2; igraph_maxflow_stats_t stats; if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|O", kwlist, @@ -11775,7 +11980,7 @@ PyObject *igraphmodule_Graph_maxflow(igraphmodule_GraphObject * self, PyObject *capacity_object = Py_None, *flow_o, *cut_o, *partition_o, *v1_o, *v2_o; igraph_vector_t capacity_vector; igraph_real_t res; - igraph_integer_t v1, v2; + igraph_int_t v1, v2; igraph_vector_t flow; igraph_vector_int_t cut, partition; igraph_maxflow_stats_t stats; @@ -11861,7 +12066,7 @@ PyObject *igraphmodule_Graph_all_st_cuts(igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { static char *kwlist[] = { "source", "target", NULL }; - igraph_integer_t source, target; + igraph_int_t source, target; igraph_vector_int_list_t cuts, partition1s; PyObject *source_o, *target_o; PyObject *cuts_o, *partition1s_o; @@ -11912,7 +12117,7 @@ PyObject *igraphmodule_Graph_all_st_mincuts(igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { static char *kwlist[] = { "source", "target", "capacity", NULL }; - igraph_integer_t source, target; + igraph_int_t source, target; igraph_real_t value; igraph_vector_int_list_t cuts, partition1s; igraph_vector_t capacity_vector; @@ -11979,7 +12184,7 @@ PyObject *igraphmodule_Graph_mincut_value(igraphmodule_GraphObject * self, PyObject *capacity_object = Py_None, *v1_o = Py_None, *v2_o = Py_None; igraph_vector_t capacity_vector; igraph_real_t res, mincut; - igraph_integer_t n, v1 = -1, v2 = -1; + igraph_int_t n, v1 = -1, v2 = -1; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO", kwlist, &v1_o, &v2_o, &capacity_object)) @@ -12060,7 +12265,7 @@ PyObject *igraphmodule_Graph_mincut(igraphmodule_GraphObject * self, igraph_vector_t capacity_vector; igraph_real_t value; igraph_vector_int_t partition, partition2, cut; - igraph_integer_t source = -1, target = -1; + igraph_int_t source = -1, target = -1; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO", kwlist, &source_o, &target_o, &capacity_object)) @@ -12201,7 +12406,7 @@ PyObject *igraphmodule_Graph_st_mincut(igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { static char *kwlist[] = { "source", "target", "capacity", NULL }; - igraph_integer_t source, target; + igraph_int_t source, target; PyObject *cut_o, *part_o, *part2_o, *result_o; PyObject *source_o, *target_o, *capacity_o = Py_None; igraph_vector_t capacity_vector; @@ -12517,13 +12722,15 @@ PyObject *igraphmodule_Graph_vertex_coloring_greedy( PyObject *igraphmodule_Graph_cliques(igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { - static char *kwlist[] = { "min", "max", NULL }; + static char *kwlist[] = { "min", "max", "max_results", NULL }; PyObject *list; + PyObject *max_results_o = Py_None; Py_ssize_t min_size = 0, max_size = 0; igraph_vector_int_list_t res; + igraph_int_t max_results; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|nn", kwlist, - &min_size, &max_size)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|nnO", kwlist, + &min_size, &max_size, &max_results_o)) return NULL; if (min_size >= 0) { @@ -12538,12 +12745,16 @@ PyObject *igraphmodule_Graph_cliques(igraphmodule_GraphObject * self, max_size = -1; } + if (igraphmodule_PyObject_to_max_results_t(max_results_o, &max_results)) { + return NULL; + } + if (igraph_vector_int_list_init(&res, 0)) { igraphmodule_handle_igraph_error(); return NULL; } - if (igraph_cliques(&self->g, &res, min_size, max_size)) { + if (igraph_cliques(&self->g, &res, min_size, max_size, max_results)) { igraph_vector_int_list_destroy(&res); return igraphmodule_handle_igraph_error(); } @@ -12633,25 +12844,30 @@ PyObject *igraphmodule_Graph_maximum_bipartite_matching(igraphmodule_GraphObject */ PyObject *igraphmodule_Graph_maximal_cliques(igraphmodule_GraphObject * self, PyObject* args, PyObject* kwds) { - static char* kwlist[] = { "min", "max", "file", NULL }; - PyObject *list, *file = Py_None; + static char* kwlist[] = { "min", "max", "file", "max_results", NULL }; + PyObject *list, *file = Py_None, *max_results_o = Py_None; Py_ssize_t min = 0, max = 0; igraph_vector_int_list_t res; igraphmodule_filehandle_t filehandle; + igraph_int_t max_results = IGRAPH_UNLIMITED; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|nnO", kwlist, &min, &max, &file)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|nnOO", kwlist, &min, &max, &file, &max_results_o)) return NULL; CHECK_SSIZE_T_RANGE(min, "minimum size"); CHECK_SSIZE_T_RANGE(max, "maximum size"); + if (igraphmodule_PyObject_to_max_results_t(max_results_o, &max_results)) { + return NULL; + } + if (file == Py_None) { if (igraph_vector_int_list_init(&res, 0)) { PyErr_SetString(PyExc_MemoryError, ""); return NULL; } - if (igraph_maximal_cliques(&self->g, &res, min, max)) { + if (igraph_maximal_cliques(&self->g, &res, min, max, max_results)) { igraph_vector_int_list_destroy(&res); return igraphmodule_handle_igraph_error(); } @@ -12665,7 +12881,7 @@ PyObject *igraphmodule_Graph_maximal_cliques(igraphmodule_GraphObject * self, return igraphmodule_handle_igraph_error(); } if (igraph_maximal_cliques_file(&self->g, - igraphmodule_filehandle_get(&filehandle), min, max)) { + igraphmodule_filehandle_get(&filehandle), min, max, max_results)) { igraphmodule_filehandle_destroy(&filehandle); return igraphmodule_handle_igraph_error(); } @@ -12679,7 +12895,7 @@ PyObject *igraphmodule_Graph_maximal_cliques(igraphmodule_GraphObject * self, */ PyObject *igraphmodule_Graph_clique_number(igraphmodule_GraphObject *self, PyObject* Py_UNUSED(_null)) { - igraph_integer_t i; + igraph_int_t i; if (igraph_clique_number(&self->g, &i)) { return igraphmodule_handle_igraph_error(); @@ -12695,13 +12911,14 @@ PyObject *igraphmodule_Graph_independent_vertex_sets(igraphmodule_GraphObject * self, PyObject * args, PyObject * kwds) { - static char *kwlist[] = { "min", "max", NULL }; - PyObject *list; + static char *kwlist[] = { "min", "max", "max_results", NULL }; + PyObject *list, *max_results_o = Py_None; Py_ssize_t min_size = 0, max_size = 0; igraph_vector_int_list_t res; + igraph_int_t max_results = IGRAPH_UNLIMITED; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|nn", kwlist, - &min_size, &max_size)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|nnO", kwlist, + &min_size, &max_size, &max_results_o)) return NULL; if (min_size >= 0) { @@ -12716,12 +12933,16 @@ PyObject *igraphmodule_Graph_independent_vertex_sets(igraphmodule_GraphObject max_size = -1; } + if (igraphmodule_PyObject_to_max_results_t(max_results_o, &max_results)) { + return NULL; + } + if (igraph_vector_int_list_init(&res, 0)) { PyErr_SetString(PyExc_MemoryError, ""); return NULL; } - if (igraph_independent_vertex_sets(&self->g, &res, min_size, max_size)) { + if (igraph_independent_vertex_sets(&self->g, &res, min_size, max_size, max_results)) { igraph_vector_int_list_destroy(&res); return igraphmodule_handle_igraph_error(); } @@ -12759,17 +12980,39 @@ PyObject *igraphmodule_Graph_largest_independent_vertex_sets( * \brief Find all maximal independent vertex sets in a graph */ PyObject *igraphmodule_Graph_maximal_independent_vertex_sets( - igraphmodule_GraphObject *self, PyObject* Py_UNUSED(_null) + igraphmodule_GraphObject *self, PyObject* args, PyObject *kwds ) { - PyObject *list; + static char *kwlist[] = { "min", "max", "max_results", NULL }; + PyObject *list, *max_results_o = Py_None; + Py_ssize_t min_size = 0, max_size = 0; igraph_vector_int_list_t res; + igraph_int_t max_results = IGRAPH_UNLIMITED; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|nnO", kwlist, &min_size, &max_size, &max_results_o)) + return NULL; + + if (min_size >= 0) { + CHECK_SSIZE_T_RANGE(min_size, "minimum size"); + } else { + min_size = -1; + } + + if (max_size >= 0) { + CHECK_SSIZE_T_RANGE(max_size, "maximum size"); + } else { + max_size = -1; + } + + if (igraphmodule_PyObject_to_max_results_t(max_results_o, &max_results)) { + return NULL; + } if (igraph_vector_int_list_init(&res, 0)) { PyErr_SetString(PyExc_MemoryError, ""); return NULL; } - if (igraph_maximal_independent_vertex_sets(&self->g, &res)) { + if (igraph_maximal_independent_vertex_sets(&self->g, &res, min_size, max_size, max_results)) { igraph_vector_int_list_destroy(&res); return igraphmodule_handle_igraph_error(); } @@ -12785,7 +13028,7 @@ PyObject *igraphmodule_Graph_maximal_independent_vertex_sets( PyObject *igraphmodule_Graph_independence_number( igraphmodule_GraphObject *self, PyObject* Py_UNUSED(_null) ) { - igraph_integer_t i; + igraph_int_t i; if (igraph_independence_number(&self->g, &i)) { return igraphmodule_handle_igraph_error(); @@ -12928,16 +13171,18 @@ PyObject *igraphmodule_Graph_community_edge_betweenness(igraphmodule_GraphObject PyObject *res, *qs, *ms; igraph_matrix_int_t merges; igraph_vector_t q; - igraph_vector_t *weights = 0; + igraph_vector_t *weights = NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO", kwlist, &directed, &weights_o)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO", kwlist, &directed, &weights_o)) { return NULL; + } - if (igraphmodule_attrib_to_vector_t(weights_o, self, &weights, - ATTRIBUTE_TYPE_EDGE)) return NULL; + if (igraphmodule_attrib_to_vector_t(weights_o, self, &weights, ATTRIBUTE_TYPE_EDGE)) { + return NULL; + } if (igraph_matrix_int_init(&merges, 0, 0)) { - if (weights != 0) { + if (weights) { igraph_vector_destroy(weights); free(weights); } return igraphmodule_handle_igraph_error(); @@ -12945,50 +13190,44 @@ PyObject *igraphmodule_Graph_community_edge_betweenness(igraphmodule_GraphObject if (igraph_vector_init(&q, 0)) { igraph_matrix_int_destroy(&merges); - if (weights != 0) { + if (weights) { igraph_vector_destroy(weights); free(weights); } return igraphmodule_handle_igraph_error(); } if (igraph_community_edge_betweenness(&self->g, - /* removed_edges = */ 0, - /* edge_betweenness = */ 0, + /* removed_edges = */ NULL, + /* edge_betweenness = */ NULL, /* merges = */ &merges, - /* bridges = */ 0, - /* modularity = */ weights ? 0 : &q, - /* membership = */ 0, + /* bridges = */ NULL, + /* modularity = */ &q, + /* membership = */ NULL, PyObject_IsTrue(directed), - weights)) { - igraphmodule_handle_igraph_error(); - if (weights != 0) { + weights, + /* lengths = */ NULL)) { + + igraph_vector_destroy(&q); + igraph_matrix_int_destroy(&merges); + if (weights) { igraph_vector_destroy(weights); free(weights); } - igraph_matrix_int_destroy(&merges); - igraph_vector_destroy(&q); - return NULL; + + return igraphmodule_handle_igraph_error();; } - if (weights != 0) { + if (weights) { igraph_vector_destroy(weights); free(weights); } - if (weights == 0) { - /* Calculate modularity vector only in the unweighted case as we don't - * calculate modularities for the weighted case */ - qs=igraphmodule_vector_t_to_PyList(&q, IGRAPHMODULE_TYPE_FLOAT); - igraph_vector_destroy(&q); - if (!qs) { - igraph_matrix_int_destroy(&merges); - return NULL; - } - } else { - qs = Py_None; - Py_INCREF(qs); - igraph_vector_destroy(&q); + qs = igraphmodule_vector_t_to_PyList(&q, IGRAPHMODULE_TYPE_FLOAT); + igraph_vector_destroy(&q); + if (!qs) { + igraph_matrix_int_destroy(&merges); + return NULL; } - ms=igraphmodule_matrix_int_t_to_PyList(&merges); + ms = igraphmodule_matrix_int_t_to_PyList(&merges); igraph_matrix_int_destroy(&merges); if (ms == NULL) { @@ -13169,10 +13408,12 @@ PyObject *igraphmodule_Graph_community_infomap(igraphmodule_GraphObject * self, return NULL; } - if (igraph_community_infomap(/*in */ &self->g, - /*e_weight=*/ e_ws, /*v_weight=*/ v_ws, - /*nb_trials=*/nb_trials, - /*out*/ &membership, &codelength)) { + if (igraph_community_infomap( + /*in */ &self->g, /*e_weight=*/ e_ws, /*v_weight=*/ v_ws, + /*nb_trials=*/nb_trials, /*is_regularized=*/0, + /*regularization_strength=*/ 0, + /*out*/ &membership, &codelength) + ) { igraphmodule_handle_igraph_error(); igraph_vector_int_destroy(&membership); if (e_ws) { @@ -13212,17 +13453,23 @@ PyObject *igraphmodule_Graph_community_infomap(igraphmodule_GraphObject * self, PyObject *igraphmodule_Graph_community_label_propagation( igraphmodule_GraphObject *self, PyObject *args, PyObject *kwds) { - static char *kwlist[] = { "weights", "initial", "fixed", NULL }; + static char *kwlist[] = { "weights", "initial", "fixed", "variant", NULL }; PyObject *weights_o = Py_None, *initial_o = Py_None, *fixed_o = Py_None; + PyObject *variant_o = NULL; PyObject *result_o; igraph_vector_int_t membership, *initial = 0; igraph_vector_t *ws = 0; igraph_vector_bool_t fixed; + igraph_lpa_variant_t variant = IGRAPH_LPA_DOMINANCE; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO", kwlist, &weights_o, &initial_o, &fixed_o)) { + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOO", kwlist, &weights_o, &initial_o, &fixed_o, &variant_o)) { return NULL; } + if (igraphmodule_PyObject_to_lpa_variant_t(variant_o, &variant)) { + return NULL; + } + if (fixed_o != Py_None) { if (igraphmodule_PyObject_to_vector_bool_t(fixed_o, &fixed)) return NULL; @@ -13241,7 +13488,7 @@ PyObject *igraphmodule_Graph_community_label_propagation( igraph_vector_int_init(&membership, igraph_vcount(&self->g)); if (igraph_community_label_propagation(&self->g, &membership, - IGRAPH_OUT, ws, initial, (fixed_o != Py_None ? &fixed : 0))) { + IGRAPH_OUT, ws, initial, (fixed_o != Py_None ? &fixed : 0), variant)) { if (fixed_o != Py_None) igraph_vector_bool_destroy(&fixed); if (ws) { igraph_vector_destroy(ws); free(ws); } if (initial) { igraph_vector_int_destroy(initial); free(initial); } @@ -13326,15 +13573,16 @@ PyObject *igraphmodule_Graph_community_multilevel(igraphmodule_GraphObject *self */ PyObject *igraphmodule_Graph_community_optimal_modularity( igraphmodule_GraphObject *self, PyObject *args, PyObject *kwds) { - static char *kwlist[] = {"weights", NULL}; + static char *kwlist[] = {"weights", "resolution", NULL}; PyObject *weights_o = Py_None; igraph_real_t modularity; igraph_vector_int_t membership; igraph_vector_t* weights = 0; + double resolution = 1; PyObject *res; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwlist, - &weights_o)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Od", kwlist, + &weights_o, &resolution)) return NULL; if (igraph_vector_int_init(&membership, igraph_vcount(&self->g))) { @@ -13348,8 +13596,8 @@ PyObject *igraphmodule_Graph_community_optimal_modularity( return NULL; } - if (igraph_community_optimal_modularity(&self->g, &modularity, - &membership, weights)) { + if (igraph_community_optimal_modularity(&self->g, weights, resolution, &modularity, + &membership)) { igraphmodule_handle_igraph_error(); igraph_vector_int_destroy(&membership); if (weights != 0) { @@ -13508,11 +13756,12 @@ PyObject *igraphmodule_Graph_community_walktrap(igraphmodule_GraphObject * self, PyObject *igraphmodule_Graph_community_leiden(igraphmodule_GraphObject *self, PyObject *args, PyObject *kwds) { - static char *kwlist[] = {"edge_weights", "node_weights", "resolution", + static char *kwlist[] = {"edge_weights", "node_weights", "node_in_weights", "resolution", "normalize_resolution", "beta", "initial_membership", "n_iterations", NULL}; PyObject *edge_weights_o = Py_None; PyObject *node_weights_o = Py_None; + PyObject *node_in_weights_o = Py_None; PyObject *initial_membership_o = Py_None; PyObject *normalize_resolution = Py_False; PyObject *res = Py_None; @@ -13521,14 +13770,14 @@ PyObject *igraphmodule_Graph_community_leiden(igraphmodule_GraphObject *self, Py_ssize_t n_iterations = 2; double resolution = 1.0; double beta = 0.01; - igraph_vector_t *edge_weights = NULL, *node_weights = NULL; + igraph_vector_t *edge_weights = NULL, *node_weights = NULL, *node_in_weights = NULL; igraph_vector_int_t *membership = NULL; igraph_bool_t start = true; - igraph_integer_t nb_clusters = 0; + igraph_int_t nb_clusters = 0; igraph_real_t quality = 0.0; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOdOdOn", kwlist, - &edge_weights_o, &node_weights_o, &resolution, &normalize_resolution, &beta, &initial_membership_o, &n_iterations)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOdOdOn", kwlist, + &edge_weights_o, &node_weights_o, &node_in_weights_o, &resolution, &normalize_resolution, &beta, &initial_membership_o, &n_iterations)) return NULL; if (n_iterations >= 0) { @@ -13551,6 +13800,13 @@ PyObject *igraphmodule_Graph_community_leiden(igraphmodule_GraphObject *self, error = -1; } + /* Get node in-weights (directed case) */ + if (!error && igraphmodule_attrib_to_vector_t(node_in_weights_o, self, &node_in_weights, + ATTRIBUTE_TYPE_VERTEX)) { + igraphmodule_handle_igraph_error(); + error = -1; + } + /* Get initial membership */ if (!error && igraphmodule_attrib_to_vector_int_t(initial_membership_o, self, &membership, ATTRIBUTE_TYPE_VERTEX)) { @@ -13561,30 +13817,35 @@ PyObject *igraphmodule_Graph_community_leiden(igraphmodule_GraphObject *self, if (!error && membership == 0) { start = 0; membership = (igraph_vector_int_t*)calloc(1, sizeof(igraph_vector_int_t)); - if (membership==0) { + if (membership == 0) { PyErr_NoMemory(); error = -1; - } else { - igraph_vector_int_init(membership, 0); + } else if (igraph_vector_int_init(membership, 0)) { + igraphmodule_handle_igraph_error(); + error = -1; } } - if (PyObject_IsTrue(normalize_resolution)) + if (!error && PyObject_IsTrue(normalize_resolution)) { /* If we need to normalize the resolution parameter, * we will need to have node weights. */ if (node_weights == 0) { node_weights = (igraph_vector_t*)calloc(1, sizeof(igraph_vector_t)); - if (node_weights==0) { + if (node_weights == 0) { PyErr_NoMemory(); error = -1; - } else { - igraph_vector_init(node_weights, 0); - if (igraph_strength(&self->g, node_weights, igraph_vss_all(), IGRAPH_ALL, 0, edge_weights)) { - igraphmodule_handle_igraph_error(); - error = -1; - } + } else if (igraph_vector_init(node_weights, 0)) { + igraphmodule_handle_igraph_error(); + error = -1; + } else if (igraph_strength( + &self->g, node_weights, igraph_vss_all(), + igraph_is_directed(&self->g) ? IGRAPH_OUT : IGRAPH_ALL, + IGRAPH_NO_LOOPS, edge_weights + )) { + igraphmodule_handle_igraph_error(); + error = -1; } } resolution /= igraph_vector_sum(node_weights); @@ -13593,7 +13854,7 @@ PyObject *igraphmodule_Graph_community_leiden(igraphmodule_GraphObject *self, /* Run actual Leiden algorithm for several iterations. */ if (!error) { error = igraph_community_leiden(&self->g, - edge_weights, node_weights, + edge_weights, node_weights, node_in_weights, resolution, beta, start, n_iterations, membership, &nb_clusters, &quality); @@ -13607,6 +13868,10 @@ PyObject *igraphmodule_Graph_community_leiden(igraphmodule_GraphObject *self, igraph_vector_destroy(node_weights); free(node_weights); } + if (node_in_weights != 0) { + igraph_vector_destroy(node_in_weights); + free(node_in_weights); + } if (!error && membership != 0) { res = igraphmodule_vector_int_t_to_PyList(membership); @@ -13620,6 +13885,157 @@ PyObject *igraphmodule_Graph_community_leiden(igraphmodule_GraphObject *self, return error ? NULL : Py_BuildValue("Nd", res, (double) quality); } + /** + * Fluid communities + */ +PyObject *igraphmodule_Graph_community_fluid_communities(igraphmodule_GraphObject *self, + PyObject *args, PyObject *kwds) { + static char *kwlist[] = {"no_of_communities", NULL}; + Py_ssize_t no_of_communities; + igraph_vector_int_t membership; + PyObject *result; + + // Parse the Python integer argument + if (!PyArg_ParseTupleAndKeywords(args, kwds, "n", kwlist, &no_of_communities)) { + return NULL; + } + + if (igraph_vector_int_init(&membership, 0)) { + igraphmodule_handle_igraph_error(); + return NULL; + } + + if (igraph_community_fluid_communities(&self->g, no_of_communities, &membership)) { + igraphmodule_handle_igraph_error(); + igraph_vector_int_destroy(&membership); + return NULL; + } + + result = igraphmodule_vector_int_t_to_PyList(&membership); + igraph_vector_int_destroy(&membership); + + return result; +} + +/** + * Voronoi clustering + */ +PyObject *igraphmodule_Graph_community_voronoi(igraphmodule_GraphObject *self, + PyObject *args, PyObject *kwds) { + static char *kwlist[] = {"lengths", "weights", "mode", "radius", NULL}; + PyObject *lengths_o = Py_None, *weights_o = Py_None; + PyObject *mode_o = Py_None; + PyObject *radius_o = Py_None; + igraph_vector_t *lengths_v = NULL; + igraph_vector_t *weights_v = NULL; + igraph_vector_int_t membership_v, generators_v; + igraph_neimode_t mode = IGRAPH_OUT; + igraph_real_t radius = -1.0; /* negative means auto-optimize */ + igraph_real_t modularity = IGRAPH_NAN; + PyObject *membership_o, *generators_o, *result_o; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOO", kwlist, + &lengths_o, &weights_o, &mode_o, &radius_o)) { + return NULL; + } + + /* Handle mode parameter */ + if (igraphmodule_PyObject_to_neimode_t(mode_o, &mode)) { + return NULL; + } + + /* Handle radius parameter */ + if (radius_o != Py_None) { + if (igraphmodule_PyObject_to_real_t(radius_o, &radius)) { + return NULL; + } + } + + /* Handle lengths parameter */ + if (igraphmodule_attrib_to_vector_t(lengths_o, self, &lengths_v, ATTRIBUTE_TYPE_EDGE)) { + return NULL; + } + + /* Handle weights parameter */ + if (igraphmodule_attrib_to_vector_t(weights_o, self, &weights_v, ATTRIBUTE_TYPE_EDGE)) { + if (lengths_v != NULL) { + igraph_vector_destroy(lengths_v); free(lengths_v); + } + return NULL; + } + + /* Initialize result vectors */ + if (igraph_vector_int_init(&membership_v, 0)) { + if (lengths_v != NULL) { + igraph_vector_destroy(lengths_v); free(lengths_v); + } + if (weights_v != NULL) { + igraph_vector_destroy(weights_v); free(weights_v); + } + igraphmodule_handle_igraph_error(); + return NULL; + } + + if (igraph_vector_int_init(&generators_v, 0)) { + if (lengths_v != NULL) { + igraph_vector_destroy(lengths_v); free(lengths_v); + } + if (weights_v != NULL) { + igraph_vector_destroy(weights_v); free(weights_v); + } + igraph_vector_int_destroy(&membership_v); + igraphmodule_handle_igraph_error(); + return NULL; + } + + /* Call the C function - pass NULL for None parameters */ + if (igraph_community_voronoi(&self->g, &membership_v, &generators_v, + &modularity, + lengths_v, + weights_v, + mode, radius)) { + + if (lengths_v != NULL) { + igraph_vector_destroy(lengths_v); free(lengths_v); + } + if (weights_v != NULL) { + igraph_vector_destroy(weights_v); free(weights_v); + } + igraph_vector_int_destroy(&membership_v); + igraph_vector_int_destroy(&generators_v); + igraphmodule_handle_igraph_error(); + return NULL; + } + + /* Clean up input vectors */ + if (lengths_v != NULL) { + igraph_vector_destroy(lengths_v); free(lengths_v); + } + if (weights_v != NULL) { + igraph_vector_destroy(weights_v); free(weights_v); + } + + /* Convert results to Python objects */ + membership_o = igraphmodule_vector_int_t_to_PyList(&membership_v); + igraph_vector_int_destroy(&membership_v); + if (!membership_o) { + igraph_vector_int_destroy(&generators_v); + return NULL; + } + + generators_o = igraphmodule_vector_int_t_to_PyList(&generators_v); + igraph_vector_int_destroy(&generators_v); + if (!generators_o) { + Py_DECREF(membership_o); + return NULL; + } + + /* Return tuple with membership, generators, and modularity */ + result_o = Py_BuildValue("(NNd)", membership_o, generators_o, modularity); + + return result_o; +} + /********************************************************************** * Random walks * **********************************************************************/ @@ -13632,7 +14048,7 @@ PyObject *igraphmodule_Graph_random_walk(igraphmodule_GraphObject * self, static char *kwlist[] = { "start", "steps", "mode", "stuck", "weights", "return_type", NULL }; PyObject *start_o, *mode_o = Py_None, *stuck_o = Py_None, *weights_o = Py_None, *return_type_o = Py_None; - igraph_integer_t start; + igraph_int_t start; Py_ssize_t steps = 10; igraph_neimode_t mode = IGRAPH_OUT; igraph_random_walk_stuck_t stuck = IGRAPH_RANDOM_WALK_STUCK_RETURN; @@ -13767,6 +14183,46 @@ PyObject *igraphmodule_Graph_random_walk(igraphmodule_GraphObject * self, } } +/********************************************************************** + * Spatial graphs * + **********************************************************************/ + +PyObject *igraphmodule_Graph_Nearest_Neighbor_Graph(PyTypeObject *type, + PyObject *args, PyObject *kwds) { + static char *kwlist[] = {"points", "k", "r", "metric", "directed", NULL}; + PyObject *points_o = Py_None, *metric_o = Py_None, *directed_o = Py_False; + double r = -1; + Py_ssize_t k = 1; + igraph_matrix_t points; + igraphmodule_GraphObject *self; + igraph_t graph; + igraph_metric_t metric = IGRAPH_METRIC_EUCLIDEAN; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|ndOO", kwlist, + &points_o, &k, &r, &metric_o, &directed_o)) { + return NULL; + } + + if (igraphmodule_PyObject_to_metric_t(metric_o, &metric)) { + return NULL; + } + + if (igraphmodule_PyObject_to_matrix_t(points_o, &points, "points")) { + return NULL; + } + + if (igraph_nearest_neighbor_graph(&graph, &points, metric, k, r, PyObject_IsTrue(directed_o))) { + igraph_matrix_destroy(&points); + return igraphmodule_handle_igraph_error(); + } + + igraph_matrix_destroy(&points); + + CREATE_GRAPH_FROM_TYPE(self, graph, type); + + return (PyObject *) self; +} + /********************************************************************** * Special internal methods that you won't need to mess around with * **********************************************************************/ @@ -14051,24 +14507,21 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { /* interface to igraph_neighbors */ {"neighbors", (PyCFunction) igraphmodule_Graph_neighbors, METH_VARARGS | METH_KEYWORDS, - "neighbors(vertex, mode=\"all\")\n--\n\n" + "neighbors(vertex, mode=\"all\", loops=\"twice\", multiple=True)\n--\n\n" "Returns adjacent vertices to a given vertex.\n\n" "@param vertex: a vertex ID\n" "@param mode: whether to return only successors (C{\"out\"}),\n" " predecessors (C{\"in\"}) or both (C{\"all\"}). Ignored for undirected\n" - " graphs."}, - - {"successors", (PyCFunction) igraphmodule_Graph_successors, - METH_VARARGS | METH_KEYWORDS, - "successors(vertex)\n--\n\n" - "Returns the successors of a given vertex.\n\n" - "Equivalent to calling the L{neighbors()} method with type=C{\"out\"}."}, - - {"predecessors", (PyCFunction) igraphmodule_Graph_predecessors, - METH_VARARGS | METH_KEYWORDS, - "predecessors(vertex)\n--\n\n" - "Returns the predecessors of a given vertex.\n\n" - "Equivalent to calling the L{neighbors()} method with type=C{\"in\"}."}, + " graphs." + "@param loops: whether to return loops in I{undirected} graphs once\n" + " (C{\"once\"}), twice (C{\"twice\"}) or not at all (C{\"ignore\"}). C{False}\n" + " is accepted as an alias to C{\"ignore\"} and C{True} is accepted as an\n" + " alias to C{\"twice\"}. For directed graphs, C{\"twice\"} is equivalent\n" + " to C{\"once\"} (except when C{mode} is C{\"all\"} because the graph is\n" + " then treated as undirected).\n" + "@param multiple: whether to return endpoints of multiple edges as many\n" + " times as their multiplicities." + }, /* interface to igraph_get_eid */ {"get_eid", (PyCFunction) igraphmodule_Graph_get_eid, @@ -14112,7 +14565,14 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "@param vertex: a vertex ID\n" "@param mode: whether to return only successors (C{\"out\"}),\n" " predecessors (C{\"in\"}) or both (C{\"all\"}). Ignored for undirected\n" - " graphs."}, + " graphs." + "@param loops: whether to return loops in I{undirected} graphs once\n" + " (C{\"once\"}), twice (C{\"twice\"}) or not at all (C{\"ignore\"}). C{False}\n" + " is accepted as an alias to C{\"ignore\"} and C{True} is accepted as an\n" + " alias to C{\"twice\"}. For directed graphs, C{\"twice\"} is equivalent\n" + " to C{\"once\"} (except when C{mode} is C{\"all\"} because the graph is\n" + " then treated as undirected).\n" + }, ////////////////////// // GRAPH GENERATORS // @@ -14273,13 +14733,17 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { // interface to igraph_erdos_renyi_game {"Erdos_Renyi", (PyCFunction) igraphmodule_Graph_Erdos_Renyi, METH_VARARGS | METH_CLASS | METH_KEYWORDS, - "Erdos_Renyi(n, p, m, directed=False, loops=False)\n--\n\n" + "Erdos_Renyi(n, p, m, directed=False, loops=False, edge_labeled=False)\n--\n\n" "Generates a graph based on the Erdős-Rényi model.\n\n" "@param n: the number of vertices.\n" "@param p: the probability of edges. If given, C{m} must be missing.\n" "@param m: the number of edges. If given, C{p} must be missing.\n" "@param directed: whether to generate a directed graph.\n" - "@param loops: whether self-loops are allowed.\n"}, + "@param loops: whether self-loops are allowed.\n" + "@param edge_labeled: whether to sample uniformly from the set of\n" + " I{ordered} edge lists. Use C{False} to recover the classic\n" + " Erdős-Rényi model.\n" + }, /* interface to igraph_famous */ {"Famous", (PyCFunction) igraphmodule_Graph_Famous, @@ -14451,7 +14915,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { /* interface to igraph_bipartite_game */ {"_Random_Bipartite", (PyCFunction) igraphmodule_Graph_Random_Bipartite, METH_VARARGS | METH_CLASS | METH_KEYWORDS, - "_Random_Bipartite(n1, n2, p=None, m=None, directed=False, neimode=\"all\")\n--\n\n" + "_Random_Bipartite(n1, n2, p=None, m=None, directed=False, neimode=\"all\", allowed_edge_types=\"simple\", edge_labeled=False)\n--\n\n" "Internal function, undocumented.\n\n" "@see: Graph.Random_Bipartite()\n\n"}, @@ -14479,21 +14943,29 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { /* interface to igraph_sbm_game */ {"SBM", (PyCFunction) igraphmodule_Graph_SBM, METH_VARARGS | METH_CLASS | METH_KEYWORDS, - "SBM(n, pref_matrix, block_sizes, directed=False, loops=False)\n--\n\n" + "SBM(pref_matrix, block_sizes, directed=False, allowed_edge_types=\"simple\")\n--\n\n" "Generates a graph based on a stochastic block model.\n\n" - "A given number of vertices are generated. Every vertex is assigned to a\n" - "vertex type according to the given block sizes. Vertices of the same\n" + "Every vertex is assigned to a vertex type according to the given block\n" + "sizes, which also determine the total vertex count. Vertices of the same\n" "type will be assigned consecutive vertex IDs. Finally, every\n" "vertex pair is evaluated and an edge is created between them with a\n" "probability depending on the types of the vertices involved. The\n" "probabilities are taken from the preference matrix.\n\n" - "@param n: the number of vertices in the graph\n" - "@param pref_matrix: matrix giving the connection probabilities for\n" - " different vertex types.\n" + "@param pref_matrix: matrix giving the connection probabilities (or expected\n" + " edge multiplicities for multigraphs) between different vertex types.\n" "@param block_sizes: list giving the number of vertices in each block; must\n" " sum up to I{n}.\n" "@param directed: whether to generate a directed graph.\n" - "@param loops: whether loop edges are allowed.\n"}, + "@param allowed_edge_types: controls whether loops or multi-edges are allowed\n" + " during the generation process. Note that not all combinations are supported\n" + " for all types of graphs; an exception will be raised for unsupported\n" + " combinations. Possible values are:\n" + "\n" + " - C{\"simple\"}: simple graphs (no self-loops, no multi-edges)\n" + " - C{\"loops\"}: single self-loops allowed, but not multi-edges\n" + " - C{\"multi\"}: multi-edges allowed, but not self-loops\n" + " - C{\"all\"}: multi-edges and self-loops allowed\n" + "\n"}, // interface to igraph_star {"Star", (PyCFunction) igraphmodule_Graph_Star, @@ -14530,7 +15002,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "the number of vertices in the graph, a list of shifts giving\n" "additional edges to a cycle backbone and another integer giving how\n" "many times the shifts should be performed. See\n" - "U{http://mathworld.wolfram.com/LCFNotation.html} for details.\n\n" + "U{https://mathworld.wolfram.com/LCFNotation.html} for details.\n\n" "@param n: the number of vertices\n" "@param shifts: the shifts in a list or tuple\n" "@param repeats: the number of repeats\n" @@ -14631,7 +15103,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { /* interface to igraph_static_fitness_game */ {"Static_Fitness", (PyCFunction) igraphmodule_Graph_Static_Fitness, METH_VARARGS | METH_CLASS | METH_KEYWORDS, - "Static_Fitness(m, fitness_out, fitness_in=None, loops=False, multiple=False)\n--\n\n" + "Static_Fitness(m, fitness_out, fitness_in=None, allowed_edge_types=\"simple\")\n--\n\n" "Generates a non-growing graph with edge probabilities proportional to node\n" "fitnesses.\n\n" "The algorithm randomly selects vertex pairs and connects them until the given\n" @@ -14646,8 +15118,16 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "@param fitness_in: a numeric vector with non-negative entries, one for each\n" " vertex. These values represent the in-fitness scores for directed graphs.\n" " For undirected graphs, this argument must be C{None}.\n" - "@param loops: whether loop edges are allowed.\n" - "@param multiple: whether multiple edges are allowed.\n" + "@param allowed_edge_types: controls whether loops or multi-edges are allowed\n" + " during the generation process. Note that not all combinations are supported\n" + " for all types of graphs; an exception will be raised for unsupported\n" + " combinations. Possible values are:\n" + "\n" + " - C{\"simple\"}: simple graphs (no self-loops, no multi-edges)\n" + " - C{\"loops\"}: single self-loops allowed, but not multi-edges\n" + " - C{\"multi\"}: multi-edges allowed, but not self-loops\n" + " - C{\"all\"}: multi-edges and self-loops allowed\n" + "\n" "@return: a directed or undirected graph with the prescribed power-law\n" " degree distributions.\n" }, @@ -14655,8 +15135,8 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { /* interface to igraph_static_power_law_game */ {"Static_Power_Law", (PyCFunction) igraphmodule_Graph_Static_Power_Law, METH_VARARGS | METH_CLASS | METH_KEYWORDS, - "Static_Power_Law(n, m, exponent_out, exponent_in=-1, loops=False, " - "multiple=False, finite_size_correction=True)\n--\n\n" + "Static_Power_Law(n, m, exponent_out, exponent_in=-1, allowed_edge_types=\"simple\", " + "finite_size_correction=True)\n--\n\n" "Generates a non-growing graph with prescribed power-law degree distributions.\n\n" "B{References}\n\n" " - Goh K-I, Kahng B, Kim D: Universal behaviour of load distribution\n" @@ -14674,8 +15154,16 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "@param exponent_in: the exponent of the in-degree distribution, which\n" " must be between 2 and infinity (inclusive) It can also be negative, in\n" " which case an undirected graph will be generated.\n" - "@param loops: whether loop edges are allowed.\n" - "@param multiple: whether multiple edges are allowed.\n" + "@param allowed_edge_types: controls whether loops or multi-edges are allowed\n" + " during the generation process. Note that not all combinations are supported\n" + " for all types of graphs; an exception will be raised for unsupported\n" + " combinations. Possible values are:\n" + "\n" + " - C{\"simple\"}: simple graphs (no self-loops, no multi-edges)\n" + " - C{\"loops\"}: single self-loops allowed, but not multi-edges\n" + " - C{\"multi\"}: multi-edges allowed, but not self-loops\n" + " - C{\"all\"}: multi-edges and self-loops allowed\n" + "\n" "@param finite_size_correction: whether to apply a finite-size correction\n" " to the generated fitness values for exponents less than 3. See the\n" " paper of Cho et al for more details.\n" @@ -14861,7 +15349,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { /* interface to igraph_watts_strogatz_game */ {"Watts_Strogatz", (PyCFunction) igraphmodule_Graph_Watts_Strogatz, METH_VARARGS | METH_CLASS | METH_KEYWORDS, - "Watts_Strogatz(dim, size, nei, p, loops=False, multiple=False)\n--\n\n" + "Watts_Strogatz(dim, size, nei, p, allowed_edge_types=\"simple\")\n--\n\n" "This function generates networks with the small-world property based on a\n" "variant of the Watts-Strogatz model. The network is obtained by first creating\n" "a periodic undirected lattice, then rewiring both endpoints of each edge with\n" @@ -14881,8 +15369,16 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "@param nei: value giving the distance (number of steps) within which\n" " two vertices will be connected.\n" "@param p: rewiring probability\n\n" - "@param loops: specifies whether loop edges are allowed\n" - "@param multiple: specifies whether multiple edges are allowed\n" + "@param allowed_edge_types: controls whether loops or multi-edges are allowed\n" + " during the generation process. Note that not all combinations are supported\n" + " for all types of graphs; an exception will be raised for unsupported\n" + " combinations. Possible values are:\n" + "\n" + " - C{\"simple\"}: simple graphs (no self-loops, no multi-edges)\n" + " - C{\"loops\"}: single self-loops allowed, but not multi-edges\n" + " - C{\"multi\"}: multi-edges allowed, but not self-loops\n" + " - C{\"all\"}: multi-edges and self-loops allowed\n" + "\n" "@see: L{Lattice()}, L{rewire()}, L{rewire_edges()} if more flexibility is\n" " needed\n" }, @@ -14940,7 +15436,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { /* interface to igraph_assortativity */ {"assortativity", (PyCFunction)igraphmodule_Graph_assortativity, METH_VARARGS | METH_KEYWORDS, - "assortativity(types1, types2=None, directed=True, normalized=True)\n--\n\n" + "assortativity(types1, types2=None, directed=True, normalized=True, weights=None)\n--\n\n" "Returns the assortativity of the graph based on numeric properties\n" "of the vertices.\n\n" "This coefficient is basically the correlation between the actual\n" @@ -14965,6 +15461,8 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "@param normalized: whether to compute the normalized covariance, i.e.\n" " Pearson correlation. Supply True here to compute the standard\n" " assortativity.\n" + "@param weights: edge weights to be used. Can be a sequence or iterable or\n" + " even an edge attribute name.\n" "@return: the assortativity coefficient\n\n" "@see: L{assortativity_degree()} when the types are the vertex degrees\n" }, @@ -14985,7 +15483,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { /* interface to igraph_assortativity_nominal */ {"assortativity_nominal", (PyCFunction)igraphmodule_Graph_assortativity_nominal, METH_VARARGS | METH_KEYWORDS, - "assortativity_nominal(types, directed=True, normalized=True)\n--\n\n" + "assortativity_nominal(types, directed=True, normalized=True, weights=None)\n--\n\n" "Returns the assortativity of the graph based on vertex categories.\n\n" "Assuming that the vertices belong to different categories, this\n" "function calculates the assortativity coefficient, which specifies\n" @@ -15004,6 +15502,8 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "@param normalized: whether to compute the (usual) normalized assortativity.\n" " The unnormalized version is identical to modularity. Supply True here to\n" " compute the standard assortativity.\n" + "@param weights: edge weights to be used. Can be a sequence or iterable or\n" + " even an edge attribute name.\n" "@return: the assortativity coefficient\n\n" }, @@ -15030,8 +15530,6 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "Calculates Kleinberg's authority score for the vertices of the graph\n\n" "@param weights: edge weights to be used. Can be a sequence or iterable or\n" " even an edge attribute name.\n" - "@param scale: whether to normalize the scores so that the largest one\n" - " is 1.\n" "@param arpack_options: an L{ARPACKOptions} object used to fine-tune\n" " the ARPACK eigenvector calculation. If omitted, the module-level\n" " variable called C{arpack_options} is used.\n" @@ -15271,13 +15769,15 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { /* interface to igraph_density */ {"density", (PyCFunction) igraphmodule_Graph_density, METH_VARARGS | METH_KEYWORDS, - "density(loops=False)\n--\n\n" + "density(loops=False, weights=None)\n--\n\n" "Calculates the density of the graph.\n\n" "@param loops: whether to take loops into consideration. If C{True},\n" " the algorithm assumes that there might be some loops in the graph\n" " and calculates the density accordingly. If C{False}, the algorithm\n" " assumes that there can't be any loops.\n" - "@return: the density of the graph."}, + "@param weights: weights associated to the edges. Can be an attribute name\n" + " as well. If C{None}, every edge will have the same weight.\n" + "@return: the (weighted or unweighted) density of the graph."}, /* interface to igraph_mean_degree */ {"mean_degree", (PyCFunction) igraphmodule_Graph_mean_degree, @@ -15449,8 +15949,6 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "components, and the eigenvector centrality calculated for each separately.\n\n" "@param directed: whether to consider edge directions in a directed\n" " graph. Ignored for undirected graphs.\n" - "@param scale: whether to normalize the centralities so the largest\n" - " one will always be 1.\n" "@param weights: edge weights given as a list or an edge attribute. If\n" " C{None}, all edges have equal weight.\n" "@param return_eigenvalue: whether to return the actual largest\n" @@ -15643,7 +16141,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { {"_get_all_simple_paths", (PyCFunction) igraphmodule_Graph_get_all_simple_paths, METH_VARARGS | METH_KEYWORDS, - "_get_all_simple_paths(v, to=None, cutoff=-1, mode=\"out\")\n--\n\n" + "_get_all_simple_paths(v, to=None, minlen=0, maxlen=-1, mode=\"out\", max_results=None)\n--\n\n" "Internal function, undocumented.\n\n" "@see: Graph.get_all_simple_paths()\n\n" }, @@ -15681,8 +16179,6 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "Calculates Kleinberg's hub score for the vertices of the graph\n\n" "@param weights: edge weights to be used. Can be a sequence or iterable or\n" " even an edge attribute name.\n" - "@param scale: whether to normalize the scores so that the largest one\n" - " is 1.\n" "@param arpack_options: an L{ARPACKOptions} object used to fine-tune\n" " the ARPACK eigenvector calculation. If omitted, the module-level\n" " variable called C{arpack_options} is used.\n" @@ -15854,7 +16350,8 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "@param vertices: a single vertex ID or a list of vertex IDs, or\n" " C{None} meaning all the vertices in the graph.\n" "@param order: the order of the neighborhood, i.e. the maximum number of\n" - " steps to take from the seed vertex.\n" + " steps to take from the seed vertex. Negative values are interpreted as\n" + " an infinite order, i.e. no limit on the number of steps.\n" "@param mode: specifies how to take into account the direction of\n" " the edges if a directed graph is analyzed. C{\"out\"} means that\n" " only the outgoing edges are followed, so all vertices reachable\n" @@ -15882,7 +16379,8 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "@param vertices: a single vertex ID or a list of vertex IDs, or\n" " C{None} meaning all the vertices in the graph.\n" "@param order: the order of the neighborhood, i.e. the maximum number of\n" - " steps to take from the seed vertex.\n" + " steps to take from the seed vertex. Negative values are interpreted as\n" + " an infinite order, i.e. no limit on the number of steps.\n" "@param mode: specifies how to take into account the direction of\n" " the edges if a directed graph is analyzed. C{\"out\"} means that\n" " only the outgoing edges are followed, so all vertices reachable\n" @@ -15960,8 +16458,8 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "permute_vertices(permutation)\n--\n\n" "Permutes the vertices of the graph according to the given permutation\n" "and returns the new graph.\n\n" - "Vertex M{k} of the original graph will become vertex M{permutation[k]}\n" - "in the new graph. No validity checks are performed on the permutation\n" + "Vertex M{k} of the new graph will belong to vertex M{permutation[k]}\n" + "in the original graph. No validity checks are performed on the permutation\n" "vector.\n\n" "@return: the new graph\n" }, @@ -16010,33 +16508,33 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { }, /* interface to igraph_rewire */ - {"rewire", (PyCFunction) igraphmodule_Graph_rewire, + {"_rewire", (PyCFunction) igraphmodule_Graph_rewire, METH_VARARGS | METH_KEYWORDS, - "rewire(n=None, mode=\"simple\")\n--\n\n" - "Randomly rewires the graph while preserving the degree distribution.\n\n" - "The rewiring is done \"in-place\", so the original graph will be modified.\n" - "If you want to preserve the original graph, use the L{copy} method before\n" - "rewiring.\n\n" - "@param n: the number of rewiring trials. The default is 10 times the number\n" - " of edges.\n" - "@param mode: the rewiring algorithm to use. It can either be C{\"simple\"} or\n" - " C{\"loops\"}; the former does not create or destroy loop edges while the\n" - " latter does.\n"}, + "_rewire(n=None, allowed_edge_types=\"simple\")\n--\n\n" + "Internal function, undocumented.\n\n" + "@see: Graph.rewire()\n\n"}, /* interface to igraph_rewire_edges */ {"rewire_edges", (PyCFunction) igraphmodule_Graph_rewire_edges, METH_VARARGS | METH_KEYWORDS, - "rewire_edges(prob, loops=False, multiple=False)\n--\n\n" + "rewire_edges(prob, allowed_edge_types=\"simple\")\n--\n\n" "Rewires the edges of a graph with constant probability.\n\n" "Each endpoint of each edge of the graph will be rewired with a constant\n" "probability, given in the first argument.\n\n" "Please note that the rewiring is done \"in-place\", so the original\n" "graph will be modified. If you want to preserve the original graph,\n" - "use the L{copy} method before.\n\n" + "use the L{cop y} method before.\n\n" "@param prob: rewiring probability\n" - "@param loops: whether the algorithm is allowed to create loop edges\n" - "@param multiple: whether the algorithm is allowed to create multiple\n" - " edges.\n"}, + "@param allowed_edge_types: controls whether loops or multi-edges are allowed\n" + " during the rewiring process. Note that not all combinations are supported\n" + " for all types of graphs; an exception will be raised for unsupported\n" + " combinations. Possible values are:\n" + "\n" + " - C{\"simple\"}: simple graphs (no self-loops, no multi-edges)\n" + " - C{\"loops\"}: single self-loops allowed, but not multi-edges\n" + " - C{\"multi\"}: multi-edges allowed, but not self-loops\n" + " - C{\"all\"}: multi-edges and self-loops allowed\n" + "\n"}, /* interface to igraph_distances */ {"distances", (PyCFunction) igraphmodule_Graph_distances, @@ -16121,7 +16619,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { /* interface to igraph_minimum_spanning_tree */ {"_spanning_tree", (PyCFunction) igraphmodule_Graph_spanning_tree, METH_VARARGS | METH_KEYWORDS, - "_spanning_tree(weights=None)\n--\n\n" + "_spanning_tree(weights=None, method=\"auto\")\n--\n\n" "Internal function, undocumented.\n\n" "@see: Graph.spanning_tree()"}, @@ -16220,7 +16718,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { " small-world networks. I{Nature} 393(6884):440-442, 1998.\n" " - Barrat A, Barthelemy M, Pastor-Satorras R and Vespignani A:\n" " The architecture of complex weighted networks. I{PNAS} 101, 3747 (2004).\n" - " U{http://arxiv.org/abs/cond-mat/0311416}.\n\n" + " U{https://arxiv.org/abs/cond-mat/0311416}.\n\n" "@param vertices: a list containing the vertex IDs which should be\n" " included in the result. C{None} means all of the vertices.\n" "@param mode: defines how to treat vertices with degree less than two.\n" @@ -16532,7 +17030,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { {"fundamental_cycles", (PyCFunction) igraphmodule_Graph_fundamental_cycles, METH_VARARGS | METH_KEYWORDS, - "fundamental_cycles(start_vid=None, cutoff=None)\n--\n\n" + "fundamental_cycles(start_vid=None, cutoff=None, weights=None)\n--\n\n" "Finds a single fundamental cycle basis of the graph\n\n" "@param start_vid: when C{None} or negative, a complete fundamental cycle basis is\n" " returned. When it is a vertex or a vertex ID, the fundamental cycles\n" @@ -16541,11 +17039,13 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "@param cutoff: when C{None} or negative, a complete cycle basis is returned. Otherwise\n" " the BFS is stopped after this many steps, so the result will effectively\n" " include cycles of length M{2 * cutoff + 1} or shorter only.\n" + "@param weights: edge weights to be used. Can be a sequence or iterable or\n" + " even an edge attribute name.\n" "@return: the cycle basis as a list of tuples containing edge IDs" }, {"minimum_cycle_basis", (PyCFunction) igraphmodule_Graph_minimum_cycle_basis, METH_VARARGS | METH_KEYWORDS, - "minimum_cycle_basis(cutoff=None, complete=True, use_cycle_order=True)\n--\n\n" + "minimum_cycle_basis(cutoff=None, complete=True, use_cycle_order=True, weights=None)\n--\n\n" "Computes a minimum cycle basis of the graph\n\n" "@param cutoff: when C{None} or negative, a complete minimum cycle basis is returned.\n" " Otherwise only those cycles in the result will be part of some minimum\n" @@ -16561,8 +17061,28 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "@param use_cycle_order: if C{True}, every cycle is returned in natural\n" " order: the edge IDs will appear ordered along the cycle. If C{False},\n" " no guarantees are given about the ordering of edge IDs within cycles.\n" + "@param weights: edge weights to be used. Can be a sequence or iterable or\n" + " even an edge attribute name.\n" "@return: the cycle basis as a list of tuples containing edge IDs" }, + {"simple_cycles", (PyCFunction) igraphmodule_Graph_simple_cycles, + METH_VARARGS | METH_KEYWORDS, + "simple_cycles(mode=None, min=-1, max=-1, output=\"vpath\")\n--\n\n" + "Finds simple cycles in a graph\n\n" + "@param mode: for directed graphs, specifies how the edge directions\n" + " should be taken into account. C{\"all\"} means that the edge directions\n" + " must be ignored, C{\"out\"} means that the edges must be oriented away\n" + " from the root, C{\"in\"} means that the edges must be oriented\n" + " towards the root. Ignored for undirected graphs.\n" + "@param min: the minimum number of vertices in a cycle\n" + " for it to be returned.\n" + "@param max: the maximum number of vertices in a cycle\n" + " for it to be considered.\n" + "@param output: determines what should be returned. If this is\n" + " C{\"vpath\"}, a list of tuples of vertex IDs will be returned. If this is\n" + " C{\"epath\"}, edge IDs are returned instead of vertex IDs.\n" + "@return: see the documentation of the C{output} parameter.\n" + }, /********************/ /* LAYOUT FUNCTIONS */ @@ -16818,7 +17338,8 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "forces among the vertices and then the physical system is simulated\n" "until it reaches an equilibrium or the maximal number of iterations is\n" "reached.\n\n" - "See U{http://www.schmuhl.org/graphopt/} for the original graphopt.\n\n" + "See U{https://web.archive.org/web/20220611030748/http://www.schmuhl.org/graphopt/}\n" + "and U{https://sourceforge.net/projects/graphopt/} for the original graphopt.\n\n" "@param niter: the number of iterations to perform. Should be a couple\n" " of hundred in general.\n\n" "@param node_charge: the charge of the vertices, used to calculate electric\n" @@ -17133,7 +17654,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "Read_DIMACS(f, directed=False)\n--\n\n" "Reads a graph from a file conforming to the DIMACS minimum-cost flow file format.\n\n" "For the exact description of the format, see\n" - "U{http://lpsolve.sourceforge.net/5.5/DIMACS.htm}\n\n" + "U{https://lpsolve.sourceforge.net/5.5/DIMACS.htm}\n\n" "Restrictions compared to the official description of the format:\n\n" " - igraph's DIMACS reader requires only three fields in an arc definition,\n" " describing the edge's source and target node and its capacity.\n" @@ -17169,7 +17690,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "Read_GraphDB(f, directed=False)\n--\n\n" "Reads a GraphDB format file and creates a graph based on it.\n\n" "GraphDB is a binary format, used in the graph database for\n" - "isomorphism testing (see U{http://amalfi.dis.unina.it/graph/}).\n\n" + "isomorphism testing (see U{https://mivia.unisa.it/datasets/graph-database/arg-database/}).\n\n" "@param f: the name of the file or a Python file handle\n" "@param directed: whether the generated graph should be directed.\n"}, /* interface to igraph_read_graph_graphml */ @@ -17392,7 +17913,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "algorithm.\n\n" "Passing the permutation returned here to L{permute_vertices()} will\n" "transform the graph into its canonical form.\n\n" - "See U{http://www.tcs.hut.fi/Software/bliss/index.html} for more information\n" + "See U{https://users.aalto.fi/~tjunttil/bliss/} for more information\n" "about the BLISS algorithm and canonical permutations.\n\n" "@param sh: splitting heuristics for graph as a case-insensitive string,\n" " with the following possible values:\n\n" @@ -17418,7 +17939,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "count_automorphisms(sh=\"fl\", color=None)\n--\n\n" "Calculates the number of automorphisms of a graph using the BLISS isomorphism\n" "algorithm.\n\n" - "See U{http://www.tcs.hut.fi/Software/bliss/index.html} for more information\n" + "See U{https://users.aalto.fi/~tjunttil/bliss/} for more information\n" "about the BLISS algorithm and canonical permutations.\n\n" "@param sh: splitting heuristics for graph as a case-insensitive string,\n" " with the following possible values:\n\n" @@ -17469,7 +17990,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { " sh1=\"fl\", sh2=None, color1=None, color2=None)\n--\n\n" "Checks whether the graph is isomorphic to another graph, using the\n" "BLISS isomorphism algorithm.\n\n" - "See U{http://www.tcs.hut.fi/Software/bliss/index.html} for more information\n" + "See U{https://users.aalto.fi/~tjunttil/bliss/} for more information\n" "about the BLISS algorithm.\n\n" "@param other: the other graph with which we want to compare the graph.\n" "@param color1: optional vector storing the coloring of the vertices of\n" @@ -18074,14 +18595,17 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { /********************************/ {"cliques", (PyCFunction) igraphmodule_Graph_cliques, METH_VARARGS | METH_KEYWORDS, - "cliques(min=0, max=0)\n--\n\n" + "cliques(min=0, max=0, max_results=None)\n--\n\n" "Returns some or all cliques of the graph as a list of tuples.\n\n" "A clique is a complete subgraph -- a set of vertices where an edge\n" "is present between any two of them (excluding loops)\n\n" "@param min: the minimum size of cliques to be returned. If zero or\n" " negative, no lower bound will be used.\n" "@param max: the maximum size of cliques to be returned. If zero or\n" - " negative, no upper bound will be used."}, + " negative, no upper bound will be used.\n" + "@param max_results: the maximum number of results to return. C{None}\n" + " means no limit on the number of results.\n" + }, {"largest_cliques", (PyCFunction) igraphmodule_Graph_largest_cliques, METH_NOARGS, "largest_cliques()\n--\n\n" @@ -18093,7 +18617,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { " L{maximal_cliques()} for the maximal cliques"}, {"maximal_cliques", (PyCFunction) igraphmodule_Graph_maximal_cliques, METH_VARARGS | METH_KEYWORDS, - "maximal_cliques(min=0, max=0, file=None)\n--\n\n" + "maximal_cliques(min=0, max=0, file=None, max_results=None)\n--\n\n" "Returns the maximal cliques of the graph as a list of tuples.\n\n" "A maximal clique is a clique which can't be extended by adding any other\n" "vertex to it. A maximal clique is not necessarily one of the largest\n" @@ -18107,6 +18631,8 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "@param file: a file object or the name of the file to write the results\n" " to. When this argument is C{None}, the maximal cliques will be returned\n" " as a list of lists.\n" + "@param max_results: the maximum number of results to return. C{None}\n" + " means no limit on the number of results.\n" "@return: the maximal cliques of the graph as a list of lists, or C{None}\n" " if the C{file} argument was given." "@see: L{largest_cliques()} for the largest cliques."}, @@ -18119,14 +18645,17 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { {"independent_vertex_sets", (PyCFunction) igraphmodule_Graph_independent_vertex_sets, METH_VARARGS | METH_KEYWORDS, - "independent_vertex_sets(min=0, max=0)\n--\n\n" + "independent_vertex_sets(min=0, max=0, max_results=None)\n--\n\n" "Returns some or all independent vertex sets of the graph as a list of tuples.\n\n" "Two vertices are independent if there is no edge between them. Members\n" "of an independent vertex set are mutually independent.\n\n" "@param min: the minimum size of sets to be returned. If zero or\n" " negative, no lower bound will be used.\n" "@param max: the maximum size of sets to be returned. If zero or\n" - " negative, no upper bound will be used."}, + " negative, no upper bound will be used.\n" + "@param max_results: the maximum number of results to return. C{None}\n" + " means no limit on the number of results.\n" + }, {"largest_independent_vertex_sets", (PyCFunction) igraphmodule_Graph_largest_independent_vertex_sets, METH_NOARGS, @@ -18141,8 +18670,8 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { " (nonextendable) independent vertex sets"}, {"maximal_independent_vertex_sets", (PyCFunction) igraphmodule_Graph_maximal_independent_vertex_sets, - METH_NOARGS, - "maximal_independent_vertex_sets()\n--\n\n" + METH_VARARGS | METH_KEYWORDS, + "maximal_independent_vertex_sets(min=0, max=0, max_results=None)\n--\n\n" "Returns the maximal independent vertex sets of the graph as a list of tuples.\n\n" "A maximal independent vertex set is an independent vertex set\n" "which can't be extended by adding any other vertex to it. A maximal\n" @@ -18151,6 +18680,12 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "B{Reference}: S. Tsukiyama, M. Ide, H. Ariyoshi and I. Shirawaka: A new\n" "algorithm for generating all the maximal independent sets.\n" "I{SIAM J Computing}, 6:505-517, 1977.\n\n" + "@param min: the minimum size of sets to be returned. If zero or\n" + " negative, no lower bound will be used.\n" + "@param max: the maximum size of sets to be returned. If zero or\n" + " negative, no upper bound will be used.\n" + "@param max_results: the maximum number of results to return. C{None}\n" + " means no limit on the number of results.\n\n" "@see: L{largest_independent_vertex_sets()} for the largest independent\n" " vertex sets\n" }, @@ -18256,21 +18791,43 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "\n" "@see: modularity()\n" }, + {"community_fluid_communities", + (PyCFunction) igraphmodule_Graph_community_fluid_communities, + METH_VARARGS | METH_KEYWORDS, + "community_fluid_communities(no_of_communities)\n--\n\n" + "Community detection based on fluids interacting on the graph.\n\n" + "The algorithm is based on the simple idea of several fluids interacting\n" + "in a non-homogeneous environment (the graph topology), expanding and\n" + "contracting based on their interaction and density. Weighted graphs are\n" + "not supported.\n\n" + "B{Reference}\n\n" + " - Parés F, Gasulla DG, et. al. (2018) Fluid Communities: A Competitive,\n" + " Scalable and Diverse Community Detection Algorithm. In: Complex Networks\n" + " & Their Applications VI: Proceedings of Complex Networks 2017 (The Sixth\n" + " International Conference on Complex Networks and Their Applications),\n" + " Springer, vol 689, p 229. https://doi.org/10.1007/978-3-319-72150-7_19\n\n" + "@param no_of_communities: The number of communities to be found. Must be\n" + " greater than 0 and fewer than number of vertices in the graph.\n" + "@return: a list with the community membership of each vertex.\n" + "@note: The graph must be simple and connected. Edge directions will be\n" + " ignored if the graph is directed.\n" + "@note: Time complexity: O(|E|)\n", + }, {"community_infomap", (PyCFunction) igraphmodule_Graph_community_infomap, METH_VARARGS | METH_KEYWORDS, "community_infomap(edge_weights=None, vertex_weights=None, trials=10)\n--\n\n" "Finds the community structure of the network according to the Infomap\n" "method of Martin Rosvall and Carl T. Bergstrom.\n\n" - "See U{http://www.mapequation.org} for a visualization of the algorithm\n" - "or one of the references provided below.\n" - "B{References}\n" + "See U{https://www.mapequation.org} for a visualization of the algorithm\n" + "or one of the references provided below.\n\n" + "B{References}\n\n" " - M. Rosvall and C. T. Bergstrom: I{Maps of information flow reveal\n" " community structure in complex networks}. PNAS 105, 1118 (2008).\n" - " U{http://arxiv.org/abs/0707.0609}\n" + " U{https://arxiv.org/abs/0707.0609}\n" " - M. Rosvall, D. Axelsson and C. T. Bergstrom: I{The map equation}.\n" " I{Eur Phys J Special Topics} 178, 13 (2009).\n" - " U{http://arxiv.org/abs/0906.1405}\n" + " U{https://arxiv.org/abs/0906.1405}\n" "\n" "@param edge_weights: name of an edge attribute or a list containing\n" " edge weights.\n" @@ -18283,7 +18840,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { {"community_label_propagation", (PyCFunction) igraphmodule_Graph_community_label_propagation, METH_VARARGS | METH_KEYWORDS, - "community_label_propagation(weights=None, initial=None, fixed=None)\n--\n\n" + "community_label_propagation(weights=None, initial=None, fixed=None, variant=\"dominance\")\n--\n\n" "Finds the community structure of the graph according to the label\n" "propagation method of Raghavan et al.\n\n" "Initially, each vertex is assigned a different label. After that,\n" @@ -18299,7 +18856,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "B{Reference}: Raghavan, U.N. and Albert, R. and Kumara, S. Near linear\n" "time algorithm to detect community structures in large-scale\n" "networks. I{Phys Rev E} 76:036106, 2007.\n" - "U{http://arxiv.org/abs/0709.2938}.\n" + "U{https://arxiv.org/abs/0709.2938}.\n" "\n" "@param weights: name of an edge attribute or a list containing\n" " edge weights\n" @@ -18313,6 +18870,9 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { " It only makes sense if initial labels are also given. Unlabeled\n" " vertices cannot be fixed. Note that vertex attribute names are not\n" " accepted here.\n" + "@param variant: the variant of the algorithm to use: C{\"dominance\"}, \n" + " C{\"retention\"} or C{\"fast\"}. See the documentation of the C core\n" + " of igraph for details.\n" "@return: the resulting membership vector\n" }, {"community_leading_eigenvector", (PyCFunction) igraphmodule_Graph_community_leading_eigenvector, @@ -18354,7 +18914,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "\n" "B{Reference}: VD Blondel, J-L Guillaume, R Lambiotte and E Lefebvre: Fast\n" "unfolding of community hierarchies in large networks. J Stat Mech\n" - "P10008 (2008), U{http://arxiv.org/abs/0803.0476}\n" + "P10008 (2008), U{https://arxiv.org/abs/0803.0476}\n" "\n" "Attention: this function is wrapped in a more convenient syntax in the\n" "derived class L{Graph}. It is advised to use that instead of this version.\n\n" @@ -18385,12 +18945,18 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "is typically high. So we gradually remove the edge with the highest\n" "betweenness from the network and recalculate edge betweenness after every\n" "removal, as long as all edges are removed.\n\n" + "When edge weights are given, the ratio of betweenness and weight values\n" + "is used to choose which edges to remove first, as described in\n" + "M. E. J. Newman: Analysis of Weighted Networks (2004), Section C.\n" + "Thus, edges with large weights are treated as strong connections,\n" + "and will be removed later than weak connections having similar betweenness.\n" + "Weights are also used for calculating modularity.\n\n" "Attention: this function is wrapped in a more convenient syntax in the\n" "derived class L{Graph}. It is advised to use that instead of this version.\n\n" "@param directed: whether to take into account the directedness of the edges\n" " when we calculate the betweenness values.\n" "@param weights: name of an edge attribute or a list containing\n" - " edge weights.\n\n" + " edge weights. Higher weights indicate stronger connections.\n\n" "@return: a tuple with the merge matrix that describes the dendrogram\n" " and the modularity scores before each merge. The modularity scores\n" " use the weights if the original graph was weighted.\n" @@ -18452,6 +19018,42 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { " original implementation is used.\n" "@return: the community membership vector.\n" }, + {"community_voronoi", + (PyCFunction) igraphmodule_Graph_community_voronoi, + METH_VARARGS | METH_KEYWORDS, + "community_voronoi(lengths=None, weights=None, mode=\"out\", radius=None)\n--\n\n" + "Finds communities using Voronoi partitioning.\n\n" + "This function finds communities using a Voronoi partitioning of vertices based\n" + "on the given edge lengths divided by the edge clustering coefficient.\n" + "The generator vertices are chosen to be those with the largest local relative\n" + "density within a radius, with the local relative density of a vertex defined as\n" + "C{s * m / (m + k)}, where s is the strength of the vertex, m is the number of\n" + "edges within the vertex's first order neighborhood, while k is the number of\n" + "edges with only one endpoint within this neighborhood.\n\n" + "B{References}\n\n" + " - Deritei et al., Community detection by graph Voronoi diagrams,\n" + " New Journal of Physics 16, 063007 (2014)\n" + " U{https://doi.org/10.1088/1367-2630/16/6/063007}\n" + " - Molnár et al., Community Detection in Directed Weighted Networks\n" + " using Voronoi Partitioning, Scientific Reports 14, 8124 (2024)\n" + " U{https://doi.org/10.1038/s41598-024-58624-4}\n\n" + "@param lengths: edge lengths, or C{None} to consider all edges as having\n" + " unit length. Voronoi partitioning will use edge lengths equal to\n" + " lengths / ECC where ECC is the edge clustering coefficient.\n" + "@param weights: edge weights, or C{None} to consider all edges as having\n" + " unit weight. Weights are used when selecting generator points, as well\n" + " as for computing modularity.\n" + "@param mode: if C{\"out\"} (the default), distances from generator points to all other\n" + " nodes are considered. If C{\"in\"}, the reverse distances are used.\n" + " If C{\"all\"}, edge directions are ignored. This parameter is ignored\n" + " for undirected graphs.\n" + "@param radius: the radius/resolution to use when selecting generator points.\n" + " The larger this value, the fewer partitions there will be. Pass C{None}\n" + " to automatically select the radius that maximizes modularity.\n" + "@return: a tuple containing the membership vector, generator vertices, and\n" + " modularity score: (membership, generators, modularity).\n" + "@rtype: tuple\n" + }, {"community_leiden", (PyCFunction) igraphmodule_Graph_community_leiden, METH_VARARGS | METH_KEYWORDS, @@ -18493,7 +19095,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { "Attention: this function is wrapped in a more convenient syntax in the\n" "derived class L{Graph}. It is advised to use that instead of this version.\n\n" "B{Reference}: Pascal Pons, Matthieu Latapy: Computing communities in large\n" - "networks using random walks, U{http://arxiv.org/abs/physics/0512106}.\n\n" + "networks using random walks, U{https://arxiv.org/abs/physics/0512106}.\n\n" "@param weights: name of an edge attribute or a list containing\n" " edge weights\n" "@return: a tuple with the list of merges and the modularity scores corresponding\n" @@ -18548,6 +19150,22 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { " the given length (shorter if the random walk got stuck).\n" }, + /**********************/ + /* SPATIAL GRAPHS */ + /**********************/ + {"Nearest_Neighbor_Graph", (PyCFunction)igraphmodule_Graph_Nearest_Neighbor_Graph, + METH_VARARGS | METH_CLASS | METH_KEYWORDS, + "Nearest_Neighbor_Graph(points, k=1, r=-1, metric=\"euclidean\", directed=False)\n--\n\n" + "Constructs a k nearest neighbor graph of a give point set. Each point is\n" + "connected to at most k spatial neighbors within a radius of 1.\n\n" + "@param points: coordinates of the points to use, in an arbitrary number of dimensions\n" + "@param k: at most how many neighbors to connect to. Pass a negative value to ignore\n" + "@param r: only neighbors within this radius are considered. Pass a negative value to ignore\n" + "@param metric: the metric to use. C{\"euclidean\"} and C{\"manhattan\"} are supported.\n" + "@param directed: whethe to create directed edges.\n" + "@return: the nearest neighbor graph.\n" + }, + /**********************/ /* INTERNAL FUNCTIONS */ /**********************/ diff --git a/src/_igraph/igraphmodule.c b/src/_igraph/igraphmodule.c index 97864f61e..cd57da92a 100644 --- a/src/_igraph/igraphmodule.c +++ b/src/_igraph/igraphmodule.c @@ -142,12 +142,8 @@ static int igraphmodule_clear(PyObject *m) { return 0; } -static igraph_error_t igraphmodule_igraph_interrupt_hook(void* data) { - if (PyErr_CheckSignals()) { - IGRAPH_FINALLY_FREE(); - return IGRAPH_INTERRUPTED; - } - return IGRAPH_SUCCESS; +static igraph_bool_t igraphmodule_igraph_interrupt_hook() { + return PyErr_CheckSignals(); } igraph_error_t igraphmodule_igraph_progress_hook(const char* message, igraph_real_t percent, @@ -231,6 +227,38 @@ PyObject* igraphmodule_set_status_handler(PyObject* self, PyObject* o) { Py_RETURN_NONE; } +PyObject* igraphmodule_align_layout(PyObject* self, PyObject* args, PyObject* kwds) { + static char* kwlist[] = {"graph", "layout", NULL}; + PyObject *graph_o, *layout_o; + PyObject *res; + igraph_t *graph; + igraph_matrix_t layout; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, &graph_o, &layout_o)) { + return NULL; + } + + if (igraphmodule_PyObject_to_igraph_t(graph_o, &graph)) { + return NULL; + } + + if (igraphmodule_PyObject_to_matrix_t(layout_o, &layout, "layout")) { + return NULL; + } + + if (igraph_layout_align(graph, &layout)) { + igraphmodule_handle_igraph_error(); + igraph_matrix_destroy(&layout); + return NULL; + } + + res = igraphmodule_matrix_t_to_PyList(&layout, IGRAPHMODULE_TYPE_FLOAT); + + igraph_matrix_destroy(&layout); + + return res; +} + PyObject* igraphmodule_convex_hull(PyObject* self, PyObject* args, PyObject* kwds) { static char* kwlist[] = {"vs", "coords", NULL}; PyObject *vs, *o, *o1 = 0, *o2 = 0, *o1_float, *o2_float, *coords = Py_False; @@ -317,7 +345,7 @@ PyObject* igraphmodule_convex_hull(PyObject* self, PyObject* args, PyObject* kwd igraph_matrix_destroy(&mtrx); return NULL; } - if (igraph_convex_hull(&mtrx, &result, 0)) { + if (igraph_convex_hull_2d(&mtrx, &result, 0)) { igraphmodule_handle_igraph_error(); igraph_matrix_destroy(&mtrx); igraph_vector_int_destroy(&result); @@ -331,7 +359,7 @@ PyObject* igraphmodule_convex_hull(PyObject* self, PyObject* args, PyObject* kwd igraph_matrix_destroy(&mtrx); return NULL; } - if (igraph_convex_hull(&mtrx, 0, &resmat)) { + if (igraph_convex_hull_2d(&mtrx, 0, &resmat)) { igraphmodule_handle_igraph_error(); igraph_matrix_destroy(&mtrx); igraph_matrix_destroy(&resmat); @@ -641,7 +669,7 @@ PyObject* igraphmodule_split_join_distance(PyObject *self, static char* kwlist[] = { "comm1", "comm2", NULL }; PyObject *comm1_o, *comm2_o; igraph_vector_int_t comm1, comm2; - igraph_integer_t distance12, distance21; + igraph_int_t distance12, distance21; if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, &comm1_o, &comm2_o)) @@ -666,7 +694,7 @@ PyObject* igraphmodule_split_join_distance(PyObject *self, igraph_vector_int_destroy(&comm1); igraph_vector_int_destroy(&comm2); - /* sizeof(Py_ssize_t) is most likely the same as sizeof(igraph_integer_t), + /* sizeof(Py_ssize_t) is most likely the same as sizeof(igraph_int_t), * but even if it isn't, we cast explicitly so we are safe */ return Py_BuildValue("nn", (Py_ssize_t)distance12, (Py_ssize_t)distance21); } @@ -687,7 +715,7 @@ PyObject *igraphmodule_umap_compute_weights( PyObject *result_o; igraphmodule_GraphObject * graph; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, &graph_o, &dist_o)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O", kwlist, igraphmodule_GraphType, &graph_o, &dist_o)) return NULL; /* Initialize distances */ @@ -794,6 +822,10 @@ static PyMethodDef igraphmodule_methods[] = METH_VARARGS | METH_KEYWORDS, "_power_law_fit(data, xmin=-1, force_continuous=False, p_precision=0.01)\n--\n\n" }, + {"_align_layout", (PyCFunction)igraphmodule_align_layout, + METH_VARARGS | METH_KEYWORDS, + "_align_layout(graph, layout)\n--\n\n" + }, {"convex_hull", (PyCFunction)igraphmodule_convex_hull, METH_VARARGS | METH_KEYWORDS, "convex_hull(vs, coords=False)\n--\n\n" @@ -1035,9 +1067,10 @@ PyObject* PyInit__igraph(void) PyObject* m; static void *PyIGraph_API[PyIGraph_API_pointers]; PyObject *c_api_object; + igraph_error_t retval; /* Prevent linking 64-bit igraph to 32-bit Python */ - PY_IGRAPH_ASSERT_AT_BUILD_TIME(sizeof(igraph_integer_t) >= sizeof(Py_ssize_t)); + PY_IGRAPH_ASSERT_AT_BUILD_TIME(sizeof(igraph_int_t) >= sizeof(Py_ssize_t)); /* Check if the module is already initialized (possibly in another Python * interpreter. If so, bail out as we don't support this. */ @@ -1047,6 +1080,14 @@ PyObject* PyInit__igraph(void) INITERROR; } + /* Initialize the igraph library */ + retval = igraph_setup(); + if (retval != IGRAPH_SUCCESS) { + PyErr_Format(PyExc_RuntimeError, "Failed to initialize the C core of " + "the igraph library, code: %d", retval); + INITERROR; + } + /* Run basic initialization of the pyhelpers.c module */ if (igraphmodule_helpers_init()) { INITERROR; @@ -1117,9 +1158,6 @@ PyObject* PyInit__igraph(void) PyModule_AddIntConstant(m, "GET_ADJACENCY_LOWER", IGRAPH_GET_ADJACENCY_LOWER); PyModule_AddIntConstant(m, "GET_ADJACENCY_BOTH", IGRAPH_GET_ADJACENCY_BOTH); - PyModule_AddIntConstant(m, "REWIRING_SIMPLE", IGRAPH_REWIRING_SIMPLE); - PyModule_AddIntConstant(m, "REWIRING_SIMPLE_LOOPS", IGRAPH_REWIRING_SIMPLE_LOOPS); - PyModule_AddIntConstant(m, "ADJ_DIRECTED", IGRAPH_ADJ_DIRECTED); PyModule_AddIntConstant(m, "ADJ_UNDIRECTED", IGRAPH_ADJ_UNDIRECTED); PyModule_AddIntConstant(m, "ADJ_MAX", IGRAPH_ADJ_MAX); diff --git a/src/_igraph/indexing.c b/src/_igraph/indexing.c index 5643cabba..8da3f600e 100644 --- a/src/_igraph/indexing.c +++ b/src/_igraph/indexing.c @@ -31,8 +31,8 @@ /***************************************************************************/ static PyObject* igraphmodule_i_Graph_adjmatrix_indexing_get_value_for_vertex_pair( - igraph_t* graph, igraph_integer_t from, igraph_integer_t to, PyObject* values) { - igraph_integer_t eid; + igraph_t* graph, igraph_int_t from, igraph_int_t to, PyObject* values) { + igraph_int_t eid; PyObject* result; /* Retrieving a single edge */ @@ -53,14 +53,14 @@ static PyObject* igraphmodule_i_Graph_adjmatrix_indexing_get_value_for_vertex_pa } static PyObject* igraphmodule_i_Graph_adjmatrix_get_index_row(igraph_t* graph, - igraph_integer_t from, igraph_vs_t* to, igraph_neimode_t neimode, + igraph_int_t from, igraph_vs_t* to, igraph_neimode_t neimode, PyObject* values); PyObject* igraphmodule_Graph_adjmatrix_get_index(igraph_t* graph, PyObject* row_index, PyObject* column_index, PyObject* attr_name) { PyObject *result = 0, *values; igraph_vs_t vs1, vs2; - igraph_integer_t vid1 = -1, vid2 = -1; + igraph_int_t vid1 = -1, vid2 = -1; char* attr; if (igraphmodule_PyObject_to_vs_t(row_index, &vs1, graph, 0, &vid1)) @@ -131,10 +131,10 @@ PyObject* igraphmodule_Graph_adjmatrix_get_index(igraph_t* graph, } static PyObject* igraphmodule_i_Graph_adjmatrix_get_index_row(igraph_t* graph, - igraph_integer_t from, igraph_vs_t* to, igraph_neimode_t neimode, + igraph_int_t from, igraph_vs_t* to, igraph_neimode_t neimode, PyObject* values) { igraph_vector_int_t eids; - igraph_integer_t eid, i, n, v; + igraph_int_t eid, i, n, v; igraph_vit_t vit; PyObject *result = 0, *item; @@ -142,7 +142,7 @@ static PyObject* igraphmodule_i_Graph_adjmatrix_get_index_row(igraph_t* graph, /* Simple case: all edges */ IGRAPH_PYCHECK(igraph_vector_int_init(&eids, 0)); IGRAPH_FINALLY(igraph_vector_int_destroy, &eids); - IGRAPH_PYCHECK(igraph_incident(graph, &eids, from, neimode)); + IGRAPH_PYCHECK(igraph_incident(graph, &eids, from, neimode, IGRAPH_LOOPS)); n = igraph_vector_int_size(&eids); result = igraphmodule_PyList_Zeroes(igraph_vcount(graph)); @@ -263,12 +263,12 @@ void igraphmodule_i_Graph_adjmatrix_set_index_data_destroy( } static int igraphmodule_i_Graph_adjmatrix_set_index_row(igraph_t* graph, - igraph_integer_t from, igraph_vs_t* to, igraph_neimode_t neimode, + igraph_int_t from, igraph_vs_t* to, igraph_neimode_t neimode, PyObject* values, PyObject* new_value, igraphmodule_i_Graph_adjmatrix_set_index_data_t* data) { PyObject *iter = 0, *item; igraph_vit_t vit; - igraph_integer_t v, v1, v2, eid; + igraph_int_t v, v1, v2, eid; igraph_bool_t deleting, ok = true; /* Check whether new_value is an iterable (and not a string). If not, @@ -423,7 +423,7 @@ int igraphmodule_Graph_adjmatrix_set_index(igraph_t* graph, PyObject *values; igraph_vs_t vs1, vs2; igraph_vit_t vit; - igraph_integer_t vid1 = -1, vid2 = -1, eid = -1; + igraph_int_t vid1 = -1, vid2 = -1, eid = -1; igraph_bool_t ok = true; igraphmodule_i_Graph_adjmatrix_set_index_data_t data; char* attr; diff --git a/src/_igraph/utils.c b/src/_igraph/utils.c deleted file mode 100644 index 2fd58420c..000000000 --- a/src/_igraph/utils.c +++ /dev/null @@ -1,70 +0,0 @@ -/* - IGraph library. - Copyright (C) 2006-2023 Tamas Nepusz - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - 02110-1301 USA - -*/ - -#include "utils.h" - -/************************ Miscellaneous functions *************************/ - -/** - * Helper function that automatically selects a shortest path algorithm based on - * a graph, its weight vector and the source vertex set (if any). - */ -igraphmodule_shortest_path_algorithm_t igraphmodule_select_shortest_path_algorithm( - const igraph_t* graph, const igraph_vector_t* weights, const igraph_vs_t* from_vs, - igraph_neimode_t mode, igraph_bool_t allow_johnson -) { - igraph_error_t retval; - igraph_integer_t vs_size; - - /* Select the most suitable algorithm */ - if (weights && igraph_vector_size(weights) > 0) { - if (igraph_vector_min(weights) >= 0) { - /* Only positive weights, use Dijkstra's algorithm */ - return IGRAPHMODULE_SHORTEST_PATH_ALGORITHM_DIJKSTRA; - } else if (allow_johnson) { - /* There are negative weights. For a small number of sources, use Bellman-Ford. - * Otherwise, use Johnson's algorithm */ - if (from_vs) { - retval = igraph_vs_size(graph, from_vs, &vs_size); - } else { - retval = IGRAPH_SUCCESS; - vs_size = IGRAPH_INTEGER_MAX; - } - if (retval == IGRAPH_SUCCESS) { - if (vs_size <= 100 || mode != IGRAPH_OUT) { - return IGRAPHMODULE_SHORTEST_PATH_ALGORITHM_BELLMAN_FORD; - } else { - return IGRAPHMODULE_SHORTEST_PATH_ALGORITHM_JOHNSON; - } - } else { - /* Error while calling igraph_vs_size(). Use Bellman-Ford. */ - return IGRAPHMODULE_SHORTEST_PATH_ALGORITHM_BELLMAN_FORD; - } - } else { - /* Johnson's algorithm is disallowed, use Bellman-Ford */ - return IGRAPHMODULE_SHORTEST_PATH_ALGORITHM_BELLMAN_FORD; - } - } else { - /* No weights or empty weight vector, use Dijstra, which should fall back to - * an unweighted algorithm */ - return IGRAPHMODULE_SHORTEST_PATH_ALGORITHM_DIJKSTRA; - } -} diff --git a/src/_igraph/utils.h b/src/_igraph/utils.h deleted file mode 100644 index bc39b84b9..000000000 --- a/src/_igraph/utils.h +++ /dev/null @@ -1,40 +0,0 @@ -/* -*- mode: C -*- */ -/* - IGraph library. - Copyright (C) 2006-2023 Tamas Nepusz - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - 02110-1301 USA - -*/ - -#ifndef IGRAPHMODULE_UTILS_H -#define IGRAPHMODULE_UTILS_H - -#include "preamble.h" - -#include -#include -#include "convert.h" -#include "graphobject.h" - -/************************ Miscellaneous functions *************************/ - -igraphmodule_shortest_path_algorithm_t igraphmodule_select_shortest_path_algorithm( - const igraph_t* graph, const igraph_vector_t* weights, const igraph_vs_t* from_vs, - igraph_neimode_t mode, igraph_bool_t allow_johnson -); - -#endif diff --git a/src/_igraph/vertexobject.c b/src/_igraph/vertexobject.c index b15e67d90..0c1ad31e9 100644 --- a/src/_igraph/vertexobject.c +++ b/src/_igraph/vertexobject.c @@ -54,7 +54,7 @@ int igraphmodule_Vertex_Check(PyObject* obj) { * exception and returns zero if the vertex object is invalid. */ int igraphmodule_Vertex_Validate(PyObject* obj) { - igraph_integer_t n; + igraph_int_t n; igraphmodule_VertexObject *self; igraphmodule_GraphObject *graph; @@ -98,7 +98,7 @@ int igraphmodule_Vertex_Validate(PyObject* obj) { * changes, your existing vertex objects will point to elsewhere * (or they might even get invalidated). */ -PyObject* igraphmodule_Vertex_New(igraphmodule_GraphObject *gref, igraph_integer_t idx) { +PyObject* igraphmodule_Vertex_New(igraphmodule_GraphObject *gref, igraph_int_t idx) { return PyObject_CallFunction((PyObject*) igraphmodule_VertexType, "On", gref, (Py_ssize_t) idx); } @@ -110,7 +110,7 @@ PyObject* igraphmodule_Vertex_New(igraphmodule_GraphObject *gref, igraph_integer static int igraphmodule_Vertex_init(igraphmodule_EdgeObject *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = { "graph", "vid", NULL }; PyObject *g, *index_o = Py_None; - igraph_integer_t vid; + igraph_int_t vid; igraph_t *graph; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|O", kwlist, @@ -531,7 +531,7 @@ int igraphmodule_Vertex_set_attribute(igraphmodule_VertexObject* self, PyObject* /* result is NULL, check whether there was an error */ if (!PyErr_Occurred()) { /* no, there wasn't, so we must simply add the attribute */ - igraph_integer_t i, n = igraph_vcount(&o->g); + igraph_int_t i, n = igraph_vcount(&o->g); result = PyList_New(n); for (i = 0; i < n; i++) { if (i != self->idx) { @@ -572,9 +572,9 @@ PyObject* igraphmodule_Vertex_get_index(igraphmodule_VertexObject* self, void* c /** * \ingroup python_interface_vertex - * Returns the vertex index as an igraph_integer_t + * Returns the vertex index as an igraph_int_t */ -igraph_integer_t igraphmodule_Vertex_get_index_igraph_integer(igraphmodule_VertexObject* self) { +igraph_int_t igraphmodule_Vertex_get_index_igraph_integer(igraphmodule_VertexObject* self) { return self->idx; } @@ -618,7 +618,7 @@ static PyObject* _convert_to_edge_list(igraphmodule_VertexObject* vertex, PyObje for (i = 0; i < n; i++) { PyObject* idx = PyList_GetItem(obj, i); PyObject* edge; - igraph_integer_t idx_int; + igraph_int_t idx_int; if (!idx) { return NULL; @@ -663,7 +663,7 @@ static PyObject* _convert_to_vertex_list(igraphmodule_VertexObject* vertex, PyOb for (i = 0; i < n; i++) { PyObject* idx = PyList_GetItem(obj, i); PyObject* v; - igraph_integer_t idx_int; + igraph_int_t idx_int; if (!idx) { return NULL; @@ -840,10 +840,10 @@ PyMethodDef igraphmodule_Vertex_methods[] = { GRAPH_PROXY_METHOD_SPEC(neighbors, "neighbors"), GRAPH_PROXY_METHOD_SPEC_3(outdegree, "outdegree"), GRAPH_PROXY_METHOD_SPEC_3(pagerank, "pagerank"), - GRAPH_PROXY_METHOD_SPEC(predecessors, "predecessors"), + GRAPH_PROXY_METHOD_SPEC_3(predecessors, "predecessors"), GRAPH_PROXY_METHOD_SPEC(personalized_pagerank, "personalized_pagerank"), GRAPH_PROXY_METHOD_SPEC(strength, "strength"), - GRAPH_PROXY_METHOD_SPEC(successors, "successors"), + GRAPH_PROXY_METHOD_SPEC_3(successors, "successors"), {NULL} }; @@ -875,6 +875,9 @@ PyDoc_STRVAR( " >>> v[\"color\"] = \"red\" #doctest: +SKIP\n" " >>> print(v[\"color\"]) #doctest: +SKIP\n" " red\n" + "\n" + "@ivar index: Index of the vertex\n" + "@ivar graph: The graph the vertex belongs to\t" ); int igraphmodule_Vertex_register_type() { diff --git a/src/_igraph/vertexobject.h b/src/_igraph/vertexobject.h index ef8ce9e7b..21f1be9b7 100644 --- a/src/_igraph/vertexobject.h +++ b/src/_igraph/vertexobject.h @@ -35,7 +35,7 @@ typedef struct { PyObject_HEAD igraphmodule_GraphObject* gref; - igraph_integer_t idx; + igraph_int_t idx; long hash; } igraphmodule_VertexObject; @@ -44,8 +44,8 @@ extern PyTypeObject* igraphmodule_VertexType; int igraphmodule_Vertex_register_type(void); int igraphmodule_Vertex_Check(PyObject* obj); -PyObject* igraphmodule_Vertex_New(igraphmodule_GraphObject *gref, igraph_integer_t idx); -igraph_integer_t igraphmodule_Vertex_get_index_igraph_integer(igraphmodule_VertexObject* self); +PyObject* igraphmodule_Vertex_New(igraphmodule_GraphObject *gref, igraph_int_t idx); +igraph_int_t igraphmodule_Vertex_get_index_igraph_integer(igraphmodule_VertexObject* self); PyObject* igraphmodule_Vertex_update_attributes(PyObject* self, PyObject* args, PyObject* kwds); #endif diff --git a/src/_igraph/vertexseqobject.c b/src/_igraph/vertexseqobject.c index d511cd1d0..4e4f66e59 100644 --- a/src/_igraph/vertexseqobject.c +++ b/src/_igraph/vertexseqobject.c @@ -106,7 +106,7 @@ int igraphmodule_VertexSeq_init(igraphmodule_VertexSeqObject *self, igraph_vs_all(&vs); } else if (PyLong_Check(vsobj)) { /* We selected a single vertex */ - igraph_integer_t idx; + igraph_int_t idx; if (igraphmodule_PyObject_to_integer_t(vsobj, &idx)) { return -1; @@ -120,7 +120,7 @@ int igraphmodule_VertexSeq_init(igraphmodule_VertexSeqObject *self, igraph_vs_1(&vs, idx); } else { igraph_vector_int_t v; - igraph_integer_t n = igraph_vcount(&((igraphmodule_GraphObject*)g)->g); + igraph_int_t n = igraph_vcount(&((igraphmodule_GraphObject*)g)->g); if (igraphmodule_PyObject_to_vector_int_t(vsobj, &v)) return -1; if (!igraph_vector_int_isininterval(&v, 0, n-1)) { @@ -168,7 +168,7 @@ void igraphmodule_VertexSeq_dealloc(igraphmodule_VertexSeqObject* self) { */ Py_ssize_t igraphmodule_VertexSeq_sq_length(igraphmodule_VertexSeqObject* self) { igraph_t *g; - igraph_integer_t result; + igraph_int_t result; if (!self->gref) { return -1; @@ -190,7 +190,7 @@ Py_ssize_t igraphmodule_VertexSeq_sq_length(igraphmodule_VertexSeqObject* self) PyObject* igraphmodule_VertexSeq_sq_item(igraphmodule_VertexSeqObject* self, Py_ssize_t i) { igraph_t *g; - igraph_integer_t idx = -1; + igraph_int_t idx = -1; if (!self->gref) { return NULL; @@ -260,7 +260,7 @@ PyObject* igraphmodule_VertexSeq_attribute_names(igraphmodule_VertexSeqObject* s PyObject* igraphmodule_VertexSeq_get_attribute_values(igraphmodule_VertexSeqObject* self, PyObject* o) { igraphmodule_GraphObject *gr = self->gref; PyObject *result=0, *values, *item; - igraph_integer_t i, n; + igraph_int_t i, n; if (!igraphmodule_attribute_name_check(o)) return 0; @@ -409,7 +409,7 @@ int igraphmodule_VertexSeq_set_attribute_values_mapping(igraphmodule_VertexSeqOb PyObject *dict, *list, *item; igraphmodule_GraphObject *gr; igraph_vector_int_t vs; - igraph_integer_t i, j, n, no_of_nodes; + igraph_int_t i, j, n, no_of_nodes; gr = self->gref; dict = ATTR_STRUCT_DICT(&gr->g)[ATTRHASH_IDX_VERTEX]; @@ -540,7 +540,7 @@ int igraphmodule_VertexSeq_set_attribute_values_mapping(igraphmodule_VertexSeqOb /* We don't have attributes with the given name yet. Create an entry * in the dict, create a new list, fill with None for vertices not in the * sequence and copy the rest */ - igraph_integer_t n2 = igraph_vcount(&gr->g); + igraph_int_t n2 = igraph_vcount(&gr->g); list = PyList_New(n2); if (list == 0) { igraph_vector_int_destroy(&vs); @@ -605,7 +605,7 @@ PyObject* igraphmodule_VertexSeq_set_attribute_values(igraphmodule_VertexSeqObje */ PyObject* igraphmodule_VertexSeq_find(igraphmodule_VertexSeqObject *self, PyObject *args) { PyObject *item; - igraph_integer_t i; + igraph_int_t i; Py_ssize_t n; igraph_vit_t vit; @@ -683,7 +683,7 @@ PyObject* igraphmodule_VertexSeq_select(igraphmodule_VertexSeqObject *self, PyObject *args) { igraphmodule_VertexSeqObject *result; igraphmodule_GraphObject *gr; - igraph_integer_t igraph_idx, i, j, n, m; + igraph_int_t igraph_idx, i, j, n, m; igraph_bool_t working_on_whole_graph = igraph_vs_is_all(&self->vs); igraph_vector_int_t v, v2; @@ -787,7 +787,7 @@ PyObject* igraphmodule_VertexSeq_select(igraphmodule_VertexSeqObject *self, for (; i < n; i++) { PyObject *item2 = PyTuple_GetItem(args, i); - igraph_integer_t idx; + igraph_int_t idx; if (item2 == 0) { Py_DECREF(result); igraph_vector_int_destroy(&v); diff --git a/src/igraph/__init__.py b/src/igraph/__init__.py index 7f0e328b7..6a4e189b9 100644 --- a/src/igraph/__init__.py +++ b/src/igraph/__init__.py @@ -2,7 +2,6 @@ igraph library. """ - __license__ = """ Copyright (C) 2006- The igraph development team @@ -48,8 +47,6 @@ IN, InternalError, OUT, - REWIRING_SIMPLE, - REWIRING_SIMPLE_LOOPS, STAR_IN, STAR_MUTUAL, STAR_OUT, @@ -109,7 +106,9 @@ _community_multilevel, _community_optimal_modularity, _community_edge_betweenness, + _community_fluid_communities, _community_spinglass, + _community_voronoi, _community_walktrap, _k_core, _community_leiden, @@ -225,6 +224,7 @@ from igraph.io.images import _write_graph_to_svg from igraph.layout import ( Layout, + align_layout, _layout, _layout_auto, _layout_sugiyama, @@ -239,6 +239,7 @@ intersection, operator_method_registry as _operator_method_registry, ) +from igraph.rewiring import _rewire from igraph.seq import EdgeSeq, VertexSeq, _add_proxy_methods from igraph.statistics import ( FittedPowerLaw, @@ -580,6 +581,23 @@ def is_weighted(self): """ return "weight" in self.edge_attributes() + ############################################# + # Neighbors + + def predecessors(self, vertex, loops=True, multiple=True): + """Returns the predecessors of a given vertex. + + Equivalent to calling the L{Graph.neighbors()} method with mode=C{\"in\"}. + """ + return self.neighbors(vertex, mode="in", loops=loops, multiple=multiple) + + def successors(self, vertex, loops=True, multiple=True): + """Returns the successors of a given vertex. + + Equivalent to calling the L{Graph.neighbors()} method with mode=C{\"out\"}. + """ + return self.neighbors(vertex, mode="out", loops=loops, multiple=multiple) + ############################################# # Vertex and edge sequence @property @@ -615,6 +633,7 @@ def es(self): disjoint_union = _operator_method_registry["disjoint_union"] union = _operator_method_registry["union"] intersection = _operator_method_registry["intersection"] + rewire = _rewire ############################################# # Adjacency/incidence @@ -658,7 +677,9 @@ def es(self): community_multilevel = _community_multilevel community_optimal_modularity = _community_optimal_modularity community_edge_betweenness = _community_edge_betweenness + community_fluid_communities = _community_fluid_communities community_spinglass = _community_spinglass + community_voronoi = _community_voronoi community_walktrap = _community_walktrap k_core = _k_core community_leiden = _community_leiden @@ -687,7 +708,7 @@ def es(self): ########################### # Paths/traversals - def get_all_simple_paths(self, v, to=None, cutoff=-1, mode="out"): + def get_all_simple_paths(self, v, to=None, minlen=0, maxlen=-1, mode="out", max_results=None): """Calculates all the simple paths from a given node to some other nodes (or all of them) in a graph. @@ -703,23 +724,19 @@ def get_all_simple_paths(self, v, to=None, cutoff=-1, mode="out"): paths. This can be a single vertex ID, a list of vertex IDs, a single vertex name, a list of vertex names or a L{VertexSeq} object. C{None} means all the vertices. - @param cutoff: maximum length of path that is considered. If negative, + @param minlen: minimum length of path that is considered. + @param maxlen: maximum length of path that is considered. If negative, paths of all lengths are considered. @param mode: the directionality of the paths. C{\"in\"} means to calculate incoming paths, C{\"out\"} means to calculate outgoing paths, C{\"all\"} means to calculate both ones. + @param max_results: the maximum number of results to return. C{None} means + no limit on the number of results. @return: all of the simple paths from the given node to every other reachable node in the graph in a list. Note that in case of mode=C{\"in\"}, the vertices in a path are returned in reversed order! """ - paths = self._get_all_simple_paths(v, to, cutoff, mode) - prev = 0 - result = [] - for index, item in enumerate(paths): - if item < 0: - result.append(paths[prev:index]) - prev = index + 1 - return result + return self._get_all_simple_paths(v, to, minlen, maxlen, mode, max_results) def path_length_hist(self, directed=True): """Returns the path length histogram of the graph @@ -782,7 +799,7 @@ def dfs(self, vid, mode=OUT): return (vids, parents) - def spanning_tree(self, weights=None, return_tree=True): + def spanning_tree(self, weights=None, return_tree=True, method="auto"): """Calculates a minimum spanning tree for a graph. B{Reference}: Prim, R.C. Shortest connection networks and some @@ -795,11 +812,16 @@ def spanning_tree(self, weights=None, return_tree=True): the minimum spanning tree instead (when C{return_tree} is C{False}). The default is C{True} for historical reasons as this argument was introduced in igraph 0.6. + @param method: the algorithm to use. C{"auto"} means that the algorithm + is selected automatically. C{"prim"} means that Prim's algorithm is + used. C{"kruskal"} means that Kruskal's algorithm is used. + C{"unweighted"} assumes that the graph is unweighted even if weights + are provided. @return: the spanning tree as a L{Graph} object if C{return_tree} is C{True} or the IDs of the edges that constitute the spanning tree if C{return_tree} is C{False}. """ - result = GraphBase._spanning_tree(self, weights) + result = GraphBase._spanning_tree(self, weights, method) if return_tree: return self.subgraph_edges(result, delete_vertices=False) return result @@ -860,7 +882,7 @@ def transitivity_avglocal_undirected(self, mode="nan", weights=None): networks. I{Nature} 393(6884):440-442, 1998. - Barrat A, Barthelemy M, Pastor-Satorras R and Vespignani A: The architecture of complex weighted networks. I{PNAS} 101, 3747 - (2004). U{http://arxiv.org/abs/cond-mat/0311416}. + (2004). U{https://arxiv.org/abs/cond-mat/0311416}. @param mode: defines how to treat vertices with degree less than two. If C{TRANSITIVITY_ZERO} or C{"zero"}, these vertices will have zero @@ -951,8 +973,7 @@ def Incidence(cls, *args, **kwds): def are_connected(self, *args, **kwds): """Deprecated alias to L{Graph.are_adjacent()}.""" deprecated( - "Graph.are_connected() is deprecated; use Graph.are_adjacent() " - "instead" + "Graph.are_connected() is deprecated; use Graph.are_adjacent() " "instead" ) return self.are_adjacent(*args, **kwds) @@ -1101,7 +1122,9 @@ def write(graph, filename, *args, **kwds): _community_multilevel, _community_optimal_modularity, _community_edge_betweenness, + _community_fluid_communities, _community_spinglass, + _community_voronoi, _community_walktrap, _k_core, _community_leiden, @@ -1133,6 +1156,7 @@ def write(graph, filename, *args, **kwds): _cohesive_blocks, _connected_components, _add_proxy_methods, + _rewire, ) # Re-export from _igraph for API docs @@ -1248,8 +1272,6 @@ def write(graph, filename, *args, **kwds): "GET_ADJACENCY_UPPER", "IN", "OUT", - "REWIRING_SIMPLE", - "REWIRING_SIMPLE_LOOPS", "STAR_IN", "STAR_MUTUAL", "STAR_OUT", diff --git a/src/igraph/adjacency.py b/src/igraph/adjacency.py index b38889c81..3d42b0bb2 100644 --- a/src/igraph/adjacency.py +++ b/src/igraph/adjacency.py @@ -113,26 +113,35 @@ def _get_adjacency_sparse(self, attribute=None): weights = self.es[attribute] N = self.vcount() - mtx = sparse.csr_matrix((weights, list(zip(*edges))), shape=(N, N)) + r, c = zip(*edges) if edges else ([], []) + mtx = sparse.csr_matrix((weights, (r, c)), shape=(N, N)) if not self.is_directed(): mtx = mtx + sparse.triu(mtx, 1).T + sparse.tril(mtx, -1).T return mtx -def _get_adjlist(self, mode="out"): +def _get_adjlist(self, mode="out", loops="twice", multiple=True): """Returns the adjacency list representation of the graph. The adjacency list representation is a list of lists. Each item of the outer list belongs to a single vertex of the graph. The inner list contains the neighbors of the given vertex. - @param mode: if C{\"out\"}, returns the successors of the vertex. If - C{\"in\"}, returns the predecessors of the vertex. If C{\"all\"}, both + @param mode: if C{"out"}, returns the successors of the vertex. If + C{"in"}, returns the predecessors of the vertex. If C{"all"}, both the predecessors and the successors will be returned. Ignored for undirected graphs. + @param loops: whether to return loops in I{undirected} graphs once + (C{"once"}), twice (C{"twice"}) or not at all (C{"ignore"}). C{False} + is accepted as an alias to C{"ignore"} and C{True} is accepted as an + alias to C{"twice"}. For directed graphs, C{"twice"} is equivalent + to C{"once"} (except when C{mode} is C{"all"} because the graph is + then treated as undirected). + @param multiple: whether to return endpoints of multiple edges as many + times as their multiplicities. """ - return [self.neighbors(idx, mode) for idx in range(self.vcount())] + return [self.neighbors(idx, mode, loops, multiple) for idx in range(self.vcount())] def _get_biadjacency(graph, types="type", *args, **kwds): @@ -153,7 +162,7 @@ def _get_biadjacency(graph, types="type", *args, **kwds): return super(Graph, graph).get_biadjacency(types, *args, **kwds) -def _get_inclist(graph, mode="out"): +def _get_inclist(graph, mode="out", loops="twice"): """Returns the incidence list representation of the graph. The incidence list representation is a list of lists. Each @@ -161,9 +170,15 @@ def _get_inclist(graph, mode="out"): The inner list contains the IDs of the incident edges of the given vertex. - @param mode: if C{\"out\"}, returns the successors of the vertex. If - C{\"in\"}, returns the predecessors of the vertex. If C{\"all\"}, both + @param mode: if C{"out"}, returns the successors of each vertex. If + C{"in"}, returns the predecessors of each vertex. If C{"all"}, both the predecessors and the successors will be returned. Ignored for undirected graphs. + @param loops: whether to return loops in I{undirected} graphs once + (C{"once"}), twice (C{"twice"}) or not at all (C{"ignore"}). C{False} + is accepted as an alias to C{"ignore"} and C{True} is accepted as an + alias to C{"twice"}. For directed graphs, C{"twice"} is equivalent + to C{"once"} (except when C{mode} is C{"all"} because the graph is + then treated as undirected). """ - return [graph.incident(idx, mode) for idx in range(graph.vcount())] + return [graph.incident(idx, mode, loops) for idx in range(graph.vcount())] diff --git a/src/igraph/app/shell.py b/src/igraph/app/shell.py index 6a22eb097..d8318bcd0 100644 --- a/src/igraph/app/shell.py +++ b/src/igraph/app/shell.py @@ -19,7 +19,6 @@ Mac OS X users are likely to invoke igraph from the command line. """ - from abc import ABCMeta, abstractmethod import re import sys diff --git a/src/igraph/clustering.py b/src/igraph/clustering.py index 89b34b56e..36df961ad 100644 --- a/src/igraph/clustering.py +++ b/src/igraph/clustering.py @@ -5,7 +5,7 @@ from copy import deepcopy from io import StringIO -from igraph._igraph import GraphBase, community_to_membership +from igraph._igraph import GraphBase, community_to_membership, _compare_communities from igraph.configuration import Configuration from igraph.datatypes import UniqueIdGenerator from igraph.drawing.colors import ClusterColoringPalette @@ -1500,10 +1500,8 @@ def compare_communities(comm1, comm2, method="vi", remove_none=False): @return: the calculated measure. """ - import igraph._igraph - vec1, vec2 = _prepare_community_comparison(comm1, comm2, remove_none) - return igraph._igraph._compare_communities(vec1, vec2, method) + return _compare_communities(vec1, vec2, method) def split_join_distance(comm1, comm2, remove_none=False): diff --git a/src/igraph/community.py b/src/igraph/community.py index e2dcb47da..a3c6ce50b 100644 --- a/src/igraph/community.py +++ b/src/igraph/community.py @@ -37,12 +37,12 @@ def _community_infomap(graph, edge_weights=None, vertex_weights=None, trials=10) - M. Rosvall and C. T. Bergstrom: Maps of information flow reveal community structure in complex networks, I{PNAS} 105, 1118 (2008). - U{http://dx.doi.org/10.1073/pnas.0706851105}, - U{http://arxiv.org/abs/0707.0609}. + U{https://doi.org/10.1073/pnas.0706851105}, + U{https://arxiv.org/abs/0707.0609}. - M. Rosvall, D. Axelsson, and C. T. Bergstrom: The map equation, I{Eur Phys. J Special Topics} 178, 13 (2009). - U{http://dx.doi.org/10.1140/epjst/e2010-01179-1}, - U{http://arxiv.org/abs/0906.1405}. + U{https://doi.org/10.1140/epjst/e2010-01179-1}, + U{https://arxiv.org/abs/0906.1405}. @param edge_weights: name of an edge attribute or a list containing edge weights. @@ -125,7 +125,7 @@ def _community_label_propagation(graph, weights=None, initial=None, fixed=None): B{Reference}: Raghavan, U.N. and Albert, R. and Kumara, S. Near linear time algorithm to detect community structures in large-scale networks. - I{Phys Rev} E 76:036106, 2007. U{http://arxiv.org/abs/0709.2938}. + I{Phys Rev} E 76:036106, 2007. U{https://arxiv.org/abs/0709.2938}. @param weights: name of an edge attribute or a list containing edge weights @@ -165,7 +165,7 @@ def _community_multilevel(graph, weights=None, return_levels=False, resolution=1 B{Reference}: VD Blondel, J-L Guillaume, R Lambiotte and E Lefebvre: Fast unfolding of community hierarchies in large networks, I{J Stat Mech} - P10008 (2008). U{http://arxiv.org/abs/0803.0476} + P10008 (2008). U{https://arxiv.org/abs/0803.0476} @param weights: edge attribute name or a list containing edge weights @@ -235,6 +235,13 @@ def _community_edge_betweenness(graph, clusters=None, directed=True, weights=Non separate components. The result of the clustering will be represented by a dendrogram. + When edge weights are given, the ratio of betweenness and weight values + is used to choose which edges to remove first, as described in + M. E. J. Newman: Analysis of Weighted Networks (2004), Section C. + Thus, edges with large weights are treated as strong connections, + and will be removed later than weak connections having similar betweenness. + Weights are also used for calculating modularity. + @param clusters: the number of clusters we would like to see. This practically defines the "level" where we "cut" the dendrogram to get the membership vector of the vertices. If C{None}, the dendrogram @@ -245,7 +252,7 @@ def _community_edge_betweenness(graph, clusters=None, directed=True, weights=Non @param directed: whether the directionality of the edges should be taken into account or not. @param weights: name of an edge attribute or a list containing - edge weights. + edge weights. Higher weights indicate stronger connections. @return: a L{VertexDendrogram} object, initally cut at the maximum modularity or at the desired number of clusters. """ @@ -271,11 +278,11 @@ def _community_spinglass(graph, *args, **kwds): - Reichardt J and Bornholdt S: Statistical mechanics of community detection. I{Phys Rev E} 74:016110 (2006). - U{http://arxiv.org/abs/cond-mat/0603718}. + U{https://arxiv.org/abs/cond-mat/0603718}. - Traag VA and Bruggeman J: Community detection in networks with positive and negative links. I{Phys Rev E} 80:036115 (2009). - U{http://arxiv.org/abs/0811.2329}. + U{https://arxiv.org/abs/0811.2329}. @keyword weights: edge weights to be used. Can be a sequence or iterable or even an edge attribute name. @@ -320,6 +327,68 @@ def _community_spinglass(graph, *args, **kwds): return VertexClustering(graph, membership, modularity_params=modularity_params) +def _community_voronoi(graph, lengths=None, weights=None, mode="out", radius=None): + """Finds communities using Voronoi partitioning. + + This function finds communities using a Voronoi partitioning of vertices based + on the given edge lengths divided by the edge clustering coefficient. + The generator vertices are chosen to be those with the largest local relative + density within a radius, with the local relative density of a vertex defined + as C{s * m / (m + k)}, where C{s} is the strength of the vertex, C{m} is + the number of edges within the vertex's first order neighborhood, while C{k} + is the number of edges with only one endpoint within this neighborhood. + + B{References} + + - Deritei et al., Community detection by graph Voronoi diagrams, + I{New Journal of Physics} 16, 063007 (2014). + U{https://doi.org/10.1088/1367-2630/16/6/063007}. + - Molnár et al., Community Detection in Directed Weighted Networks using + Voronoi Partitioning, I{Scientific Reports} 14, 8124 (2024). + U{https://doi.org/10.1038/s41598-024-58624-4}. + + @param lengths: edge lengths, or C{None} to consider all edges as having + unit length. Voronoi partitioning will use edge lengths equal to + lengths / ECC where ECC is the edge clustering coefficient. + @param weights: edge weights, or C{None} to consider all edges as having + unit weight. Weights are used when selecting generator points, as well + as for computing modularity. + @param mode: specifies how to use the direction of edges when computing + distances from generator points. If C{"out"} (the default), distances + from generator points to all other nodes are considered following the + direction of edges. If C{"in"}, distances are computed in the reverse + direction (i.e., from all nodes to generator points). If C{"all"}, + edge directions are ignored and the graph is treated as undirected. + This parameter is ignored for undirected graphs. + @param radius: the radius/resolution to use when selecting generator points. + The larger this value, the fewer partitions there will be. Pass C{None} + to automatically select the radius that maximizes modularity. + @return: an appropriate L{VertexClustering} object with an extra attribute + called C{generators} (the generator vertices). + """ + # Convert mode string to proper enum value to avoid deprecation warning + if isinstance(mode, str): + mode_map = {"out": "out", "in": "in", "all": "all", "total": "all"} # alias + if mode.lower() in mode_map: + mode = mode_map[mode.lower()] + else: + raise ValueError(f"Invalid mode '{mode}'. Must be one of: out, in, all") + + membership, generators, modularity = GraphBase.community_voronoi(graph, lengths, weights, mode, radius) + + params = {"generators": generators} + modularity_params = {} + if weights is not None: + modularity_params["weights"] = weights + + clustering = VertexClustering( + graph, membership, modularity=modularity, params=params, modularity_params=modularity_params + ) + + clustering.generators = generators + return clustering + + def _community_walktrap(graph, weights=None, steps=4): """Community detection algorithm of Latapy & Pons, based on random walks. @@ -329,7 +398,7 @@ def _community_walktrap(graph, weights=None, steps=4): as a dendrogram. B{Reference}: Pascal Pons, Matthieu Latapy: Computing communities in large - networks using random walks, U{http://arxiv.org/abs/physics/0512106}. + networks using random walks, U{https://arxiv.org/abs/physics/0512106}. @param weights: name of an edge attribute or a list containing edge weights @@ -392,7 +461,8 @@ def _community_leiden( initial_membership=None, n_iterations=2, node_weights=None, - **kwds + node_in_weights=None, + **kwds, ): """Finds the community structure of the graph using the Leiden algorithm of Traag, van Eck & Waltman. @@ -422,6 +492,11 @@ def _community_leiden( If this is not provided, it will be automatically determined on the basis of whether you want to use CPM or modularity. If you do provide this, please make sure that you understand what you are doing. + @param node_in_weights: the inbound node weights used in the directed + variant of the Leiden algorithm. If this is not provided, it will be + automatically determined on the basis of whether you want to use CPM or + modularity. If you do provide this, please make sure that you understand + what you are doing. @return: an appropriate L{VertexClustering} object with an extra attribute called C{quality} that stores the value of the internal quality function optimized by the algorithm. @@ -443,6 +518,7 @@ def _community_leiden( graph, edge_weights=weights, node_weights=node_weights, + node_in_weights=node_in_weights, resolution=resolution, normalize_resolution=(objective_function == "modularity"), beta=beta, @@ -461,6 +537,47 @@ def _community_leiden( ) +def _community_fluid_communities(graph, no_of_communities): + """Community detection based on fluids interacting on the graph. + + The algorithm is based on the simple idea of several fluids interacting + in a non-homogeneous environment (the graph topology), expanding and + contracting based on their interaction and density. Weighted graphs are + not supported. + + This function implements the community detection method described in: + Parés F, Gasulla DG, et. al. (2018) Fluid Communities: A Competitive, + Scalable and Diverse Community Detection Algorithm. + + @param no_of_communities: The number of communities to be found. Must be + greater than 0 and fewer than or equal to the number of vertices in the graph. + @return: an appropriate L{VertexClustering} object. + """ + # Validate input parameters + if no_of_communities <= 0: + raise ValueError("no_of_communities must be greater than 0") + + if no_of_communities > graph.vcount(): + raise ValueError("no_of_communities must be fewer than or equal to the number of vertices") + + # Check if graph is weighted (not supported) + if graph.is_weighted(): + raise ValueError("Weighted graphs are not supported by the fluid communities algorithm") + + # Handle directed graphs - the algorithm works on undirected graphs + # but can accept directed graphs (they are treated as undirected) + if graph.is_directed(): + import warnings + warnings.warn( + "Directed graphs are treated as undirected in the fluid communities algorithm", + UserWarning, + stacklevel=2 + ) + + membership = GraphBase.community_fluid_communities(graph, no_of_communities) + return VertexClustering(graph, membership) + + def _modularity(self, membership, weights=None, resolution=1, directed=True): """Calculates the modularity score of the graph with respect to a given clustering. diff --git a/src/igraph/configuration.py b/src/igraph/configuration.py index d0202f541..9accb5325 100644 --- a/src/igraph/configuration.py +++ b/src/igraph/configuration.py @@ -326,7 +326,7 @@ def load(self, stream=None): if file_was_open: stream.close() - def save(self, stream: Optional[Union[str, IO[str]]]=None): + def save(self, stream: Optional[Union[str, IO[str]]] = None): """Saves the configuration. @param stream: name of a file or a file-like object. The configuration diff --git a/src/igraph/drawing/__init__.py b/src/igraph/drawing/__init__.py index e6d06b4bc..05743728a 100644 --- a/src/igraph/drawing/__init__.py +++ b/src/igraph/drawing/__init__.py @@ -5,9 +5,9 @@ It also has experimental support for plotly. The Cairo backend is dependent on the C{pycairo} or C{cairocffi} libraries that -provide Python bindings to the popular U{Cairo library}. -This means that if you don't have U{pycairo} -or U{cairocffi} installed, you won't be able +provide Python bindings to the popular U{Cairo library}. +This means that if you don't have U{pycairo} +or U{cairocffi} installed, you won't be able to use the Cairo plotting backend. Whenever the documentation refers to the C{pycairo} library, you can safely replace it with C{cairocffi} as the two are API-compatible. @@ -21,12 +21,11 @@ If you do not want to (or cannot) install any of the dependencies outlined above, you can still save the graph to an SVG file and view it from -U{Mozilla Firefox} (free) or edit it in -U{Inkscape} (free), U{Skencil} +U{Mozilla Firefox} (free) or edit it in +U{Inkscape} (free), U{Skencil} (formerly known as Sketch, also free) or Adobe Illustrator. """ - from pathlib import Path from warnings import warn diff --git a/src/igraph/drawing/cairo/edge.py b/src/igraph/drawing/cairo/edge.py index 1b0c46731..7535004c9 100644 --- a/src/igraph/drawing/cairo/edge.py +++ b/src/igraph/drawing/cairo/edge.py @@ -168,14 +168,16 @@ def draw_directed_edge(self, edge, src_vertex, dest_vertex): ] # Midpoint of the base of the arrow triangle - x_arrow_mid, y_arrow_mid = (aux_points[0][0] + aux_points[1][0]) / 2.0, ( - aux_points[0][1] + aux_points[1][1] - ) / 2.0 + x_arrow_mid, y_arrow_mid = ( + (aux_points[0][0] + aux_points[1][0]) / 2.0, + (aux_points[0][1] + aux_points[1][1]) / 2.0, + ) # Vector representing the base of the arrow triangle x_arrow_base_vec, y_arrow_base_vec = ( - aux_points[0][0] - aux_points[1][0] - ), (aux_points[0][1] - aux_points[1][1]) + (aux_points[0][0] - aux_points[1][0]), + (aux_points[0][1] - aux_points[1][1]), + ) # Recalculate the curve such that it lands on the base of the arrow triangle aux1, aux2 = get_bezier_control_points_for_curved_edge( @@ -224,9 +226,10 @@ def draw_directed_edge(self, edge, src_vertex, dest_vertex): ] # Midpoint of the base of the arrow triangle - x_arrow_mid, y_arrow_mid = (aux_points[0][0] + aux_points[1][0]) / 2.0, ( - aux_points[0][1] + aux_points[1][1] - ) / 2.0 + x_arrow_mid, y_arrow_mid = ( + (aux_points[0][0] + aux_points[1][0]) / 2.0, + (aux_points[0][1] + aux_points[1][1]) / 2.0, + ) # Draw the line ctx.line_to(x_arrow_mid, y_arrow_mid) diff --git a/src/igraph/drawing/cairo/plot.py b/src/igraph/drawing/cairo/plot.py index ac98210db..5ac247b8f 100644 --- a/src/igraph/drawing/cairo/plot.py +++ b/src/igraph/drawing/cairo/plot.py @@ -6,7 +6,7 @@ The Cairo backend is dependent on the C{pycairo} or C{cairocffi} libraries that provide Python bindings to the popular U{Cairo library}. This means that if you don't have U{pycairo} -or U{cairocffi} installed, you won't be able +or U{cairocffi} installed, you won't be able to use the Cairo plotting backend. Whenever the documentation refers to the C{pycairo} library, you can safely replace it with C{cairocffi} as the two are API-compatible. @@ -17,12 +17,11 @@ If you do not want to (or cannot) install any of the dependencies outlined above, you can still save the graph to an SVG file and view it from -U{Mozilla Firefox} (free) or edit it in -U{Inkscape} (free), U{Skencil} +U{Mozilla Firefox} (free) or edit it in +U{Inkscape} (free), U{Skencil} (formerly known as Sketch, also free) or Adobe Illustrator. """ - import os from io import BytesIO @@ -286,7 +285,7 @@ def redraw(self, context=None): bbox=bbox, palette=palette, *args, # noqa: B026 - **kwds + **kwds, ) if opacity < 1.0: ctx.pop_group_to_source() diff --git a/src/igraph/drawing/graph.py b/src/igraph/drawing/graph.py index ec326c48d..00f77f402 100644 --- a/src/igraph/drawing/graph.py +++ b/src/igraph/drawing/graph.py @@ -7,8 +7,8 @@ - Matplotlib axes (L{MatplotlibGraphDrawer}) It also contains routines to send an igraph graph directly to -(U{Cytoscape}) using the -(U{CytoscapeRPC plugin}), see +(U{Cytoscape}) using the +(U{CytoscapeRPC plugin}), see L{CytoscapeGraphDrawer}. L{CytoscapeGraphDrawer} can also fetch the current network from Cytoscape and convert it to igraph format. """ @@ -24,8 +24,8 @@ class CytoscapeGraphDrawer(AbstractXMLRPCDrawer, AbstractGraphDrawer): """Graph drawer that sends/receives graphs to/from Cytoscape using CytoscapeRPC. - This graph drawer cooperates with U{Cytoscape} - using U{CytoscapeRPC}. + This graph drawer cooperates with U{Cytoscape} + using U{CytoscapeRPC}. You need to install the CytoscapeRPC plugin first and start the XML-RPC server on a given port (port 9000 by default) from the appropriate Plugins submenu in Cytoscape. diff --git a/src/igraph/drawing/plotly/edge.py b/src/igraph/drawing/plotly/edge.py index 15ea40a29..d9ee1e2cb 100644 --- a/src/igraph/drawing/plotly/edge.py +++ b/src/igraph/drawing/plotly/edge.py @@ -103,14 +103,16 @@ def draw_directed_edge(self, edge, src_vertex, dest_vertex): ] # Midpoint of the base of the arrow triangle - x_arrow_mid, y_arrow_mid = (aux_points[0][0] + aux_points[1][0]) / 2.0, ( - aux_points[0][1] + aux_points[1][1] - ) / 2.0 + x_arrow_mid, y_arrow_mid = ( + (aux_points[0][0] + aux_points[1][0]) / 2.0, + (aux_points[0][1] + aux_points[1][1]) / 2.0, + ) # Vector representing the base of the arrow triangle x_arrow_base_vec, y_arrow_base_vec = ( - aux_points[0][0] - aux_points[1][0] - ), (aux_points[0][1] - aux_points[1][1]) + (aux_points[0][0] - aux_points[1][0]), + (aux_points[0][1] - aux_points[1][1]), + ) # Recalculate the curve such that it lands on the base of the arrow triangle aux1, aux2 = get_bezier_control_points_for_curved_edge( @@ -161,9 +163,10 @@ def draw_directed_edge(self, edge, src_vertex, dest_vertex): ] # Midpoint of the base of the arrow triangle - x_arrow_mid, y_arrow_mid = (aux_points[0][0] + aux_points[1][0]) / 2.0, ( - aux_points[0][1] + aux_points[1][1] - ) / 2.0 + x_arrow_mid, y_arrow_mid = ( + (aux_points[0][0] + aux_points[1][0]) / 2.0, + (aux_points[0][1] + aux_points[1][1]) / 2.0, + ) # Draw the line path.append( format_path_step( diff --git a/src/igraph/drawing/shapes.py b/src/igraph/drawing/shapes.py index 4f0ea97b5..0c3ccd1c8 100644 --- a/src/igraph/drawing/shapes.py +++ b/src/igraph/drawing/shapes.py @@ -16,7 +16,6 @@ name in the C{shape} attribute of vertices. """ - __all__ = ("ShapeDrawerDirectory",) from abc import ABCMeta, abstractmethod diff --git a/src/igraph/io/bipartite.py b/src/igraph/io/bipartite.py index 70fac3750..1268dc0bf 100644 --- a/src/igraph/io/bipartite.py +++ b/src/igraph/io/bipartite.py @@ -6,7 +6,7 @@ def _construct_bipartite_graph_from_adjacency( multiple=False, weighted=None, *args, - **kwds + **kwds, ): """Creates a bipartite graph from a bipartite adjacency matrix. @@ -117,7 +117,8 @@ def _construct_full_bipartite_graph( def _construct_random_bipartite_graph( - cls, n1, n2, p=None, m=None, directed=False, neimode="all", *args, **kwds + cls, n1, n2, p=None, m=None, directed=False, neimode="all", + allowed_edge_types="simple", edge_labeled=False, *args, **kwds ): """Generates a random bipartite graph with the given number of vertices and edges (if m is given), or with the given number of vertices and the given @@ -140,13 +141,23 @@ def _construct_random_bipartite_graph( edges will always point from type 1 to type 2. If it is C{"in"}, edges will always point from type 2 to type 1. This argument is ignored for undirected graphs. + @param allowed_edge_types: controls whether multi-edges are allowed + during the generation process. Possible values are: + + - C{"simple"}: simple graphs (no self-loops) + - C{"multi"}: multi-edges allowed + + @param edge_labeled: whether to sample uniformly from the set of + I{ordered} edge lists. Use C{False} to recover the classic random + bipartite model. """ if p is None: p = -1 if m is None: m = -1 result, types = cls._Random_Bipartite( - n1, n2, p, m, directed, neimode, *args, **kwds + n1, n2, p, m, directed, neimode, allowed_edge_types, edge_labeled, + *args, **kwds ) result.vs["type"] = types return result diff --git a/src/igraph/io/images.py b/src/igraph/io/images.py index 052be0500..da1c8b2c9 100644 --- a/src/igraph/io/images.py +++ b/src/igraph/io/images.py @@ -17,7 +17,7 @@ def _write_graph_to_svg( edge_stroke_widths="width", font_size=16, *args, - **kwds + **kwds, ): """Saves the graph as an SVG (Scalable Vector Graphics) file @@ -149,7 +149,7 @@ def _write_graph_to_svg( print('', file=f) print( - "", + "", file=f, ) print(file=f) @@ -289,16 +289,12 @@ def _write_graph_to_svg( vs = str(vertex_size) print( ' '.format( - vs, c[0] - ), + -{0},0" fill="{1}"/>'.format(vs, c[0]), file=f, ) print( ' '.format( - vs, c[1] - ), + -{0},0" fill="{1}"/>'.format(vs, c[1]), file=f, ) print( diff --git a/src/igraph/layout.py b/src/igraph/layout.py index f5b0a9181..ad5fffc09 100644 --- a/src/igraph/layout.py +++ b/src/igraph/layout.py @@ -6,7 +6,6 @@ This package contains the implementation of the L{Layout} object. """ - from math import sin, cos, pi from igraph._igraph import GraphBase @@ -16,6 +15,7 @@ __all__ = ( "Layout", + "align_layout", "_layout", "_layout_auto", "_layout_sugiyama", @@ -56,6 +56,14 @@ class Layout: >>> layout[1] = coords >>> print(layout[1]) [0, 3] + + Optionally, a layout may have I{edge routing} information attached to + each edge in the layout in the L{edge_routing} property. + + @ivar edge_routing: C{None} if no edge routing information is available, + or a list of lists of control point coordinates, one for each edge. When + an edge has control points, it should be drawn in a way that the edge + passes through all the control points in the order they appear in the list. """ def __init__(self, coords=None, dim=None): @@ -69,6 +77,8 @@ def __init__(self, coords=None, dim=None): length of the coordinate list is zero, otherwise it should be left as is. """ + self.edge_routing = None + if coords is not None: self._coords = [list(coord) for coord in coords] else: @@ -534,6 +544,28 @@ def _layout(graph, layout=None, *args, **kwds): return layout +def align_layout(graph, layout): + """Aligns a graph layout with the coordinate axes + + This function centers a vertex layout on the coordinate system origin and + rotates the layout to achieve a visually pleasing alignment with the coordinate + axes. Doing this is particularly useful with force-directed layouts such as + L{Graph.layout_fruchterman_reingold}. Layouts in arbitrary dimensional spaces + are supported. + + @param graph: the graph that the layout is associated with. + @param layout: the L{Layout} object containing the vertex coordinates + to align. + @return: a new aligned L{Layout} object. + """ + from igraph._igraph import _align_layout + + if not isinstance(layout, Layout): + layout = Layout(layout) + + return Layout(_align_layout(graph, layout.coords)) + + def _layout_auto(graph, *args, **kwds): """Chooses and runs a suitable layout function based on simple topological properties of the graph. @@ -609,7 +641,6 @@ def _layout_sugiyama( hgap=1, vgap=1, maxiter=100, - return_extended_graph=False, ): """Places the vertices using a layered Sugiyama layout. @@ -650,33 +681,16 @@ def _layout_sugiyama( @param maxiter: maximum number of iterations to take in the crossing reduction step. Increase this if you feel that you are getting too many edge crossings. - @param return_extended_graph: specifies that the extended graph with the - added dummy vertices should also be returned. When this is C{True}, the - result will be a tuple containing the layout and the extended graph. The - first |V| nodes of the extended graph will correspond to the nodes of the - original graph, the remaining ones are dummy nodes. Plotting the extended - graph with the returned layout and hidden dummy nodes will produce a layout - that is similar to the original graph, but with the added edge bends. - The extended graph also contains an edge attribute called C{_original_eid} - which specifies the ID of the edge in the original graph from which the - edge of the extended graph was created. - @return: the calculated layout, which may (and usually will) have more rows - than the number of vertices; the remaining rows correspond to the dummy - nodes introduced in the layering step. When C{return_extended_graph} is - C{True}, it will also contain the extended graph. + @return: the calculated layout and an additional list of matrices where the + i-th matrix contains the control points of edge I{i} in the original graph + (or an empty matrix if no control points are needed on the edge) """ - if not return_extended_graph: - return Layout( - GraphBase._layout_sugiyama( - graph, layers, weights, hgap, vgap, maxiter, return_extended_graph - ) - ) - - layout, extd_graph, extd_to_orig_eids = GraphBase._layout_sugiyama( - graph, layers, weights, hgap, vgap, maxiter, return_extended_graph + coords, routing = GraphBase._layout_sugiyama( + graph, layers, weights, hgap, vgap, maxiter ) - extd_graph.es["_original_eid"] = extd_to_orig_eids - return Layout(layout), extd_graph + layout = Layout(coords) + layout.edge_routing = routing + return layout def _layout_method_wrapper(func): diff --git a/src/igraph/operators/functions.py b/src/igraph/operators/functions.py index b305f5cb8..4ba3ede43 100644 --- a/src/igraph/operators/functions.py +++ b/src/igraph/operators/functions.py @@ -158,7 +158,7 @@ def union(graphs, byname="auto"): raise RuntimeError( f"Some graphs are not named (got {n_named} named, {ngr-n_named} unnamed)" ) - # Now we know that byname is only used is all graphs are named + # Now we know that byname is only used if all graphs are named # Trivial cases if ngr == 0: @@ -184,7 +184,12 @@ def union(graphs, byname="auto"): # Reorder vertices to match uninames # vertex k -> p[k] permutation = [permutation_map[x] for x in ng.vs["name"]] - ng = ng.permute_vertices(permutation) + + # permute_vertices() needs the inverse permutation + inv_permutation = [0] * len(permutation) + for i, x in enumerate(permutation): + inv_permutation[x] = i + ng = ng.permute_vertices(inv_permutation) newgraphs.append(ng) else: @@ -353,7 +358,7 @@ def intersection(graphs, byname="auto", keep_all_vertices=True): raise RuntimeError( f"Some graphs are not named (got {n_named} named, {ngr-n_named} unnamed)" ) - # Now we know that byname is only used is all graphs are named + # Now we know that byname is only used if all graphs are named # Trivial cases if ngr == 0: @@ -389,7 +394,12 @@ def intersection(graphs, byname="auto", keep_all_vertices=True): # Reorder vertices to match uninames # vertex k -> p[k] permutation = [permutation_map[x] for x in ng.vs["name"]] - ng = ng.permute_vertices(permutation) + + # permute_vertices() needs the inverse permutation + inv_permutation = [0] * len(permutation) + for i, x in enumerate(permutation): + inv_permutation[x] = i + ng = ng.permute_vertices(inv_permutation) newgraphs.append(ng) else: diff --git a/src/igraph/rewiring.py b/src/igraph/rewiring.py new file mode 100644 index 000000000..b4881a6c8 --- /dev/null +++ b/src/igraph/rewiring.py @@ -0,0 +1,25 @@ +from igraph._igraph import GraphBase + +from .utils import deprecated + +__all__ = ("_rewire", ) + + +def _rewire(graph, n=None, allowed_edge_types="simple", *, mode=None): + """Randomly rewires the graph while preserving the degree distribution. + + The rewiring is done \"in-place\", so the original graph will be modified. + If you want to preserve the original graph, use the L{copy} method before + rewiring. + + @param n: the number of rewiring trials. The default is 10 times the number + of edges. + @param allowed_edge_types: the rewiring algorithm to use. It can either be + C{"simple"} or C{"loops"}; the former does not create or destroy + loop edges while the latter does. + """ + if mode is not None: + deprecated("The 'mode' keyword argument is deprecated, use 'allowed_edge_types' instead") + allowed_edge_types = mode + + return GraphBase._rewire(graph, n, allowed_edge_types) diff --git a/src/igraph/seq.py b/src/igraph/seq.py index 6107cfc9c..3790cf510 100644 --- a/src/igraph/seq.py +++ b/src/igraph/seq.py @@ -737,9 +737,7 @@ def decorated(*args, **kwds): restricted to this sequence, and returns the result. @see: Graph.%(name)s() for details. -""" % { - "name": name - } +""" % {"name": name} return decorated diff --git a/src/igraph/sparse_matrix.py b/src/igraph/sparse_matrix.py index fe9565a79..93c6fa7ae 100644 --- a/src/igraph/sparse_matrix.py +++ b/src/igraph/sparse_matrix.py @@ -24,19 +24,15 @@ def _convert_mode_argument(mode): # resolve mode constants, convert to lowercase - mode = ( - { - ADJ_DIRECTED: "directed", - ADJ_UNDIRECTED: "undirected", - ADJ_MAX: "max", - ADJ_MIN: "min", - ADJ_PLUS: "plus", - ADJ_UPPER: "upper", - ADJ_LOWER: "lower", - } - .get(mode, mode) - .lower() - ) + mode = { + ADJ_DIRECTED: "directed", + ADJ_UNDIRECTED: "undirected", + ADJ_MAX: "max", + ADJ_MIN: "min", + ADJ_PLUS: "plus", + ADJ_UPPER: "upper", + ADJ_LOWER: "lower", + }.get(mode, mode).lower() if mode not in _SUPPORTED_MODES: raise ValueError("mode should be one of " + (" ".join(_SUPPORTED_MODES))) diff --git a/src/igraph/utils.py b/src/igraph/utils.py index 2116332ae..867d68870 100644 --- a/src/igraph/utils.py +++ b/src/igraph/utils.py @@ -75,7 +75,7 @@ def numpy_to_contiguous_memoryview(obj): dtype = int32 else: raise TypeError( - f"size of igraph_integer_t in the C layer ({INTEGER_SIZE} bits) is not supported" + f"size of igraph_int_t in the C layer ({INTEGER_SIZE} bits) is not supported" ) return memoryview(require(obj, dtype=dtype, requirements="AC")) diff --git a/src/igraph/version.py b/src/igraph/version.py index 81a2aa780..b69224ddc 100644 --- a/src/igraph/version.py +++ b/src/igraph/version.py @@ -1,2 +1,2 @@ -__version_info__ = (0, 11, 8) +__version_info__ = (1, 0, 0) __version__ = ".".join("{0}".format(x) for x in __version_info__) diff --git a/tests/drawing/cairo/baseline_images/clustering_directed.png b/tests/drawing/cairo/baseline_images/clustering_directed.png index 712f1b95f..282f3fb21 100644 Binary files a/tests/drawing/cairo/baseline_images/clustering_directed.png and b/tests/drawing/cairo/baseline_images/clustering_directed.png differ diff --git a/tests/drawing/cairo/baseline_images/graph_basic.png b/tests/drawing/cairo/baseline_images/graph_basic.png index 2eee93d86..cfd394e49 100644 Binary files a/tests/drawing/cairo/baseline_images/graph_basic.png and b/tests/drawing/cairo/baseline_images/graph_basic.png differ diff --git a/tests/drawing/cairo/baseline_images/graph_directed.png b/tests/drawing/cairo/baseline_images/graph_directed.png index 83d6e3764..b94e82169 100644 Binary files a/tests/drawing/cairo/baseline_images/graph_directed.png and b/tests/drawing/cairo/baseline_images/graph_directed.png differ diff --git a/tests/drawing/cairo/baseline_images/graph_mark_groups_directed.png b/tests/drawing/cairo/baseline_images/graph_mark_groups_directed.png index 83d6e3764..b94e82169 100644 Binary files a/tests/drawing/cairo/baseline_images/graph_mark_groups_directed.png and b/tests/drawing/cairo/baseline_images/graph_mark_groups_directed.png differ diff --git a/tests/drawing/cairo/baseline_images/graph_mark_groups_squares_directed.png b/tests/drawing/cairo/baseline_images/graph_mark_groups_squares_directed.png index c5372745c..0e437f288 100644 Binary files a/tests/drawing/cairo/baseline_images/graph_mark_groups_squares_directed.png and b/tests/drawing/cairo/baseline_images/graph_mark_groups_squares_directed.png differ diff --git a/tests/drawing/matplotlib/test_graph.py b/tests/drawing/matplotlib/test_graph.py index a044d323e..b77d42174 100644 --- a/tests/drawing/matplotlib/test_graph.py +++ b/tests/drawing/matplotlib/test_graph.py @@ -54,9 +54,11 @@ def test_labels(self): g = Graph.Ring(5) fig, ax = plt.subplots(figsize=(3, 3)) plot( - g, target=ax, layout=self.layout_small_ring, - vertex_label=['1', '2', '3', '4', '5'], - vertex_label_color='white', + g, + target=ax, + layout=self.layout_small_ring, + vertex_label=["1", "2", "3", "4", "5"], + vertex_label_color="white", vertex_label_size=16, ) diff --git a/tests/test_atlas.py b/tests/test_atlas.py index 1f36f5f44..49f49531e 100644 --- a/tests/test_atlas.py +++ b/tests/test_atlas.py @@ -100,7 +100,14 @@ def testEigenvectorCentrality(self): def testHubScore(self): for idx, g in enumerate(self.__class__.graphs): try: - sc = g.hub_score() + if g.ecount() == 0: + with self.assertWarns(RuntimeWarning, msg="The graph has no edges"): + sc = g.hub_score() + elif not g.is_directed(): + with self.assertWarns(RuntimeWarning, msg="Hub and authority scores requested for undirected graph"): + sc = g.hub_score() + else: + sc = g.hub_score() except Exception as ex: self.assertTrue( False, @@ -126,7 +133,14 @@ def testHubScore(self): def testAuthorityScore(self): for idx, g in enumerate(self.__class__.graphs): try: - sc = g.authority_score() + if g.ecount() == 0: + with self.assertWarns(RuntimeWarning, msg="The graph has no edges"): + sc = g.authority_score() + elif not g.is_directed(): + with self.assertWarns(RuntimeWarning, msg="Hub and authority scores requested for undirected graph"): + sc = g.authority_score() + else: + sc = g.authority_score() except Exception as ex: self.assertTrue( False, diff --git a/tests/test_basic.py b/tests/test_basic.py index ddda4f2d2..35d511b1b 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -350,6 +350,20 @@ def testAdjacency(self): self.assertTrue(g.get_adjlist(IN) == [[2], [0], [1], [2]]) self.assertTrue(g.get_adjlist(ALL) == [[1, 2], [0, 2], [0, 1, 3], [2]]) + def testAdjacencyWithLoopsAndMultiEdges(self): + g = Graph(4, [(0, 0), (0, 1), (1, 2), (2, 0), (2, 3), (2, 3), (3, 3), (3, 3)], directed=True) + + self.assertTrue(g.neighbors(2) == [0, 1, 3, 3]) + self.assertTrue(g.predecessors(2) == [1]) + self.assertTrue(g.successors(2) == [0, 3, 3]) + + self.assertTrue(g.get_adjlist() == [[0, 1], [2], [0, 3, 3], [3, 3]]) + self.assertTrue(g.get_adjlist(IN) == [[0, 2], [0], [1], [2, 2, 3, 3]]) + self.assertTrue(g.get_adjlist(ALL) == [[0, 0, 1, 2], [0, 2], [0, 1, 3, 3], [2, 2, 3, 3, 3, 3]]) + + self.assertTrue(g.get_adjlist(ALL, loops="once") == [[0, 1, 2], [0, 2], [0, 1, 3, 3], [2, 2, 3, 3]]) + self.assertTrue(g.get_adjlist(ALL, loops="once", multiple=False) == [[0, 1, 2], [0, 2], [0, 1, 3], [2, 3]]) + def testEdgeIncidence(self): g = Graph(4, [(0, 1), (1, 2), (2, 0), (2, 3)], directed=True) self.assertTrue(g.incident(2) == [2, 3]) @@ -359,6 +373,19 @@ def testEdgeIncidence(self): self.assertTrue(g.get_inclist(IN) == [[2], [0], [1], [3]]) self.assertTrue(g.get_inclist(ALL) == [[0, 2], [0, 1], [2, 1, 3], [3]]) + def testEdgeIncidenceWithLoopsAndMultiEdges(self): + g = Graph(4, [(0, 1), (1, 2), (2, 0), (2, 3), (2, 3), (0, 0), (3, 3), (3, 3)], directed=True) + + self.assertTrue(g.incident(2) == [2, 4, 3]) + self.assertTrue(g.incident(2, IN) == [1]) + self.assertTrue(g.incident(2, ALL) == [2, 1, 4, 3]) + + self.assertTrue(g.get_inclist() == [[5, 0], [1], [2, 4, 3], [7, 6]]) + self.assertTrue(g.get_inclist(IN) == [[5, 2], [0], [1], [4, 3, 7, 6]]) + self.assertTrue(g.get_inclist(ALL) == [[5, 5, 0, 2], [0, 1], [2, 1, 4, 3], [4, 3, 7, 7, 6, 6]]) + + self.assertTrue(g.get_inclist(ALL, loops="once") == [[5, 0, 2], [0, 1], [2, 1, 4, 3], [4, 3, 7, 6]]) + def testMultiplesLoops(self): g = Graph.Tree(7, 2) diff --git a/tests/test_bipartite.py b/tests/test_bipartite.py index f3c5cc8c7..932838655 100644 --- a/tests/test_bipartite.py +++ b/tests/test_bipartite.py @@ -76,8 +76,8 @@ def testBiadjacency(self): ) ) self.assertListEqual(g.vs["type"], [False] * 2 + [True] * 3) - self.assertListEqual(g.es["weight"], [1, 1, 1, 2]) - self.assertListEqual(sorted(g.get_edgelist()), [(0, 3), (0, 4), (1, 2), (1, 3)]) + self.assertListEqual(g.get_edgelist(), [(1, 2), (0, 3), (1, 3), (0, 4)]) + self.assertListEqual(g.es["weight"], [1, 1, 2, 1]) # Graph is not weighted when weighted=`str` g = Graph.Biadjacency([[0, 1, 1], [1, 2, 0]], weighted="some_attr_name") @@ -92,8 +92,8 @@ def testBiadjacency(self): ) ) self.assertListEqual(g.vs["type"], [False] * 2 + [True] * 3) - self.assertListEqual(g.es["some_attr_name"], [1, 1, 1, 2]) - self.assertListEqual(sorted(g.get_edgelist()), [(0, 3), (0, 4), (1, 2), (1, 3)]) + self.assertListEqual(g.get_edgelist(), [(1, 2), (0, 3), (1, 3), (0, 4)]) + self.assertListEqual(g.es["some_attr_name"], [1, 1, 2, 1]) # Graph is not weighted when weighted="" g = Graph.Biadjacency([[0, 1, 1], [1, 2, 0]], weighted="") @@ -108,8 +108,8 @@ def testBiadjacency(self): ) ) self.assertListEqual(g.vs["type"], [False] * 2 + [True] * 3) - self.assertListEqual(g.es[""], [1, 1, 1, 2]) - self.assertListEqual(sorted(g.get_edgelist()), [(0, 3), (0, 4), (1, 2), (1, 3)]) + self.assertListEqual(g.get_edgelist(), [(1, 2), (0, 3), (1, 3), (0, 4)]) + self.assertListEqual(g.es[""], [1, 1, 2, 1]) # Should work when directed=True and mode=out with weighted g = Graph.Biadjacency([[0, 1, 1], [1, 2, 0]], directed=True, weighted=True) @@ -117,8 +117,8 @@ def testBiadjacency(self): all((g.vcount() == 5, g.ecount() == 4, g.is_directed(), g.is_weighted())) ) self.assertListEqual(g.vs["type"], [False] * 2 + [True] * 3) - self.assertListEqual(g.es["weight"], [1, 1, 1, 2]) - self.assertListEqual(sorted(g.get_edgelist()), [(0, 3), (0, 4), (1, 2), (1, 3)]) + self.assertListEqual(g.get_edgelist(), [(1, 2), (0, 3), (1, 3), (0, 4)]) + self.assertListEqual(g.es["weight"], [1, 1, 2, 1]) # Should work when directed=True and mode=in with weighted g = Graph.Biadjacency( @@ -128,8 +128,8 @@ def testBiadjacency(self): all((g.vcount() == 5, g.ecount() == 4, g.is_directed(), g.is_weighted())) ) self.assertListEqual(g.vs["type"], [False] * 2 + [True] * 3) - self.assertListEqual(g.es["weight"], [1, 1, 1, 2]) - self.assertListEqual(sorted(g.get_edgelist()), [(2, 1), (3, 0), (3, 1), (4, 0)]) + self.assertListEqual(g.get_edgelist(), [(2, 1), (3, 0), (3, 1), (4, 0)]) + self.assertListEqual(g.es["weight"], [1, 1, 2, 1]) # Should work when directed=True and mode=all with weighted g = Graph.Biadjacency( @@ -139,11 +139,11 @@ def testBiadjacency(self): all((g.vcount() == 5, g.ecount() == 8, g.is_directed(), g.is_weighted())) ) self.assertListEqual(g.vs["type"], [False] * 2 + [True] * 3) - self.assertListEqual(g.es["weight"], [1, 1, 1, 1, 1, 1, 2, 2]) self.assertListEqual( - sorted(g.get_edgelist()), - [(0, 3), (0, 4), (1, 2), (1, 3), (2, 1), (3, 0), (3, 1), (4, 0)], + g.get_edgelist(), + [(1, 2), (2, 1), (0, 3), (3, 0), (1, 3), (3, 1), (0, 4), (4, 0)] ) + self.assertListEqual(g.es["weight"], [1, 1, 1, 1, 2, 2, 1, 1]) def testBiadjacencyError(self): msg = "arguments weighted and multiple can not co-exist" diff --git a/tests/test_cliques.py b/tests/test_cliques.py index 3c1c215e9..40c0f9ea3 100644 --- a/tests/test_cliques.py +++ b/tests/test_cliques.py @@ -1,5 +1,7 @@ import unittest +from math import inf + from igraph import Graph from .utils import temporary_file @@ -59,9 +61,25 @@ def testCliques(self): [1, 2, 4, 5], ], } + for (lo, hi), exp in tests.items(): self.assertEqual(sorted(exp), sorted(map(sorted, self.g.cliques(lo, hi)))) + for (lo, hi), exp in tests.items(): + self.assertEqual(sorted(exp), sorted(map(sorted, self.g.cliques(lo, hi, max_results=inf)))) + + for (lo, hi), exp in tests.items(): + observed = [sorted(cl) for cl in self.g.cliques(lo, hi, max_results=10)] + for cl in observed: + self.assertTrue(cl in exp) + + for (lo, hi), _ in tests.items(): + self.assertEqual([], self.g.cliques(lo, hi, max_results=0)) + + for (lo, hi), _ in tests.items(): + with self.assertRaises(ValueError): + self.g.cliques(lo, hi, max_results=-2) + def testLargestCliques(self): self.assertEqual( sorted(map(sorted, self.g.largest_cliques())), [[1, 2, 3, 4], [1, 2, 4, 5]] @@ -138,7 +156,14 @@ def testLargestIndependentVertexSets(self): self.assertEqual( self.g1.largest_independent_vertex_sets(), [(0, 3, 4), (2, 3, 4)] ) - self.assertTrue(all(map(self.g1.is_independent_vertex_set, self.g1.largest_independent_vertex_sets()))) + self.assertTrue( + all( + map( + self.g1.is_independent_vertex_set, + self.g1.largest_independent_vertex_sets(), + ) + ) + ) def testMaximalIndependentVertexSets(self): self.assertEqual( @@ -155,7 +180,14 @@ def testMaximalIndependentVertexSets(self): (2, 4, 7, 8), ], ) - self.assertTrue(all(map(self.g2.is_independent_vertex_set, self.g2.maximal_independent_vertex_sets()))) + self.assertTrue( + all( + map( + self.g2.is_independent_vertex_set, + self.g2.maximal_independent_vertex_sets(), + ) + ) + ) def testIndependenceNumber(self): self.assertEqual(self.g2.independence_number(), 6) diff --git a/tests/test_conversion.py b/tests/test_conversion.py index bbd9574c5..6f3cdc22e 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -186,8 +186,7 @@ def testFromPrufer(self): self.assertEqual(6, g.vcount()) self.assertEqual(5, g.ecount()) self.assertEqual( - [(0, 3), (1, 3), (2, 3), (3, 4), (4, 5)], - sorted(g.get_edgelist()) + [(0, 3), (1, 3), (2, 3), (3, 4), (4, 5)], sorted(g.get_edgelist()) ) def testToPrufer(self): @@ -202,9 +201,7 @@ def suite(): representation_suite = unittest.defaultTestLoader.loadTestsFromTestCase( GraphRepresentationTests ) - prufer_suite = unittest.defaultTestLoader.loadTestsFromTestCase( - PruferTests - ) + prufer_suite = unittest.defaultTestLoader.loadTestsFromTestCase(PruferTests) return unittest.TestSuite([direction_suite, representation_suite, prufer_suite]) diff --git a/tests/test_cycles.py b/tests/test_cycles.py index 48a37f2f7..e3549121c 100644 --- a/tests/test_cycles.py +++ b/tests/test_cycles.py @@ -60,6 +60,25 @@ def test_fundamental_cycles(self): ] assert cycles == [[6, 7, 10], [8, 9, 10]] + def test_simple_cycles(self): + g = Graph( + [ + (0, 1), + (1, 2), + (2, 0), + (0, 0), + (0, 3), + (3, 4), + (4, 5), + (5, 0), + ] + ) + + vertices = g.simple_cycles(output="vpath") + edges = g.simple_cycles(output="epath") + assert len(vertices) == 3 + assert len(edges) == 3 + def test_minimum_cycle_basis(self): g = Graph( [ diff --git a/tests/test_decomposition.py b/tests/test_decomposition.py index 0bfdd1b6a..e9cb73ddc 100644 --- a/tests/test_decomposition.py +++ b/tests/test_decomposition.py @@ -263,11 +263,7 @@ def testEdgeBetweenness(self): g.es["weight"] = 1 g[0, 1] = g[1, 2] = g[2, 0] = g[3, 4] = 10 - # We need to specify the desired cluster count explicitly; this is - # because edge betweenness-based detection does not play well with - # modularity-based cluster count selection (the edge weights have - # different semantics) so we need to give igraph a hint - cl = g.community_edge_betweenness(weights="weight").as_clustering(n=2) + cl = g.community_edge_betweenness(weights="weight").as_clustering() self.assertMembershipsEqual(cl, [0, 0, 0, 1, 1]) self.assertAlmostEqual(cl.q, 0.2750, places=3) @@ -280,11 +276,66 @@ def testEigenvector(self): cl = g.community_leading_eigenvector(2) self.assertMembershipsEqual(cl, [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) self.assertAlmostEqual(cl.q, 0.4523, places=3) + + def testFluidCommunities(self): + # Test with a simple graph: two cliques connected by a single edge + g = Graph.Full(5) + Graph.Full(5) + g.add_edges([(0, 5)]) + + # Test basic functionality - should find 2 communities + cl = g.community_fluid_communities(2) + self.assertEqual(len(set(cl.membership)), 2) + self.assertMembershipsEqual(cl, [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) + + # Test with 3 cliques + g = Graph.Full(4) + Graph.Full(4) + Graph.Full(4) + g += [(0, 4), (4, 8)] # Connect the cliques + cl = g.community_fluid_communities(3) + self.assertEqual(len(set(cl.membership)), 3) + self.assertMembershipsEqual(cl, [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]) + + # Test error conditions + # Number of communities must be positive + with self.assertRaises(Exception): + g.community_fluid_communities(0) + + # Number of communities cannot exceed number of vertices + with self.assertRaises(Exception): + g.community_fluid_communities(g.vcount() + 1) + + # Test with disconnected graph (should raise error) + g_disconnected = Graph.Full(3) + Graph.Full(3) # No connecting edge + with self.assertRaises(Exception): + g_disconnected.community_fluid_communities(2) + + # Test with single vertex (edge case) + g_single = Graph(1) + cl = g_single.community_fluid_communities(1) + self.assertEqual(cl.membership, [0]) + + # Test with small connected graph + g_small = Graph([(0, 1), (1, 2), (2, 0)]) # Triangle + cl = g_small.community_fluid_communities(1) + self.assertEqual(len(set(cl.membership)), 1) + self.assertEqual(cl.membership, [0, 0, 0]) + + # Test deterministic behavior on simple structure + # Note: Fluid communities can be non-deterministic due to randomization, + # but on very simple structures it should be consistent + g_path = Graph([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)]) + cl = g_path.community_fluid_communities(2) + self.assertEqual(len(set(cl.membership)), 2) + + # Test that it returns a VertexClustering object + g = Graph.Full(6) + cl = g.community_fluid_communities(2) + self.assertIsInstance(cl, VertexClustering) + self.assertEqual(len(cl.membership), g.vcount()) def testInfomap(self): g = Graph.Famous("zachary") cl = g.community_infomap() - self.assertAlmostEqual(cl.codelength, 4.60605, places=3) + self.assertAlmostEqual(cl.codelength, 4.31179, places=3) self.assertAlmostEqual(cl.q, 0.40203, places=3) self.assertMembershipsEqual( cl, @@ -483,6 +534,55 @@ def testSpinglass(self): ok = True break self.assertTrue(ok) + + def testVoronoi(self): + # Test 1: Two disconnected cliques - should find exactly 2 communities + g = Graph.Full(5) + Graph.Full(5) # Two separate complete graphs + cl = g.community_voronoi() + + # Should find exactly 2 communities + self.assertEqual(len(cl), 2) + + # Vertices 0-4 should be in one community, vertices 5-9 in another + communities = [set(), set()] + for vertex, community in enumerate(cl.membership): + communities[community].add(vertex) + + # One community should have vertices 0-4, the other should have 5-9 + expected_communities = [{0, 1, 2, 3, 4}, {5, 6, 7, 8, 9}] + self.assertEqual( + set(frozenset(c) for c in communities), + set(frozenset(c) for c in expected_communities) + ) + + # Test 2: Two cliques connected by a single bridge edge + g = Graph.Full(4) + Graph.Full(4) + g.add_edges([(0, 4)]) # Bridge connecting the two cliques + + cl = g.community_voronoi() + + # Should still find 2 communities (bridge is weak) + self.assertEqual(len(cl), 2) + + # Check that vertices within each clique are in the same community + # Vertices 0,1,2,3 should be together, and 4,5,6,7 should be together + comm_0123 = {cl.membership[i] for i in [0, 1, 2, 3]} + comm_4567 = {cl.membership[i] for i in [4, 5, 6, 7]} + + self.assertEqual(len(comm_0123), 1) # All in same community + self.assertEqual(len(comm_4567), 1) # All in same community + self.assertNotEqual(comm_0123, comm_4567) # Different communities + + # Test 3: Three disconnected triangles + g = Graph(9) + g.add_edges([(0, 1), (1, 2), (2, 0), # Triangle 1 + (3, 4), (4, 5), (5, 3), # Triangle 2 + (6, 7), (7, 8), (8, 6)]) # Triangle 3 + + cl = g.community_voronoi() + + # Should find exactly 3 communities + self.assertEqual(len(cl), 3) def testWalktrap(self): g = Graph.Full(5) + Graph.Full(5) + Graph.Full(5) diff --git a/tests/test_foreign.py b/tests/test_foreign.py index 72047dc1a..83664e5f5 100644 --- a/tests/test_foreign.py +++ b/tests/test_foreign.py @@ -215,14 +215,14 @@ def _testNCOLOrLGL(self, func, fname, can_be_reopened=True): self.assertTrue( "name" not in g.vertex_attributes() and "weight" in g.edge_attributes() ) - self.assertTrue(g.es["weight"] == [1, 2, 0, 3, 0]) + self.assertListEqual(g.es["weight"], [1.0, 2.0, 1.0, 3.0, 1.0]) g = func(fname, directed=False) self.assertTrue( "name" in g.vertex_attributes() and "weight" in g.edge_attributes() ) - self.assertTrue(g.vs["name"] == ["eggs", "spam", "ham", "bacon"]) - self.assertTrue(g.es["weight"] == [1, 2, 0, 3, 0]) + self.assertListEqual(g.vs["name"], ["eggs", "spam", "ham", "bacon"]) + self.assertListEqual(g.es["weight"], [1.0, 2.0, 1.0, 3.0, 1.0]) def testNCOL(self): with temporary_file( diff --git a/tests/test_games.py b/tests/test_games.py index 3e78637dc..cf36c75ce 100644 --- a/tests/test_games.py +++ b/tests/test_games.py @@ -163,7 +163,7 @@ def testRewire(self): self.assertTrue(g.is_simple()) # Rewiring with loops (1) - g.rewire(10000, mode="loops") + g.rewire(10000, allowed_edge_types="loops") self.assertEqual(degrees, g.degree()) self.assertFalse(any(g.is_multiple())) @@ -171,7 +171,7 @@ def testRewire(self): g = Graph.Full(4) g[1, 3] = 0 degrees = g.degree() - g.rewire(100, mode="loops") + g.rewire(100, allowed_edge_types="loops") self.assertEqual(degrees, g.degree()) self.assertFalse(any(g.is_multiple())) @@ -185,7 +185,7 @@ def testRewire(self): self.assertTrue(g.is_simple()) # Directed graph with loops - g.rewire(10000, mode="loops") + g.rewire(10000, allowed_edge_types="loops") self.assertEqual(indeg, g.indegree()) self.assertEqual(outdeg, g.outdegree()) self.assertFalse(any(g.is_multiple())) diff --git a/tests/test_generators.py b/tests/test_generators.py index 30cc58d23..0e1227658 100644 --- a/tests/test_generators.py +++ b/tests/test_generators.py @@ -162,7 +162,20 @@ def testHexagonalLattice(self): self.assertEqual(sorted(g.get_edgelist()), sorted(el + [(y, x) for x, y in el])) def testHypercube(self): - el = [(0, 1), (0, 2), (0, 4), (1, 3), (1, 5), (2, 3), (2, 6), (3, 7), (4, 5), (4, 6), (5, 7), (6, 7)] + el = [ + (0, 1), + (0, 2), + (0, 4), + (1, 3), + (1, 5), + (2, 3), + (2, 6), + (3, 7), + (4, 5), + (4, 6), + (5, 7), + (6, 7), + ] g = Graph.Hypercube(3) self.assertEqual(g.get_edgelist(), el) @@ -416,9 +429,8 @@ def testLattice(self): def testSBM(self): pref_matrix = [[0.5, 0, 0], [0, 0, 0.5], [0, 0.5, 0]] - n = 60 types = [20, 20, 20] - g = Graph.SBM(n, pref_matrix, types) + g = Graph.SBM(pref_matrix, types) # Simple smoke tests for the expected structure of the graph self.assertTrue(g.is_simple()) @@ -427,22 +439,20 @@ def testSBM(self): g2 = g.subgraph(list(range(20, 60))) self.assertTrue(not any(e.source // 20 == e.target // 20 for e in g2.es)) - # Check loops argument - g = Graph.SBM(n, pref_matrix, types, loops=True) + # Check allowed_edge_types argument + g = Graph.SBM(pref_matrix, types, allowed_edge_types="loops") self.assertFalse(g.is_simple()) self.assertTrue(sum(g.is_loop()) > 0) # Check directedness - g = Graph.SBM(n, pref_matrix, types, directed=True) + g = Graph.SBM(pref_matrix, types, directed=True) self.assertTrue(g.is_directed()) self.assertTrue(sum(g.is_mutual()) < g.ecount()) self.assertTrue(sum(g.is_loop()) == 0) # Check error conditions - self.assertRaises(ValueError, Graph.SBM, -1, pref_matrix, types) - self.assertRaises(InternalError, Graph.SBM, 61, pref_matrix, types) pref_matrix[0][1] = 0.7 - self.assertRaises(InternalError, Graph.SBM, 60, pref_matrix, types) + self.assertRaises(InternalError, Graph.SBM, pref_matrix, types) def testTriangularLattice(self): g = Graph.Triangular_Lattice([2, 2]) @@ -481,33 +491,35 @@ def testAdjacencyNumPy(self): # ADJ_DIRECTED (default) g = Graph.Adjacency(mat) el = g.get_edgelist() - self.assertTrue(el == [(0, 1), (0, 2), (1, 0), (2, 2), (2, 2), (3, 1)]) + self.assertListEqual( + sorted(el), [(0, 1), (0, 2), (1, 0), (2, 2), (2, 2), (3, 1)] + ) # ADJ MIN g = Graph.Adjacency(mat, mode="min") el = g.get_edgelist() - self.assertTrue(el == [(0, 1), (2, 2), (2, 2)]) + self.assertListEqual(sorted(el), [(0, 1), (2, 2), (2, 2)]) # ADJ MAX g = Graph.Adjacency(mat, mode="max") el = g.get_edgelist() self.assertFalse(g.is_directed()) self.assertEqual(4, g.vcount()) - self.assertTrue(el == [(0, 1), (0, 2), (1, 3), (2, 2), (2, 2)]) + self.assertListEqual(sorted(el), [(0, 1), (0, 2), (1, 3), (2, 2), (2, 2)]) # ADJ LOWER g = Graph.Adjacency(mat, mode="lower") el = g.get_edgelist() self.assertFalse(g.is_directed()) self.assertEqual(4, g.vcount()) - self.assertTrue(el == [(0, 1), (2, 2), (2, 2), (1, 3)]) + self.assertListEqual(sorted(el), [(0, 1), (1, 3), (2, 2), (2, 2)]) # ADJ UPPER g = Graph.Adjacency(mat, mode="upper") el = g.get_edgelist() self.assertFalse(g.is_directed()) self.assertEqual(4, g.vcount()) - self.assertTrue(el == [(0, 1), (0, 2), (2, 2), (2, 2)]) + self.assertListEqual(sorted(el), [(0, 1), (0, 2), (2, 2), (2, 2)]) @unittest.skipIf(np is None, "test case depends on NumPy") def testAdjacencyNumPyLoopHandling(self): @@ -518,64 +530,66 @@ def testAdjacencyNumPyLoopHandling(self): # ADJ_DIRECTED (default) g = Graph.Adjacency(mat) el = g.get_edgelist() - self.assertTrue(el == [(0, 1), (0, 2), (1, 0), (2, 2), (2, 2), (3, 1)]) + self.assertListEqual( + sorted(el), [(0, 1), (0, 2), (1, 0), (2, 2), (2, 2), (3, 1)] + ) # ADJ MIN g = Graph.Adjacency(mat, mode="min", loops="twice") el = g.get_edgelist() - self.assertTrue(el == [(0, 1), (2, 2)]) + self.assertListEqual(sorted(el), [(0, 1), (2, 2)]) # ADJ MAX g = Graph.Adjacency(mat, mode="max", loops="twice") el = g.get_edgelist() self.assertFalse(g.is_directed()) self.assertEqual(4, g.vcount()) - self.assertTrue(el == [(0, 1), (0, 2), (1, 3), (2, 2)]) + self.assertListEqual(sorted(el), [(0, 1), (0, 2), (1, 3), (2, 2)]) # ADJ LOWER g = Graph.Adjacency(mat, mode="lower", loops="twice") el = g.get_edgelist() self.assertFalse(g.is_directed()) self.assertEqual(4, g.vcount()) - self.assertTrue(el == [(0, 1), (2, 2), (1, 3)]) + self.assertListEqual(sorted(el), [(0, 1), (1, 3), (2, 2), (2, 2)]) # ADJ UPPER g = Graph.Adjacency(mat, mode="upper", loops="twice") el = g.get_edgelist() self.assertFalse(g.is_directed()) self.assertEqual(4, g.vcount()) - self.assertTrue(el == [(0, 1), (0, 2), (2, 2)]) + self.assertListEqual(sorted(el), [(0, 1), (0, 2), (2, 2), (2, 2)]) # ADJ_DIRECTED (default) g = Graph.Adjacency(mat, loops=False) el = g.get_edgelist() - self.assertTrue(el == [(0, 1), (0, 2), (1, 0), (3, 1)]) + self.assertListEqual(sorted(el), [(0, 1), (0, 2), (1, 0), (3, 1)]) # ADJ MIN g = Graph.Adjacency(mat, mode="min", loops=False) el = g.get_edgelist() - self.assertTrue(el == [(0, 1)]) + self.assertListEqual(sorted(el), [(0, 1)]) # ADJ MAX g = Graph.Adjacency(mat, mode="max", loops=False) el = g.get_edgelist() self.assertFalse(g.is_directed()) self.assertEqual(4, g.vcount()) - self.assertTrue(el == [(0, 1), (0, 2), (1, 3)]) + self.assertListEqual(sorted(el), [(0, 1), (0, 2), (1, 3)]) # ADJ LOWER g = Graph.Adjacency(mat, mode="lower", loops=False) el = g.get_edgelist() self.assertFalse(g.is_directed()) self.assertEqual(4, g.vcount()) - self.assertTrue(el == [(0, 1), (1, 3)]) + self.assertListEqual(sorted(el), [(0, 1), (1, 3)]) # ADJ UPPER g = Graph.Adjacency(mat, mode="upper", loops=False) el = g.get_edgelist() self.assertFalse(g.is_directed()) self.assertEqual(4, g.vcount()) - self.assertTrue(el == [(0, 1), (0, 2)]) + self.assertListEqual(sorted(el), [(0, 1), (0, 2)]) @unittest.skipIf( (sparse is None) or (np is None), "test case depends on NumPy/SciPy" @@ -689,23 +703,23 @@ def testWeightedAdjacency(self): g = Graph.Weighted_Adjacency(mat, attr="w0") el = g.get_edgelist() - self.assertTrue(el == [(0, 1), (0, 2), (1, 0), (2, 2), (3, 1)]) - self.assertTrue(g.es["w0"] == [1, 2, 2, 2.5, 1]) + self.assertListEqual(el, [(1, 0), (0, 1), (3, 1), (0, 2), (2, 2)]) + self.assertListEqual(g.es["w0"], [2, 1, 1, 2, 2.5]) g = Graph.Weighted_Adjacency(mat, mode="plus") el = g.get_edgelist() - self.assertTrue(el == [(0, 1), (0, 2), (1, 3), (2, 2)]) - self.assertTrue(g.es["weight"] == [3, 2, 1, 2.5]) + self.assertListEqual(el, [(0, 1), (0, 2), (1, 3), (2, 2)]) + self.assertListEqual(g.es["weight"], [3, 2, 1, 2.5]) g = Graph.Weighted_Adjacency(mat, attr="w0", loops=False) el = g.get_edgelist() - self.assertTrue(el == [(0, 1), (0, 2), (1, 0), (3, 1)]) - self.assertTrue(g.es["w0"] == [1, 2, 2, 1]) + self.assertListEqual(el, [(1, 0), (0, 1), (3, 1), (0, 2)]) + self.assertListEqual(g.es["w0"], [2, 1, 1, 2]) g = Graph.Weighted_Adjacency(mat, attr="w0", loops="twice") el = g.get_edgelist() - self.assertTrue(el == [(0, 1), (0, 2), (1, 0), (2, 2), (3, 1)]) - self.assertTrue(g.es["w0"] == [1, 2, 2, 1.25, 1]) + self.assertListEqual(el, [(1, 0), (0, 1), (3, 1), (0, 2), (2, 2)]) + self.assertListEqual(g.es["w0"], [2, 1, 1, 2, 2.5]) @unittest.skipIf(np is None, "test case depends on NumPy") def testWeightedAdjacencyNumPy(self): @@ -715,23 +729,23 @@ def testWeightedAdjacencyNumPy(self): g = Graph.Weighted_Adjacency(mat, attr="w0") el = g.get_edgelist() - self.assertTrue(el == [(0, 1), (0, 2), (1, 0), (2, 2), (3, 1)]) - self.assertTrue(g.es["w0"] == [1, 2, 2, 2.5, 1]) + self.assertListEqual(el, [(1, 0), (0, 1), (3, 1), (0, 2), (2, 2)]) + self.assertListEqual(g.es["w0"], [2, 1, 1, 2, 2.5]) g = Graph.Weighted_Adjacency(mat, mode="plus") el = g.get_edgelist() - self.assertTrue(el == [(0, 1), (0, 2), (1, 3), (2, 2)]) - self.assertTrue(g.es["weight"] == [3, 2, 1, 2.5]) + self.assertListEqual(el, [(0, 1), (0, 2), (1, 3), (2, 2)]) + self.assertListEqual(g.es["weight"], [3, 2, 1, 2.5]) g = Graph.Weighted_Adjacency(mat, attr="w0", loops=False) el = g.get_edgelist() - self.assertTrue(el == [(0, 1), (0, 2), (1, 0), (3, 1)]) - self.assertTrue(g.es["w0"] == [1, 2, 2, 1]) + self.assertListEqual(el, [(1, 0), (0, 1), (3, 1), (0, 2)]) + self.assertListEqual(g.es["w0"], [2, 1, 1, 2]) g = Graph.Weighted_Adjacency(mat, attr="w0", loops="twice") el = g.get_edgelist() - self.assertTrue(el == [(0, 1), (0, 2), (1, 0), (2, 2), (3, 1)]) - self.assertTrue(g.es["w0"] == [1, 2, 2, 1.25, 1]) + self.assertListEqual(el, [(1, 0), (0, 1), (3, 1), (0, 2), (2, 2)]) + self.assertListEqual(g.es["w0"], [2, 1, 1, 2, 2.5]) @unittest.skipIf( (sparse is None) or (np is None), "test case depends on NumPy/SciPy" @@ -745,36 +759,36 @@ def testSparseWeightedAdjacency(self): el = g.get_edgelist() self.assertTrue(g.is_directed()) self.assertEqual(4, g.vcount()) - self.assertTrue(el == [(0, 1), (0, 2), (1, 0), (2, 2), (3, 1)]) - self.assertTrue(g.es["w0"] == [1, 2, 2, 2.5, 1]) + self.assertListEqual(el, [(0, 1), (0, 2), (1, 0), (2, 2), (3, 1)]) + self.assertListEqual(g.es["w0"], [1, 2, 2, 2.5, 1]) g = Graph.Weighted_Adjacency(mat, mode="plus") el = g.get_edgelist() self.assertFalse(g.is_directed()) self.assertEqual(4, g.vcount()) - self.assertTrue(el == [(0, 1), (0, 2), (2, 2), (1, 3)]) - self.assertTrue(g.es["weight"] == [3, 2, 2.5, 1]) + self.assertListEqual(el, [(0, 1), (0, 2), (2, 2), (1, 3)]) + self.assertListEqual(g.es["weight"], [3, 2, 2.5, 1]) g = Graph.Weighted_Adjacency(mat, mode="min") el = g.get_edgelist() self.assertFalse(g.is_directed()) self.assertEqual(4, g.vcount()) - self.assertTrue(el == [(0, 1), (2, 2)]) - self.assertTrue(g.es["weight"] == [1, 2.5]) + self.assertListEqual(el, [(0, 1), (2, 2)]) + self.assertListEqual(g.es["weight"], [1, 2.5]) g = Graph.Weighted_Adjacency(mat, attr="w0", loops=False) el = g.get_edgelist() self.assertTrue(g.is_directed()) self.assertEqual(4, g.vcount()) - self.assertTrue(el == [(0, 1), (0, 2), (1, 0), (3, 1)]) - self.assertTrue(g.es["w0"] == [1, 2, 2, 1]) + self.assertListEqual(el, [(0, 1), (0, 2), (1, 0), (3, 1)]) + self.assertListEqual(g.es["w0"], [1, 2, 2, 1]) g = Graph.Weighted_Adjacency(mat, attr="w0", loops="twice") el = g.get_edgelist() self.assertTrue(g.is_directed()) self.assertEqual(4, g.vcount()) - self.assertTrue(el == [(0, 1), (0, 2), (1, 0), (2, 2), (3, 1)]) - self.assertTrue(g.es["w0"] == [1, 2, 2, 1.25, 1]) + self.assertListEqual(el, [(0, 1), (0, 2), (1, 0), (2, 2), (3, 1)]) + self.assertListEqual(g.es["w0"], [1, 2, 2, 1.25, 1]) @unittest.skipIf((np is None) or (pd is None), "test case depends on NumPy/Pandas") def testDataFrame(self): @@ -788,9 +802,9 @@ def testDataFrame(self): [["A", "blue"], ["B", "yellow"], ["C", "blue"]], columns=[0, "color"] ) g = Graph.DataFrame(edges, directed=True, vertices=vertices, use_vids=False) - self.assertTrue(g.vs["name"] == ["A", "B", "C"]) - self.assertTrue(g.vs["color"] == ["blue", "yellow", "blue"]) - self.assertTrue(g.es["weight"] == [0.4, 0.1]) + self.assertListEqual(g.vs["name"], ["A", "B", "C"]) + self.assertListEqual(g.vs["color"], ["blue", "yellow", "blue"]) + self.assertListEqual(g.es["weight"], [0.4, 0.1]) # Issue #347 edges = pd.DataFrame({"source": [1, 2, 3], "target": [4, 5, 6]}) @@ -798,8 +812,8 @@ def testDataFrame(self): {"node": [1, 2, 3, 4, 5, 6], "label": ["1", "2", "3", "4", "5", "6"]} )[["node", "label"]] g = Graph.DataFrame(edges, directed=True, vertices=vertices, use_vids=False) - self.assertTrue(g.vs["name"] == [1, 2, 3, 4, 5, 6]) - self.assertTrue(g.vs["label"] == ["1", "2", "3", "4", "5", "6"]) + self.assertListEqual(g.vs["name"], [1, 2, 3, 4, 5, 6]) + self.assertListEqual(g.vs["label"], ["1", "2", "3", "4", "5", "6"]) # Vertex names edges = pd.DataFrame({"source": [1, 2, 3], "target": [4, 5, 6]}) @@ -880,6 +894,27 @@ def testDataFrame(self): edges = pd.DataFrame(np.array([[0, 1], [1, np.nan], [1, 2]]), dtype="Int64") Graph.DataFrame(edges) + def testNearestNeighborGraph(self): + points = [[0, 0], [1, 2], [-3, -3]] + + g = Graph.Nearest_Neighbor_Graph(points) + # expecting 1 - 2, 3 - 1 + self.assertFalse(g.is_directed()) + self.assertEqual(g.vcount(), 3) + self.assertEqual(g.ecount(), 2) + + g = Graph.Nearest_Neighbor_Graph(points, directed=True) + # expecting 1 <-> 2, 3 -> 1 + self.assertTrue(g.is_directed()) + self.assertEqual(g.vcount(), 3) + self.assertEqual(g.ecount(), 3) + + # expecting a complete graph + g = Graph.Nearest_Neighbor_Graph(points, k=2) + self.assertFalse(g.is_directed()) + self.assertEqual(g.vcount(), 3) + self.assertTrue(g.is_complete()) + def suite(): generator_suite = unittest.defaultTestLoader.loadTestsFromTestCase(GeneratorTests) diff --git a/tests/test_isomorphism.py b/tests/test_isomorphism.py index c1150da63..7984e9b34 100644 --- a/tests/test_isomorphism.py +++ b/tests/test_isomorphism.py @@ -359,6 +359,7 @@ def testCanonicalPermutation(self): perm = list(range(10)) shuffle(perm) g2 = g.permute_vertices(perm) + self.assertTrue(g.isomorphic(g2)) g3 = g.permute_vertices(g.canonical_permutation()) g4 = g2.permute_vertices(g2.canonical_permutation()) @@ -400,7 +401,7 @@ def testPermuteVertices(self): (7, 4), ], ) - _, _, mapping = g1.isomorphic_vf2(g2, return_mapping_21=True) + _, mapping, _ = g1.isomorphic_vf2(g2, return_mapping_12=True) g3 = g2.permute_vertices(mapping) self.assertTrue(g3.vcount() == g2.vcount() and g3.ecount() == g2.ecount()) self.assertTrue(set(g3.get_edgelist()) == set(g1.get_edgelist())) diff --git a/tests/test_layouts.py b/tests/test_layouts.py index ac32e0210..337f498f8 100644 --- a/tests/test_layouts.py +++ b/tests/test_layouts.py @@ -1,6 +1,6 @@ import unittest from math import hypot -from igraph import Graph, Layout, BoundingBox, InternalError +from igraph import Graph, Layout, BoundingBox, InternalError, align_layout from igraph import umap_compute_weights @@ -242,8 +242,12 @@ def testKamadaKawai(self): self.assertTrue(bbox.right <= 6) lo = g.layout( - "kk", miny=[2] * 100, maxy=[3] * 100, minx=[4] * 100, maxx=[6] * 100, - weights=range(10, g.ecount() + 10) + "kk", + miny=[2] * 100, + maxy=[3] * 100, + minx=[4] * 100, + maxx=[6] * 100, + weights=range(10, g.ecount() + 10), ) self.assertTrue(isinstance(lo, Layout)) @@ -452,6 +456,13 @@ def testDRL(self): lo = g.layout("drl") self.assertTrue(isinstance(lo, Layout)) + def testAlign(self): + g = Graph.Ring(3, circular=False) + lo = Layout([[1,1], [2,2], [3,3]]) + lo = align_layout(g, lo) + self.assertTrue(isinstance(lo, Layout)) + self.assertTrue(all(abs(lo[i][1]) < 1e-10 for i in range(3))) + def suite(): layout_suite = unittest.defaultTestLoader.loadTestsFromTestCase(LayoutTests) diff --git a/tests/test_structural.py b/tests/test_structural.py index b9d90a7dd..c0f1bdb59 100644 --- a/tests/test_structural.py +++ b/tests/test_structural.py @@ -503,19 +503,28 @@ def testEigenvectorCentrality(self): def testAuthorityScore(self): g = Graph.Tree(15, 2, TREE_IN) - asc = g.authority_score() + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + asc = g.authority_score() self.assertAlmostEqual(max(asc), 1.0, places=3) # Smoke testing - g.authority_score(scale=False, return_eigenvalue=True) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + g.authority_score(scale=False, return_eigenvalue=True) def testHubScore(self): g = Graph.Tree(15, 2, TREE_IN) - hsc = g.hub_score() + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + hsc = g.hub_score() self.assertAlmostEqual(max(hsc), 1.0, places=3) # Smoke testing - g.hub_score(scale=False, return_eigenvalue=True) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + g.hub_score(scale=False, return_eigenvalue=True) def testCoreness(self): g = Graph.Full(4) + Graph(4) + [(0, 4), (1, 5), (2, 6), (3, 7)] @@ -824,14 +833,6 @@ def testDistances(self): g.distances(weights="weight", target=[2, 3], algorithm="johnson") == [row[2:4] for row in expected] ) - self.assertRaises( - ValueError, - g.distances, - weights="weight", - target=[2, 3], - algorithm="johnson", - mode="in", - ) def testGetShortestPath(self): g = Graph(4, [(0, 1), (0, 2), (1, 3), (3, 2), (2, 1)], directed=True) diff --git a/tox.ini b/tox.ini index 221c56a55..04fb5ca92 100644 --- a/tox.ini +++ b/tox.ini @@ -1,18 +1,19 @@ -# Tox (http://tox.testrun.org/) is a tool for running tests +# Tox (https://tox.wiki) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] -envlist = py38, py39, py310, py311, py312, pypy3 +envlist = py38, py39, py310, py311, py312, py313, pypy3 [gh-actions] python = 3.8: py38 3.9: py39 - 3.10: py310 - 3.11: py311 - 3.12: py312 + 3.10: py310 + 3.11: py311 + 3.12: py312 + 3.13: py313 pypy-3.7: pypy3 [testenv] diff --git a/vendor/source/igraph b/vendor/source/igraph index 635b432ef..7b4ae766c 160000 --- a/vendor/source/igraph +++ b/vendor/source/igraph @@ -1 +1 @@ -Subproject commit 635b432eff0a89580ac9bb98068d2fbc8ef374f2 +Subproject commit 7b4ae766cbdee6b2017aa5b76752457db2a2972f