chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:36:30 +08:00
commit 55ab4e4a73
473 changed files with 72932 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
[bumpversion]
current_version = 0.2a47
commit = True
tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)a(?P<patch>\d+)
serialize = {major}.{minor}a{patch}
[bumpversion:file:setup.py]
search = version="{current_version}"
replace = version="{new_version}"
+69
View File
@@ -0,0 +1,69 @@
[flake8]
min_python_version = 3.7.0
max-line-length = 88
ban-relative-imports = true
# flake8-use-fstring: https://github.com/MichaelKim0407/flake8-use-fstring#--percent-greedy-and---format-greedy
format-greedy = 1
inline-quotes = double
enable-extensions = TC, TC1
type-checking-exempt-modules = typing, typing-extensions
eradicate-whitelist-extend = ^-.*;
extend-ignore =
# E203: Whitespace before ':' (pycqa/pycodestyle#373)
E203,
# SIM106: Handle error-cases first
SIM106,
# ANN101: Missing type annotation for self in method
ANN101,
# ANN102: Missing type annotation for cls in classmethod
ANN102,
F401,
F403,
I252,
SIM115,
N806,
N803,
B007,
E501,
C408,
SIM113,
N802,
N400,
E501,
E802,
E800,
B001,
B006,
B020,
C414,
C416,
E402,
E711,
E712,
E722,
E741,
F405,
F541,
F811,
F821,
F841,
FS001,
FS002,
I250,
N801,
N804,
N812,
N816,
Q002,
SIM102,
SIM105,
SIM111,
SIM114,
SIM118,
SIM201,
SIM203,
SIM300,
SIM401,
TC002
+32
View File
@@ -0,0 +1,32 @@
---
name: "\U0001F41E Bug Report"
about: Did you find a bug?
title: ''
labels: Bug, Triage
assignees: ''
---
<!--
Hi there! Thank you for discovering and submitting an issue.
Before you submit this; let's make sure of a few things.
Please make sure the following boxes are ticked if they are correct.
If not, please try and fulfill these first.
-->
<!-- Checked checkbox should look like this: [x] -->
- [ ] I am on the [latest](https://github.com/easy-graph/Easy-Graph/releases/latest) EasyGraph version.
- [ ] I have searched the [issues](https://github.com/easy-graph/Easy-Graph/issues) of this repo and believe that this is not a duplicate.
<!--
Once those are done, if you're able to fill in the following list with your information,
it'd be very helpful to whoever handles the issue.
-->
- **OS version and name**: <!-- Replace with version + name -->
- **EasyGraph version**: <!-- Replace with version -->
<!-- - **Link of a [Gist](https://gist.github.com/) with the contents of your pyproject.toml file**: Gist Link Here -->
## Issue
<!-- Now feel free to write your issue, but please be descriptive! Thanks again 🙌 ❤️ -->
@@ -0,0 +1,22 @@
---
name: "\U0001F4DA Documentation"
about: Did you find errors, problems, or anything unintelligible in the docs (https://easy-graph.github.io/)?
title: ''
labels: Documentation, Triage
assignees: ''
---
<!--
Hi there! Thank you for discovering and submitting an issue with our documentation.
Before you submit this; let's make sure of a few things.
Please make sure the following boxes are ticked if they are correct.
If not, please try and fulfill these first.
-->
<!-- Checked checkbox should look like this: [x] -->
- [ ] I have searched the [issues](https://github.com/easy-graph/Easy-Graph/issues) of this repo and believe that this is not a duplicate.
## Issue
<!-- Now feel free to write your issue, but please be descriptive! Thanks again 🙌 ❤️ -->
@@ -0,0 +1,20 @@
---
name: "\U0001F5C3 Everything Else"
about: For questions and issues that do not fall in any of the other categories. This
can include questions about EasyGraph's roadmap. For support questions, please post
on StackOverflow.
title: ''
labels: ''
assignees: ''
---
<!-- Describe your question and issue here. This space is meant to be used for general questions that are neither bugs, feature requests, nor documentation issues. A good example would be a question regarding EasyGraph's roadmap, for example. If you're looking for help when it comes to using EasyGraph, consider posting a question on StackOverflow instead: http://stackoverflow.com/questions/tagged/python-easygraph -->
<!-- Checked checkbox should look like this: [x] -->
- [ ] I have searched the [issues](https://github.com/easy-graph/Easy-Graph/issues) of this repo and believe that this is not a duplicate.
- [ ] I have searched the [documentation](https://easy-graph.github.io/) and believe that my question is not covered.
## Issue
<!-- Now feel free to write your issue, but please be descriptive! Thanks again 🙌 ❤️ -->
@@ -0,0 +1,23 @@
---
name: "\U0001F381 Feature Request"
about: Do you have ideas for new features and improvements?
title: ''
labels: Feature, Triage
assignees: ''
---
<!--
Hi there! Thank you for wanting to make EasyGraph better.
Before you submit this; let's make sure of a few things.
Please make sure the following boxes are ticked if they are correct.
If not, please try and fulfill these first.
-->
<!-- Checked checkbox should look like this: [x] -->
- [ ] I have searched the [issues](https://github.com/easy-graph/Easy-Graph/issues) of this repo and believe that this is not a duplicate.
- [ ] I have searched the [documentation](https://easy-graph.github.io/) and believe that my question is not covered.
## Feature Request
<!-- Now feel free to write your idea for improvement. Thanks again 🙌 ❤️ -->
+7
View File
@@ -0,0 +1,7 @@
# Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser
blank_issues_enabled: true
contact_links:
- name: '💬 Discord Server'
url: https://discord.gg/JV6N8Whe
about: |
Chat with the community, ask questions and learn about best practices.
+10
View File
@@ -0,0 +1,10 @@
# Pull Request Check List
Resolves: #issue-number-here
<!-- This is just a reminder about the most common mistakes. Please make sure that you tick all *appropriate* boxes. But please read our [contribution guide](https://github.com/easy-graph/Easy-Graph/blob/master/CONTRIBUTING.md) at least once, it will save you unnecessary review cycles! -->
- [ ] Added **tests** for changed code.
- [ ] Updated **documentation** for changed code.
<!-- If you have *any* questions to *any* of the points above, just **submit and ask**! This checklist is here to *help* you, not to deter you from contributing! -->
+11
View File
@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"
@@ -0,0 +1,44 @@
# https://github.com/marketplace/actions/manylinux-wheel-builder
name: pypi deployer
on:
workflow_dispatch
# push
# push:
# tags:
# - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10
jobs:
Linux-build:
runs-on: ubuntu-22.04
env:
TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }}
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
steps:
- uses: actions/checkout@v2
- name: build and upload manylinux wheels
# cSpell:disable
uses: Niraj-Kamdar/manylinux-wheel-builder@master
# cSpell:enable
Matrix-build:
runs-on: ${{ matrix.os }}
env:
TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }}
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
os: [macos-latest, windows-latest]
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: build wheel
run: |
pip install wheel
python setup.py bdist_wheel
- name: upload wheel
run: |
pip install twine
twine upload dist/*
continue-on-error: true
@@ -0,0 +1,41 @@
name: Build and Publish to PyPI (macOS)
# on: push
# disabled on push cuz Error: Container action is only supported on Linux
# no solution found yet
on: workflow_dispatch
jobs:
build-n-publish:
# name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI
name: Build and publish Python 🐍 distributions 📦 to PyPI
# runs-on: ubuntu-22.04
runs-on: macos-latest
strategy:
matrix:
python-version: ["3.6", "3.7", "3.8", "3.9"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install pypa/build
run: >-
python -m
pip install
build
--user
- name: Build a binary wheel and a source tarball
run: >-
python -m
build
--sdist
--wheel
--outdir dist/
.
- name: Publish distribution 📦 to PyPI
# if: startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
with:
password: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,50 @@
name: Build and Publish to PyPI (manylinux)
on: workflow_dispatch
jobs:
build-n-publish:
# name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI
name: Build and publish Python 🐍 distributions 📦 to PyPI
# runs-on: ubuntu-22.04
runs-on: ubuntu-22.04
# strategy:
# matrix:
# python-version: ["3.6", "3.7", "3.8", "3.9"]
steps:
- uses: actions/checkout@v2
# - name: Set up Python ${{ matrix.python-version }}
# uses: actions/setup-python@v2
# with:
# python-version: ${{ matrix.python-version }}
# - name: Install pypa/build
# run: >-
# python -m
# pip install
# build
# --user
# - name: Build a binary wheel and a source tarball
# run: >-
# python -m
# build
# --sdist
# --wheel
# --outdir dist/
# .
# cSpell:disable
- name: Build on manylinux
uses: RalfG/python-wheels-manylinux-build@v0.4.2-manylinux2014_x86_64
# cSpell:enable
with:
python-versions: "cp36-cp36m cp37-cp37m cp38-cp38m cp39-cp39m"
# build-requirements: "cython numpy"
# system-packages: "lrzip-devel zlib-devel"
# pre-build-command: "sh pre-build-script.sh"
# package-path: "my_project"
# pip-wheel-args: "-w ./dist --no-deps"
# pip-wheel-args: "--sdist --wheel --outdir dist/"
- name: Publish distribution 📦 to PyPI
# if: startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
with:
password: ${{ secrets.PYPI_API_TOKEN }}
+15
View File
@@ -0,0 +1,15 @@
name: "Run Teddy's benchmarking workflow"
on:
# push:
# pull_request:
workflow_dispatch:
jobs:
trigger:
permissions: write-all
name: "Trigger Teddy's benchmarking workflow"
uses: tddschn/easygraph-bench/.github/workflows/bench-all.yaml@master
secrets:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
releaseTag: "CI-easygraph-bench"
+16
View File
@@ -0,0 +1,16 @@
name: Create diagram
on:
workflow_dispatch: {}
# push:
# branches:
# - master
jobs:
get_data:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@master
- name: Update diagram
uses: githubocto/repo-visualizer@main
with:
excluded_paths: "ignore,.github"
+50
View File
@@ -0,0 +1,50 @@
name: PR code review
on:
workflow_dispatch:
pull_request:
branches:
- pybind11
push:
branches:
- pybind11
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
strategy:
fail-fast: false
matrix:
platform: [windows-latest, macos-latest, ubuntu-latest]
python-version: ["3.9", "3.12"]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Upgrade pip
run: python -m pip install --upgrade pip
- name: Installing dependencies
uses: py-actions/py-dependency-install@v4
with:
path: "requirements.txt"
- name: Add requirements
run: python -m pip install --upgrade setuptools wheel build packaging
- name: Build and install
run: pip install --verbose .
- name: Smoke test
run: python -c "import os, tempfile; os.chdir(tempfile.gettempdir()); import easygraph as eg; G = eg.Graph(); G.add_edge(1, 2); assert G.number_of_nodes() == 2; assert G.number_of_edges() == 1"
+38
View File
@@ -0,0 +1,38 @@
name: pre-commit
on:
workflow_dispatch:
pull_request:
branches:
- pybind11
push:
branches:
- pybind11
jobs:
pre-commit:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install pre-commit
run: python -m pip install pre-commit
- name: Run pre-commit on changed files
shell: bash
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
base_ref="${{ github.event.pull_request.base.sha }}"
elif [[ -n "${{ github.event.before }}" && ! "${{ github.event.before }}" =~ ^0+$ ]]; then
base_ref="${{ github.event.before }}"
else
base_ref="$(git rev-parse HEAD~1)"
fi
pre-commit run --show-diff-on-failure --color=always --from-ref "$base_ref" --to-ref "${{ github.sha }}"
+19
View File
@@ -0,0 +1,19 @@
name: "Close stale issues and PRs"
on:
schedule:
- cron: "30 1 * * *"
jobs:
stale:
runs-on: ubuntu-22.04
steps:
- uses: actions/stale@v4
with:
stale-issue-message: "This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days."
stale-pr-message: "This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days."
close-issue-message: "This issue was closed because it has been stalled for 5 days with no activity."
close-pr-message: "This PR was closed because it has been stalled for 10 days with no activity."
days-before-issue-stale: 30
days-before-pr-stale: 45
days-before-issue-close: 5
days-before-pr-close: 10
+46
View File
@@ -0,0 +1,46 @@
name: Test the package with pytest
on:
workflow_dispatch:
pull_request:
branches:
- pybind11
push:
branches:
- pybind11
jobs:
pytest:
name: Test with pytest
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
# ubuntu 22.04 has deprecated python 3.6
python-version: [ "3.8", "3.9", "3.10","3.11", "3.12"]
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Installing dependencies
uses: py-actions/py-dependency-install@v4
with:
path: "requirements.txt"
- name: Installing test dependencies
run: |
pip install pytest
- name: Build and install package
run: |
pip install .
- name: Test with pytest
run: |
pytest easygraph --disable-warnings
+34
View File
@@ -0,0 +1,34 @@
#name: 'issue-translator'
#on:
# issue_comment:
# types: [created]
# issues:
# types: [opened]
#
#jobs:
# build:
# runs-on: ubuntu-latest
# steps:
# - uses: usthe/issues-translate-action@v2.7
# with:
# IS_MODIFY_TITLE: false
# # not require, default false, . Decide whether to modify the issue title
# # if true, the robot account @Issues-translate-bot must have modification permissions, invite @Issues-translate-bot to your project or use your custom bot.
# CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿
# # not require. Customize the translation robot prefix message.
name: 'issue-translator'
on:
issue_comment:
types: [created]
issues:
types: [opened]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: usthe/issues-translate-action@v2.7
with:
CUSTOM_BOT_NOTE: hello
BOT_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
+75
View File
@@ -0,0 +1,75 @@
name: Build,download and publish wheels
on: workflow_dispatch
jobs:
build_sdist:
name: Build source distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# sdist used to compile in user local environment
- name: Build sdist
run: pipx run build --sdist
- uses: actions/upload-artifact@v4
with:
name: cibw-sdist
path: dist/*.tar.gz
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
# macos-13 is closing down, macos-15 is apple silicon
# macos-15-intel is for the x86_64
# note: Apple has discontinued support for the x86_64 (Intel) architecture
os: [ ubuntu-latest, windows-latest, macos-15, macos-15-intel]
# modify python version in project.toml
steps:
# 检出代码
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: true
- uses: actions/setup-python@v5
# You can test your matrix by printing the current Python version
- name: Display Python version
run: python -c "import sys; print(sys.version)"
- name: Install dependencies
run: python -m pip install --upgrade pip setuptools wheel build packaging
- name: Build wheels
uses: pypa/cibuildwheel@v2.23.3
with:
output-dir: dist/
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
path: dist/*.whl
upload_pypi:
needs: [build_wheels, build_sdist]
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
# unpacks all CIBW artifacts into dist/
pattern: cibw-*
path: dist
merge-multiple: true
- name: Publish distribution 📦 to PyPI
uses: pypa/gh-action-pypi-publish@v1.12.4
with:
password: ${{ secrets.PYPI_API_TOKEN }}
skip-existing: true
+212
View File
@@ -0,0 +1,212 @@
*.tmp
.vscode
__pycache__
test/
related-repos/
tmp_test/test.py
tmp_test/dynamic_hypergraph.pdf
tmp_test/hypergraph_learning_tutorial.py
tmp_test/visulation_test.py
hypergraph-bench/
# Created by https://www.toptal.com/developers/gitignore/api/python
# Edit at https://www.toptal.com/developers/gitignore?templates=python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# End of https://www.toptal.com/developers/gitignore/api/python
.idea/
# Visual Studio project files
x64/
.vs/
*.sln
*.vcxproj
*.vcxproj.filters
*.vcxproj.user
workspace/
.DS_Store# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
tmp_test/allset_test.py
tmp_test/
RandomNetwork.txt
TEST/
CON_TEST/
Comm_TEST/
+3
View File
@@ -0,0 +1,3 @@
[submodule "cpp_easygraph/pybind11"]
path = cpp_easygraph/pybind11
url = https://github.com/pybind/pybind11.git
+94
View File
@@ -0,0 +1,94 @@
ci:
autofix_prs: false
repos:
# - repo: https://github.com/pre-commit/pre-commit-hooks
# rev: v4.3.0
# hooks:
# - id: trailing-whitespace
# - id: end-of-file-fixer
# exclude: ^.*\.egg-info/
# - id: check-merge-conflict
# - id: check-case-conflict
# - id: check-json
# - id: check-toml
# - id: check-yaml
# - id: pretty-format-json
# args: [--autofix, --no-ensure-ascii, --no-sort-keys]
# - id: check-ast
# - id: debug-statements
# - id: check-docstring-first
# - repo: https://github.com/pre-commit/pygrep-hooks
# rev: v1.9.0
# hooks:
# - id: python-check-mock-methods
# - id: python-use-type-annotations
# - id: python-check-blanket-noqa
# - repo: https://github.com/asottile/yesqa
# rev: v1.3.0
# hooks:
# - id: yesqa
# additional_dependencies: &flake8_deps
# - flake8-annotations==2.9.0
# - flake8-broken-line==0.4.0
# - flake8-bugbear==22.4.25
# - flake8-comprehensions==3.10.0
# - flake8-eradicate==1.2.1
# - flake8-quotes==3.3.1
# - flake8-simplify==0.19.2
# - flake8-tidy-imports==4.8.0
# - flake8-type-checking==1.5.0
# - flake8-typing-imports==1.12.0
# - flake8-use-fstring==1.3
# - pep8-naming==0.12.1
# - repo: https://github.com/asottile/pyupgrade
# rev: v2.37.1
# hooks:
# - id: pyupgrade
# args: [--py36-plus]
# exclude: ^(install|get)-poetry.py$
# - repo: https://github.com/hadialqattan/pycln
# rev: v2.0.4
# hooks:
# - id: pycln
# args: [--all]
- repo: https://github.com/pycqa/isort
# rev: 5.10.1
# https://stackoverflow.com/questions/75269700/pre-commit-fails-to-install-isort-5-11-4-with-error-runtimeerror-the-poetry-co
rev: 5.12.0
hooks:
- id: isort
name: "isort (python)"
types: [python]
args: []
# args: [--add-import, from __future__ import annotations]
# exclude: |
# (?x)(
# ^(install|get)-poetry.py$
# | ^src/poetry/__init__.py$
# )
# - id: isort
# name: "isort (pyi)"
# types: [pyi]
# args: [--lines-after-imports, "-1"]
- repo: https://github.com/psf/black
rev: 22.6.0
hooks:
- id: black
# - repo: https://github.com/pycqa/flake8
# rev: 4.0.1
# hooks:
# - id: flake8
# additional_dependencies: *flake8_deps
- repo: https://github.com/pre-commit/pre-commit
rev: v2.20.0
hooks:
- id: validate_manifest
+19
View File
@@ -0,0 +1,19 @@
cmake_minimum_required(VERSION 3.23)
project(easygraph)
option(EASYGRAPH_ENABLE_OPENMP "Enable OpenMP acceleration (auto-detect)" ON)
option(EASYGRAPH_ENABLE_GPU "EASYGRAPH_ENABLE_GPU" OFF)
add_subdirectory(cpp_easygraph)
if (EASYGRAPH_ENABLE_GPU)
message("easygraph gpu module is enabled")
add_subdirectory(gpu_easygraph)
target_include_directories(cpp_easygraph
PRIVATE gpu_easygraph
)
else()
message("easygraph gpu module is disabled")
endif()
+253
View File
@@ -0,0 +1,253 @@
# Contributing to easygraph
First off, thanks for taking the time to contribute!
The following is a set of guidelines for contributing to easygraph on GitHub. These are mostly guidelines, not rules. Use your best judgement, and feel free to propose changes to this document in a pull request.
#### Table of contents
[How to contribute](#how-to-contribute)
* [Reporting bugs](#reporting-bugs)
* [Suggesting enhancements](#suggesting-enhancements)
* [Contributing to documentation](#contributing-to-documentation)
* [Contributing to code](#contributing-to-code)
* [Issue triage](#issue-triage)
* [Git workflow](#git-workflow)
## How to contribute
### Reporting bugs
This section guides you through submitting a bug report for easygraph.
Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports.
Before creating bug reports, please check [this list](#before-submitting-a-bug-report) to be sure that you need to create one. When you are creating a bug report, please include as many details as possible. Fill out the [required template](https://github.com/easy-graph/Easy-Graph/blob/master/.github/ISSUE_TEMPLATE/---bug-report.md), the information it asks helps the maintainers resolve the issue faster.
> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.
#### Before submitting a bug report
<!-- * **Check the [FAQs on the official website](https://python-easygraph.org/docs/faq)** for a list of common questions and problems. -->
* **Check that your issue does not already exist in the [issue tracker](https://github.com/easy-graph/Easy-Graph/issues)**.
#### How do I submit a bug report?
Bugs are tracked on the [official issue tracker](https://github.com/easy-graph/Easy-Graph/issues) where you can create a new one and provide the following information by filling in [the template](https://github.com/easy-graph/Easy-Graph/blob/master/.github/ISSUE_TEMPLATE/---bug-report.md).
Explain the problem and include additional details to help maintainers reproduce the problem:
* **Use a clear and descriptive title** for the issue to identify the problem.
* **Describe the exact steps which reproduce the problem** in as many details as possible.
<!-- * **Provide your pyproject.toml file** in a [Gist](https://gist.github.com) after removing potential private information (like private package repositories). -->
* **Provide specific examples to demonstrate the steps to reproduce the issue**. Include links to files or GitHub projects, or copy-paste-able snippets, which you use in those examples.
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
* **Explain which behavior you expected to see instead and why.**
<!-- * **If the problem is an unexpected error being raised**, execute the corresponding command in **debug** mode (the `-vvv` option). -->
Provide more context by answering these questions:
* **Did the problem start happening recently** (e.g. after updating to a new version of easygraph) or was this always a problem?
* If the problem started happening recently, **can you reproduce the problem in an older version of easygraph?** What's the most recent version in which the problem doesn't happen?
* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens.
Include details about your configuration and environment:
* **Which version of easygraph are you using?**
* **Which Python version easygraph has been installed for?**
* **What's the name and version of the OS you're using**?
### Suggesting enhancements
This section guides you through submitting an enhancement suggestion for easygraph, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions.
Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-an-enhancement-suggestion). Fill in [the template](https://github.com/easy-graph/Easy-Graph/blob/master/.github/ISSUE_TEMPLATE/---feature-request.md), including the steps that you imagine you would take if the feature you're requesting existed.
#### Before submitting an enhancement suggestion
<!-- * **Check the [FAQs on the official website](https://python-easygraph.org/docs/faq)** for a list of common questions and problems. -->
* **Check that your issue does not already exist in the [issue tracker](https://github.com/easy-graph/Easy-Graph/issues)**.
#### How do I submit an Enhancement suggestion?
Enhancement suggestions are tracked on the [official issue tracker](https://github.com/easy-graph/Easy-Graph/issues) where you can create a new one and provide the following information:
* **Use a clear and descriptive title** for the issue to identify the suggestion.
* **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
* **Provide specific examples to demonstrate the steps**..
* **Describe the current behavior** and **explain which behavior you expected to see instead** and why.
### Contributing to documentation
One of the simplest ways to get started contributing to a project is through improving documentation. easygraph is constantly evolving, this means that sometimes our documentation has gaps. You can help by
adding missing sections, editing the existing content so it is more accessible or creating new content (tutorials, FAQs, etc).
> **Note:** A great way to understand easygraph's design and how it all fits together, is to add FAQ entries for commonly
> asked questions. easygraph members usually mark issues with [candidate/faq](https://github.com/easy-graph/Easy-Graph/issues?q=is%3Aissue+label%3Acandidate%2Ffaq+) to indicate that the issue either contains a response
> that explains how something works or might benefit from an entry in the FAQ.
Issues pertaining to the documentation are usually marked with the [Documentation](https://github.com/easy-graph/Easy-Graph/labels/Documentation) label.
### Contributing to code
#### Picking an issue
> **Note:** If you are a first time contributor, and are looking for an issue to take on, you might want to look for [Good First Issue](https://github.com/easy-graph/Easy-Graph/issues?q=is%3Aopen+is%3Aissue+label%3A%22Good+First+Issue%22)
> labelled issues. We do our best to label such issues, however we might fall behind at times. So, ask us.
<!-- If you would like to take on an issue, feel free to comment on the issue tagging `@python-easygraph/triage`. We are more than happy to discuss solutions on the issue. If you would like help with navigating -->
the code base, join us on our [Discord Server](https://discord.gg/ppgcw2wUPg).
#### Local development
You will need easygraph to start contributing on the easygraph codebase. Refer to the [documentation](https://easy-graph.github.io/) to start using easygraph.
> **Note:** Local development of easygraph requires Python 3.8 or newer.
You will first need to clone the repository using `git` and place yourself in its directory:
```bash
git clone git@github.com:python-easygraph/easygraph.git
cd easygraph
```
> **Note:** We recommend that you use a personal [fork](https://docs.github.com/en/free-pro-team@latest/github/getting-started-with-github/fork-a-repo) for this step. If you are new to GitHub collaboration,
> you can refer to the [Forking Projects Guide](https://guides.github.com/activities/forking/).
Now, you will need to install the required dependency for easygraph and be sure that the current
tests are passing on your machine:
```bash
pytest
```
<!-- easygraph uses [mypy](https://github.com/python/mypy) for typechecking, and the CI
will fail if it finds any errors. To run mypy locally:
```bash
easygraph run mypy
``` -->
easygraph uses the [black](https://github.com/psf/black) coding style and you must ensure that your
code follows it. If not, the CI will fail and your Pull Request will not be merged.
Similarly, the import statements are sorted with [isort](https://github.com/timothycrosley/isort)
and special care must be taken to respect it. If you don't, the CI will fail as well.
To make sure that you don't accidentally commit code that does not follow the coding style, you can
install a [pre-commit](https://pre-commit.com/) hook that will check that everything is in order:
```bash
pre-commit install
```
You can also run it anytime using:
```bash
pre-commit run --all-files
```
Your code must always be accompanied by corresponding tests, if tests are not present your code
will not be merged.
#### Run the Tests Locally
- Install [tox](https://tox.readthedocs.io/) and [pyenv](https://github.com/pyenv/pyenv)
- Use pyenv to install python 3.6 to 3.10, and make sure that `python3.6`, `python3.7`, ..., `python3.10` is in your `$PATH`.
- Run `tox` to run the tests across python 3.6 to 3.10.
- To only run tox on python3.9 and 3.10, run `tox -e py39,py310`.
#### Pull requests
* Fill in [the required template](https://github.com/easy-graph/Easy-Graph/blob/master/.github/PULL_REQUEST_TEMPLATE.md)
* Be sure that your pull request contains tests that cover the changed or added code.
* If your changes warrant a documentation change, the pull request must also update the documentation.
> **Note:** Make sure your branch is [rebased](https://docs.github.com/en/free-pro-team@latest/github/using-git/about-git-rebase) against the latest main branch. A maintainer might ask you to ensure the branch is
> up-to-date prior to merging your Pull Request if changes have conflicts.
All pull requests, unless otherwise instructed, need to be first accepted into the main branch (`master`).
### Issue triage
<!-- > **Note:** If you have an issue that hasn't had any attention, you can ping us `@python-easygraph/triage` on the issue. Please, give us reasonable time to get to your issue first, spamming us with messages
> does not help anyone. -->
If you are helping with the triage of reported issues, this section provides some useful information to assist you in your contribution.
#### Triage steps
<!-- 1. If `pyproject.toml` is missing or `-vvv` debug logs (with stack trace) is not provided and required, request that the issue author provides it. -->
1. Attempt to reproduce the issue with the reported easygraph version or request further clarification from the issue author.
1. Ensure the issue is not already resolved. You can attempt to reproduce using the latest preview release and/or easygraph from the main branch.
1. If the issue cannot be reproduced,
1. clarify with the issue's author,
<!-- 1. close the issue or notify `@python-easygraph/triage`. -->
1. If the issue can be reproduced,
1. comment on the issue confirming so
<!-- 1. notify `@python-easygraph/triage`. -->
1. if possible, identify the root cause of the issue.
1. if interested, attempt to fix it via a pull request.
<!-- #### Multiple versions
Often times you would want to attempt to reproduce issues with multiple versions of `easygraph` at the same time. For these use cases, the [pipx project](https://pypa.github.io/pipx/) is useful.
You can set your environment up like so.
```sh
pipx install --suffix @1.0.10 'easygraph==1.0.10'
pipx install --suffix @1.1.0rc1 'easygraph==1.1.0rc1'
pipx install --suffix @master 'easygraph @ git+https://github.com/easy-graph/Easy-Graph'
```
> **Hint:** Do not forget to update your `easygraph@master` installation in sync with upstream.
For `@local` it is recommended that you do something similar to the following as editable installs are not supported for PEP 517 projects.
```sh
# note this will not work for Windows, and we assume you have already run `easygraph install`
cd /path/to/python-easygraph/easygraph
ln -sf $(easygraph run which easygraph) ~/.local/bin/easygraph@local
```
> **Hint:** This mechanism can also be used to test pull requests. -->
### Git Workflow
All development work is performed against easygraph's main branch (`master`). All changes are expected to be submitted and accepted to this
branch.
#### Release branch
When a release is ready, the following are required before a release is tagged.
1. A release branch with the prefix `release-`, eg: `release-1.1.0rc1`.
1. A pull request from the release branch to the main branch (`master`) if it's a minor or major release. Otherwise, to the bug fix branch (eg: `1.0`).
1. The pull request description MUST include the change log corresponding to the release (eg: [#2971](https://github.com/easy-graph/Easy-Graph/pull/2971)).
1. The pull request must contain a commit that updates [CHANGELOG.md](CHANGELOG.md) and bumps the project version (eg: [#2971](https://github.com/easy-graph/Easy-Graph/pull/2971/commits/824e7b79defca435cf1d765bb633030b71b9a780)).
1. The pull request must have the `Release` label specified.
<!-- Once the branch pull-request is ready and approved, a member of `@python-easygraph/core` will, -->
1. Tag the branch with the version identifier (eg: `1.1.0rc1`).
2. Merge the pull request once the release is created and assets are uploaded by the CI.
> **Note:** In this case, we prefer a merge commit instead of squash or rebase merge.
#### Bug fix branch
Once a minor version (eg: `1.1.0`) is released, a new branch for the minor version (eg: `1.1`) is created for the bug fix releases. Changes identified
or acknowledged by the easygraph team as requiring a bug fix can be submitted as a pull requests against this branch.
At the time of writing only issues meeting the following criteria may be accepted into a bug fix branch. Trivial fixes may be accepted on a
case-by-case basis.
1. The issue breaks a core functionality and/or is a critical regression.
1. The change set does not introduce a new feature or changes an existing functionality.
1. A new minor release is not expected within a reasonable time frame.
1. If the issue affects the next minor/major release, a corresponding fix has been accepted into the main branch.
> **Note:** This is subject to the interpretation of a maintainer within the context of the issue.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

+29
View File
@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2020, Mobile Systems and Networking Group, Fudan University
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+3
View File
@@ -0,0 +1,3 @@
include README.md
include CMakeLists.txt
recursive-include cpp_easygraph *
+13
View File
@@ -0,0 +1,13 @@
pre-commit:
pre-commit run --all-files
test:
tox
publish:
gh workflow run release-cibuildwheel.yaml
bump:
bump2version patch
.PHONY: *
+187
View File
@@ -0,0 +1,187 @@
EasyGraph
==================
Copyright (C) <2020-2026> by [DataNET Group, Fudan University](https://fudan-datanet.mysxl.cn/)
___________________________________________________________________________
[![PyPI Version][pypi-image]][pypi-url]
[![Python][python-image]][python-url]
[![License][license-image]][license-url]
[![Downloads][downloads-image]][downloads-url]
[pypi-image]: https://img.shields.io/pypi/v/Python-EasyGraph.svg?label=PyPI
[pypi-url]: https://pypi.org/project/Python-EasyGraph/
[python-image]: https://img.shields.io/pypi/pyversions/Python-EasyGraph.svg?label=Python
[python-url]: https://pypi.org/project/Python-EasyGraph/
[license-image]: https://img.shields.io/pypi/l/Python-EasyGraph?label=License
[license-url]: https://github.com/easy-graph/Easy-Graph/blob/master/LICENSE
[downloads-image]: https://img.shields.io/pepy/dt/python-easygraph?label=Downloads&labelColor=brightgreen&color=yellowgreen
[downloads-url]: https://pypi.org/project/Python-EasyGraph/
- **Documentation:** https://easy-graph.github.io/
- **Source Code:** https://github.com/easy-graph/Easy-Graph
- **Issue Tracker:** https://github.com/easy-graph/Easy-Graph/issues
- **PyPI Homepage:** https://pypi.org/project/Python-EasyGraph/
- **Youtube channel:** https://www.youtube.com/@python-easygraph
# Introduction
The framework of EasyGraph is composed of four components: **EasyGraph (Core)**, **EasyHypergraph**, **EGGPU**, and **EasyGNN**.
![Framework of EasyGraph.](EG_framework.png)
**EasyGraph** is an open-source network analysis library primarily written in Python. It supports both undirected and directed networks and accommodates various network data formats. EasyGraph includes a comprehensive suite of network analysis algorithms such as community detection, structural hole spanner detection, network embedding, and motif detection. Additionally, it optimizes performance by implementing key components in C++ and utilizing multiprocessing.
<!-- # New Features in Version 1.3
- **Support for more hypergraph metrics and algorithms.** Such as [hypercoreness](https://www.nature.com/articles/s41467-023-41887-2), [vector-centrality](https://www.sciencedirect.com/science/article/pii/S0960077922006075), [s-centrality](https://epjds.epj.org/articles/epjdata/abs/2020/01/13688_2020_Article_231/13688_2020_Article_231.html), and so on.
- **Support for more hypergraph datasets.** Static hypergraph datasets and dynamic datasets can be both loaded by calling the corresponding dataset name.
- **Support for more flexible dynamic hypergraph visualization.** Users can define dynamic hypergraphs and visualize the structure of the hypergraph at each timestamp.
- **Support for more efficient hypergraph computation and hypergraph learning.** Adoption of suitable storage structure and caching strategy for different metrics/hypergraph neural networks.
-->
👉 For more details, please refer to our [documentation](https://easy-graph.github.io/) page.
---
**EasyHypergraph** is a comprehensive, computation-effective, and storage-saving hypergraph computation tool designed not only for in-depth hypergraph analysis but also for the growing field of hypergraph learning.
It bridges the gap between EasyGraph and higher-order relationships. EasyHypergraph is developed as an integrated module within the EasyGraph framework, maintaining full compatibility with its core architecture.
👉 For more details, please refer to its [documentation](https://easy-graph.github.io/docs/hypergraph.html) page.
---
**EGGPU** is a high-performance GPU-accelerated network analysis library that supports essential functions such as betweenness centrality, k-core centrality, and single-source shortest path,as well as structural hole metrics like constraint. Built on top of the EasyGraph library, EGGPU features an efficient system architecture and native CUDA implementation, while providing a user-friendly Python API and significant speedups for large-scale network analysis.
👉 For more details, please refer to its [documentation](https://easy-graph.github.io/docs/eggpu.html) page.
# 📢 EasyGraph News
## 📣 Media & Press
- [08-09-2025] [EasyHypergraph: Fast, Efficient Higher-Order Network Analysis](https://scienmag.com/easyhypergraph-fast-efficient-higher-order-network-analysis/)
- [01-15-2025] [开放原子大赛OpenRank开源数字生态分析与应用创新大赛全国一等奖 (in Chinese)](https://mp.weixin.qq.com/s/e54JHaP2AAEUN3S8RZ-Y2g)
- [01-07-2025] [计算机科学技术学院教授陈阳入选“2024中国开源先锋33人” (in Chinese)](https://news.fudan.edu.cn/2025/0107/c2463a143932/page.htm)
- [12-04-2024] [国际开源基准委员会的"顶级开源证书" (in Chinese)](https://chenyang03.wordpress.com/wp-content/uploads/2025/07/image.png?w=1024)
- [10-16-2024] [2023年度上海开源创新卓越成果奖 (in Chinese)](https://mp.weixin.qq.com/s/kO6Dpyolf74dlDvKuoLlJA)
- [11-06-2023] [复旦大学陈阳Patterns:EasyGraph——面向多学科的高性能网络结构分析工具箱|Cell Press论文速递 (in Chinese)](https://mp.weixin.qq.com/s/f2LCyQv1dYuquM_EfGX6Ow?poc_token=HBV092ijI2L534IrD0Jl_fnf3VjhX8UudcPFLH6b)
- [11-04-2023] [EasyGraph:多功能、跨平台、高效率的跨学科网络分析库 (in Chinese)](https://swarma.org/?p=46252)
## 🚀 Releases & Milestones
- [06-07-2026] EasyGraph **v1.6.2** released (Community functions upgraded)
- [05-07-2026] EasyGraph **v1.6.1** released (Add OpenMP-powered path-based functions)
- [02-01-2026] EasyGraph **v1.6** released (OpenMP-powered functions for large network analysis)
- **[01-16-2026] 🎉 1M Downloads! Thanks to our amazing community!**
- [01-01-2026] EasyGraph **v1.5.3** released ([The Hypergraph Interchange Format (HIF) standard](https://github.com/HIF-org/HIF-standard))
- [11-23-2025] EasyGraph **v1.5.2** released (LS algorithm for effective community detection)
- [10-11-2025] EasyGraph **v1.5.1** released (Python 3.14 supported)
- [07-27-2025] EasyGraph **v1.5** released (This version integrates the HWNN model and supports 11 representative network datasets)
- **[06-29-2025] 🎉 800K+ Downloads!**
- [11-22-2024] EasyGraph **v1.4.1** released (Python 3.13 supported)
- [09-20-2024] EasyGraph **v1.4** released (GPU-powered functions for large network analysis)
- [05-27-2024] EasyGraph **v1.3** released (issues related to hypergraph analysis and visualization resolved)
- [04-09-2024] EasyGraph **v1.2** released (Python 3.12 supported)
- [02-05-2024] EasyGraph **v1.1** released (hypergraph analysis and learning for higher-order networks)
- [08-17-2023] EasyGraph **v1.0** released
- [07-22-2020] EasyGraph **first public release**
## 📈 Publications
- [05-30-2025] 🎉 Our paper "EasyHypergraph: an open-source software for fast and memory-saving analysis and learning of higher-order networks" was accepted by Humanities and Social Sciences Communications (Nature Portfolio)! [[PDF](https://www.nature.com/articles/s41599-025-05180-5)]
- [08-08-2023] 🎉 Our paper "EasyGraph: A Multifunctional, Cross-Platform, and Effective Library for Interdisciplinary Network Analysis" was accepted by Patterns (Cell Press)! [[PDF](https://www.sciencedirect.com/science/article/pii/S2666389923002180)]
# Stargazers
[![Stars][star-image]][star-url]
[star-image]:https://reporoster.com/stars/easy-graph/Easy-Graph
[star-url]: https://github.com/easy-graph/Easy-Graph/stargazers
# Install
## Supported Versions
``3.8 <= Python <= 3.14`` is required.
## Installation With pip
```
$ pip install --upgrade Python-EasyGraph
```
The conda package is no longer updated or maintained.
If you've previously installed EasyGraph with conda, please uninstall it with ``conda`` and reinstall with ``pip``.
## Build From Source
If prebuilt EasyGraph wheels are not supported for your platform (OS / CPU arch, check [here](https://pypi.org/simple/python-easygraph/)), or you want to have GPU-based functions enabled, you can build it locally.
### Prerequisites
- CMake >= 3.23
- A compiler that fully supports C++11
- CUDA Toolkit 11.8 or later would be preferred (If need GPUs enabled)
### Installation
#### On Linux
```
git clone --recursive https://github.com/easy-graph/Easy-Graph
export EASYGRAPH_ENABLE_GPU="TRUE" # for users who want to enable GPUs
pip install ./Easy-Graph
```
#### On Windows
```
% For Windows users who want to enable GPU-based functions, %
% you must execute the commands below in cmd but not PowerShell. %
git clone --recursive https://github.com/easy-graph/Easy-Graph
set EASYGRAPH_ENABLE_GPU=TRUE % for users who want to enable GPUs %
pip install ./Easy-Graph
```
#### On macOS
```
# Since macOS doesn't support CUDA, we can't have GPUs enabled on macOS
git clone --recursive https://github.com/easy-graph/Easy-Graph
pip install ./Easy-Graph
```
## Hint
EasyGraph uses 1.12.1 <= [PyTorch](https://pytorch.org/get-started/locally/) < 2.0 for machine learning functions.
Note that this does not prevent your from running non-machine learning functions normally, if there is no PyTorch in your environment.
But you will receive some warnings which remind you some unavailable modules when they depend on it.
# Simple Example
This example demonstrates the general usage of methods in EasyGraph.
```python
>>> import easygraph as eg
>>> G = eg.Graph()
>>> G.add_edges([(1,2), (2,3), (1,3), (3,4), (4,5), (3,5), (5,6)])
>>> eg.pagerank(G)
{1: 0.14272233049003707, 2: 0.14272233049003694, 3: 0.2685427766200994, 4: 0.14336430577918527, 5: 0.21634929087322705, 6: 0.0862989657474143}
```
This is a simple example for the detection of [structural hole spanners](https://en.wikipedia.org/wiki/Structural_holes)
using the [HIS](https://keg.cs.tsinghua.edu.cn/jietang/publications/WWW13-Lou&Tang-Structural-Hole-Information-Diffusion.pdf) algorithm.
```python
>>> import easygraph as eg
>>> G = eg.Graph()
>>> G.add_edges([(1,2), (2,3), (1,3), (3,4), (4,5), (3,5), (5,6)])
>>> _, _, H = eg.get_structural_holes_HIS(G, C=[frozenset([1,2,3]), frozenset([4,5,6])])
>>> H # The structural hole score of each node. Note that node `4` is regarded as the most possible structural hole spanner.
{1: {0: 0.703948974609375},
2: {0: 0.703948974609375},
3: {0: 1.2799804687499998},
4: {0: 1.519976806640625},
5: {0: 1.519976806640625},
6: {0: 0.83595703125}
}
```
# Citation
If you use EasyGraph in a scientific publication, we kindly request that you cite the following paper:
```
@article{gao2023easygraph,
title={{EasyGraph: A Multifunctional, Cross-Platform, and Effective Library for Interdisciplinary Network Analysis}},
author={Min Gao and Zheng Li and Ruichen Li and Chenhao Cui and Xinyuan Chen and Bodian Ye and Yupeng Li and Weiwei Gu and Qingyuan Gong and Xin Wang and Yang Chen},
year={2023},
journal={Patterns},
volume={4},
number={10},
pages={100839},
}
```
📢 If you notice anything unexpected, please open an issue and let us know. If you have any questions or require a specific feature, feel free to discuss them with us. We are motivated to constantly make EasyGraph even better and let more developers benefit!
+135
View File
@@ -0,0 +1,135 @@
EasyGraph
==================
Copyright (C) <2020-2024> by DataNET Group, Fudan University
.. image:: https://img.shields.io/pypi/v/Python-EasyGraph.svg?label=PyPI
:target: https://pypi.org/project/Python-EasyGraph/
.. image:: https://img.shields.io/pypi/pyversions/Python-EasyGraph.svg?label=Python
:target: https://pypi.org/project/Python-EasyGraph/
.. image:: https://img.shields.io/pypi/l/Python-EasyGraph?label=License
:target: https://github.com/easy-graph/Easy-Graph/blob/master/LICENSE
.. image:: https://static.pepy.tech/personalized-badge/python-easygraph?period=total&units=international_system&left_color=brightgreen&right_color=yellowgreen&left_text=Downloads
:target: https://pypi.org/project/Python-EasyGraph/
- **Documentation:** https://easy-graph.github.io/
- **Source Code:** https://github.com/easy-graph/Easy-Graph
- **Issue Tracker:** https://github.com/easy-graph/Easy-Graph/issues
- **PyPI Homepage:** https://pypi.org/project/Python-EasyGraph/
- **Youtube channel:** https://www.youtube.com/@python-easygraph
Introduction
------------
**EasyGraph** is an open-source network analysis library. It is mainly written in Python and supports analysis for undirected networks and directed networks. EasyGraph supports various formats of network data and covers a series of important network analysis algorithms for community detection, structural hole spanner detection, network embedding, and motif detection. Moreover, EasyGraph implements some key elements using C++ and introduces multiprocessing optimization to achieve better efficiency.
New Features in Version 1.1
------------
- **Support for more hypergraph metrics and algorithms.** Such as `hypercoreness <https://www.nature.com/articles/s41467-023-41887-2>`_, `vector-centrality <https://www.sciencedirect.com/science/article/pii/S0960077922006075>`_, `s-centrality <https://epjds.epj.org/articles/epjdata/abs/2020/01/13688_2020_Article_231/13688_2020_Article_231.html>`_, and so on.
- **Support for more hypergraph datasets.** `Static hypergraph datasets and dynamic datasets <https://easy-graph.github.io/docs/reference/easygraph.datasets.html>`_ can be both loaded by calling corresponding dataset name.
- **Support for more flexible dynamic hypergraph visualization.** Users can define dynamic hypergraphs and visualize the structure of the hypergraph at each timestamp.
- **Support for more efficient hypergraph computation and hypergraph learning** Adoption of suitable storage structure and caching strategy for different metrics/hypergraph neural networks.
If you need more details, please see our `documentation <https://easy-graph.github.io/>`_ of latest version.
News
----
- [02-05-2024] We release EasyGraph 1.1! This version features hypergraph analysis and learning for higher-order network modeling and representation.
- [08-17-2023] We release EasyGraph 1.0!
- [08-08-2023] Our paper "EasyGraph: A Multifunctional, Cross-Platform, and Effective Library for Interdisciplinary Network Analysis" has been accepted by Patterns!
Stargazers
----------
.. image:: https://reporoster.com/stars/easy-graph/Easy-Graph
:target: https://github.com/easy-graph/Easy-Graph/stargazers
:alt: Stargazers repo roster for @easy-graph/Easy-Graph
Install
-------
.. The current version on PyPI is outdated, we'll push the latest version as soon as we figure out how to integrate the C++ binding framework we use with our CI pipeline.
.. In the meantime, here's a work around you can try to install the latest version of easygraph on your machine:
- **Prerequisites**
``3.8 <= Python <= 3.11`` is required.
.. Installation with ``pip`` (outdated)
- **Installation with** ``pip``
.. code::
$ pip install --upgrade Python-EasyGraph
The conda package is no longer updated or maintained.
If you've installed EasyGraph this way before, please uninstall it with ``conda`` and install it with ``pip``.
If prebuilt EasyGraph wheels are not supported for your platform (OS / CPU arch, check `here <https://pypi.org/simple/python-easygraph/>`_), you can build it locally this way:
.. code:: bash
git clone https://github.com/easy-graph/Easy-Graph && cd Easy-Graph && git checkout pybind11
pip install pybind11
python3 setup.py build_ext
python3 setup.py install
- **Hint**
EasyGraph uses 1.12.1 <= `PyTorch <https://pytorch.org/get-started/locally/>`_ < 2.0 for machine
learning functions.
Note that this does not prevent your from running non-machine learning functions normally,
if there is no PyTorch in your environment.
But you will receive some warnings which remind you some unavailable modules when they depend on it.
Simple Example
--------------
This example shows the general usage of methods in EasyGraph.
.. code:: python
>>> import easygraph as eg
>>> G = eg.Graph()
>>> G.add_edges([(1,2), (2,3), (1,3), (3,4), (4,5), (3,5), (5,6)])
>>> eg.pagerank(G)
{1: 0.14272233049003707, 2: 0.14272233049003694, 3: 0.2685427766200994, 4: 0.14336430577918527, 5: 0.21634929087322705, 6: 0.0862989657474143}
This is a simple example for the detection of `structural hole spanners <https://en.wikipedia.org/wiki/Structural_holes>`_
using the `HIS <https://keg.cs.tsinghua.edu.cn/jietang/publications/WWW13-Lou&Tang-Structural-Hole-Information-Diffusion.pdf>`_ algorithm.
.. code:: python
>>> import easygraph as eg
>>> G = eg.Graph()
>>> G.add_edges([(1,2), (2,3), (1,3), (3,4), (4,5), (3,5), (5,6)])
>>> _, _, H = eg.get_structural_holes_HIS(G, C=[frozenset([1,2,3]), frozenset([4,5,6])])
>>> H # The structural hole score of each node. Note that node `4` is regarded as the most possible structural hole spanner.
{1: {0: 0.703948974609375},
2: {0: 0.703948974609375},
3: {0: 1.2799804687499998},
4: {0: 1.519976806640625},
5: {0: 1.519976806640625},
6: {0: 0.83595703125}
}
Citation
--------
If you use EasyGraph in a scientific publication, we would appreciate citations to the following paper:
.. code:: bash
@article{gao2023easygraph,
title={{EasyGraph: A Multifunctional, Cross-Platform, and Effective Library for Interdisciplinary Network Analysis}},
author={Min Gao and Zheng Li and Ruichen Li and Chenhao Cui and Xinyuan Chen and Bodian Ye and Yupeng Li and Weiwei Gu and Qingyuan Gong and Xin Wang and Yang Chen},
year={2023},
journal={Patterns},
volume={4},
number={10}
}
+7
View File
@@ -0,0 +1,7 @@
# WeHub 来源说明
- 原始项目:`easy-graph/Easy-Graph`
- 原始仓库:https://github.com/easy-graph/Easy-Graph
- 导入方式:上游默认分支的最新快照
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
+23
View File
@@ -0,0 +1,23 @@
[default]
# extend-ignore-identifiers-re = [
# # *sigh* this just isn't worth the cost of fixing
# "AttributeID.*Supress.*",
# ]
[default.extend-identifiers]
# *sigh* this just isn't worth the cost of fixing
# AttributeIDSupressMenu = "AttributeIDSupressMenu"
[default.extend-words]
# Don't correct the surname "Teh"
# teh = "teh"
2nd = "2nd"
l_2nd = 'l_2nd'
loss_2nd = 'loss_2nd'
nd = 'nd'
[files]
extend-exclude = [
"_typos.toml",
"easygraph/functions/graph_embedding/embedding-citeseer.ipynb",
]
+83
View File
@@ -0,0 +1,83 @@
cmake_minimum_required(VERSION 3.23)
project(cpp_easygraph)
set(CMAKE_CXX_STANDARD 11)
file(GLOB SOURCES
classes/*.cpp
common/*.cpp
functions/*/*.cpp
cpp_easygraph.cpp
)
add_subdirectory(pybind11)
option(EASYGRAPH_ENABLE_OPENMP "Enable OpenMP acceleration (auto-detect)" ON)
option(EASYGRAPH_ENABLE_GPU "EASYGRAPH_ENABLE_GPU" OFF)
if (EASYGRAPH_ENABLE_GPU)
pybind11_add_module(cpp_easygraph
${SOURCES}
$<TARGET_OBJECTS:gpu_easygraph>
)
set_property(TARGET cpp_easygraph PROPERTY CUDA_ARCHITECTURES all)
target_compile_definitions(cpp_easygraph
PRIVATE EASYGRAPH_ENABLE_GPU
)
target_link_libraries(cpp_easygraph
PRIVATE cudart_static
)
else()
pybind11_add_module(cpp_easygraph
${SOURCES}
)
endif()
if (EASYGRAPH_ENABLE_OPENMP)
if (APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
find_path(EG_LIBOMP_INCLUDE_DIR
NAMES omp.h
PATHS
/opt/homebrew/opt/libomp/include
/usr/local/opt/libomp/include
)
find_library(EG_LIBOMP_LIBRARY
NAMES omp libomp
PATHS
/opt/homebrew/opt/libomp/lib
/usr/local/opt/libomp/lib
)
if (EG_LIBOMP_INCLUDE_DIR AND EG_LIBOMP_LIBRARY)
message(STATUS "libomp found: ${EG_LIBOMP_LIBRARY}")
set(OpenMP_C_FLAGS "-Xpreprocessor -fopenmp" CACHE STRING "" FORCE)
set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp" CACHE STRING "" FORCE)
set(OpenMP_CXX_INCLUDE_DIR "${EG_LIBOMP_INCLUDE_DIR}" CACHE PATH "" FORCE)
set(OpenMP_omp_LIBRARY "${EG_LIBOMP_LIBRARY}" CACHE FILEPATH "" FORCE)
else()
message(STATUS
"libomp not found on macOS. OpenMP will be disabled.\n"
"To enable OpenMP, run: brew install libomp"
)
endif()
endif()
find_package(OpenMP QUIET)
endif()
if (OpenMP_CXX_FOUND)
message(STATUS "OpenMP found, enabling parallel acceleration.")
target_link_libraries(cpp_easygraph PRIVATE OpenMP::OpenMP_CXX)
target_compile_definitions(cpp_easygraph PRIVATE EASYGRAPH_USE_OPENMP=1)
else()
message(STATUS "OpenMP not found, building in single-thread mode.")
target_compile_definitions(cpp_easygraph PRIVATE EASYGRAPH_USE_OPENMP=0)
endif()
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include "graph.h"
#include "directed_graph.h"
#include "operation.h"
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../common/common.h"
struct COOGraph {
std::vector<int> row;
std::vector<int> col;
std::vector<double> unweighted_W;
std::unordered_map<std::string, std::shared_ptr<std::vector<double>>> W_map;
std::vector<node_t> nodes;
std::unordered_map<node_t, int> node2idx;
};
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../common/common.h"
struct CSRGraph {
std::vector<int> V;
std::vector<int> E;
std::vector<double> unweighted_W;
std::unordered_map<std::string, std::shared_ptr<std::vector<double>>> W_map;
std::vector<node_t> nodes;
std::unordered_map<node_t, int> node2idx;
};
+613
View File
@@ -0,0 +1,613 @@
#include "directed_graph.h"
#include "../common/utils.h"
DiGraph::DiGraph() : Graph() {
}
py::object DiGraph__init__(py::args args, py::kwargs kwargs) {
py::object self = args[0];
self.attr("__init__")();
DiGraph& self_ = self.cast<DiGraph&>();
py::dict graph_attr = kwargs;
self_.graph.attr("update")(graph_attr);
self_.nodes_cache = py::dict();
self_.adj_cache = py::dict();
return py::none();
}
py::object DiGraph_out_degree(py::object self, py::object weight) {
py::dict degree = py::dict();
py::list edges = self.attr("edges").cast<py::list>();
py::object u, v;
py::dict d;
for (int i = 0; i < py::len(edges); i++) {
py::tuple edge = edges[i].cast<py::tuple>();
u = edge[0];
v = edge[1];
d = edge[2].cast<py::dict>();
if (degree.contains(u)) {
degree[u] = py::object(degree[u]) + d.attr("get")(weight, 1);
} else {
degree[u] = d.attr("get")(weight, 1);
}
}
py::list nodes = py::list(self.attr("nodes"));
for (int i = 0; i < py::len(nodes); i++) {
py::object node = nodes[i];
if (!degree.contains(node)) {
degree[node] = 0;
}
}
return degree;
}
py::object DiGraph_in_degree(py::object self, py::object weight) {
py::dict degree = py::dict();
py::list edges = self.attr("edges").cast<py::list>();
py::object u, v;
py::dict d;
for (int i = 0; i < py::len(edges); i++) {
py::tuple edge = edges[i].cast<py::tuple>();
u = edge[0];
v = edge[1];
d = edge[2].cast<py::dict>();
if (degree.contains(v)) {
degree[v] = py::object(degree[v]) + d.attr("get")(weight, 1);
} else {
degree[v] = d.attr("get")(weight, 1);
}
}
py::list nodes = py::list(self.attr("nodes"));
for (int i = 0; i < py::len(nodes); i++) {
py::object node = nodes[i];
if (!degree.contains(node)) {
degree[node] = 0;
}
}
return degree;
}
py::object DiGraph_degree(py::object self, py::object weight) {
py::dict degree = py::dict();
py::dict out_degree = self.attr("out_degree")(weight).cast<py::dict>();
py::dict in_degree = self.attr("in_degree")(weight).cast<py::dict>();
py::list nodes = py::list(self.attr("nodes"));
for (int i = 0; i < py::len(nodes); i++) {
py::object u = nodes[i];
degree[u] = out_degree[u] + in_degree[u];
}
return degree;
}
py::object DiGraph_size(py::object self, py::object weight) {
py::dict out_degree = self.attr("out_degree")(weight).cast<py::dict>();
py::object s = py_sum(out_degree.attr("values")());
return (weight.is_none()) ? py::int_(s) : s;
}
py::object DiGraph_number_of_edges(py::object self, py::object u, py::object v) {
if (u.is_none()) {
return self.attr("size")();
}
Graph& G = self.cast<Graph&>();
node_t u_id = G.node_to_id[u].cast<node_t>();
node_t v_id = G.node_to_id.attr("get")(v, -1).cast<node_t>();
return py::cast(int(v_id != -1 && G.adj[u_id].count(v_id)));
}
py::object DiGraph_neighbors(py::object self, py::object node) {
Graph& self_ = self.cast<Graph&>();
if (self_.node_to_id.contains(node)) {
return self.attr("adj")[node].attr("__iter__")();
} else {
PyErr_Format(PyExc_KeyError, "No node %R", node.ptr());
return py::none();
}
}
py::object DiGraph_predecessors(py::object self, py::object node) {
DiGraph& self_ = self.cast<DiGraph&>();
adj_dict_factory& pred = self_.pred;
node_t node_id = self_.node_to_id[node].cast<node_t>();
if (pred.find(node_id) != pred.end()) {
adj_attr_dict_factory node_pred_dict = pred[node_id];
py::dict node_pred = py::dict();
for (adj_attr_dict_factory::iterator i = node_pred_dict.begin();
i != node_pred_dict.end(); i++) {
edge_attr_dict_factory edge_attr_dict = i->second;
node_pred[self_.id_to_node[py::cast(i->first)]] = attr_to_dict(edge_attr_dict);
}
return node_pred.attr("__iter__")();
} else {
PyErr_Format(PyExc_KeyError, "No node %R", node.ptr());
return py::none();
}
}
node_t DiGraph_add_one_node(DiGraph& self, py::object one_node_for_adding,
py::object node_attr = py::dict()) {
node_t id;
if (self.node_to_id.contains(one_node_for_adding)) {
id = self.node_to_id[one_node_for_adding].cast<node_t>();
} else {
id = ++(self.id);
self.id_to_node[py::cast(id)] = one_node_for_adding;
self.node_to_id[one_node_for_adding] = id;
}
py::list items = py::list(node_attr.attr("items")());
self.node[id] = node_attr_dict_factory();
self.adj[id] = adj_attr_dict_factory();
self.pred[id] = adj_attr_dict_factory();
for (int i = 0; i < len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
self.node[id].insert(std::make_pair(weight_key, value));
}
return id;
}
py::object DiGraph_add_node(py::args args, py::kwargs kwargs) {
DiGraph& self = args[0].cast<DiGraph&>();
self.dirty_nodes = true;
self.dirty_adj = true;
py::object one_node_for_adding = args[1];
py::dict node_attr = kwargs;
DiGraph_add_one_node(self, one_node_for_adding, node_attr);
return py::none();
}
py::object DiGraph_add_nodes(DiGraph& self, py::list nodes_for_adding, py::list nodes_attr) {
self.dirty_nodes = true;
self.dirty_adj = true;
if (py::len(nodes_attr) != 0) {
if (py::len(nodes_for_adding) != py::len(nodes_attr)) {
PyErr_Format(PyExc_AssertionError, "Nodes and Attributes lists must have same length.");
return py::none();
}
}
for (int i = 0; i < py::len(nodes_for_adding); i++) {
py::object one_node_for_adding = nodes_for_adding[i];
py::dict node_attr;
if (py::len(nodes_attr)) {
node_attr = nodes_attr[i].cast<py::dict>();
} else {
node_attr = py::dict();
}
DiGraph_add_one_node(self, one_node_for_adding, node_attr);
}
return py::none();
}
py::object DiGraph_add_nodes_from(py::args args, py::kwargs kwargs) {
DiGraph& self = args[0].cast<DiGraph&>();
self.dirty_nodes = true;
self.dirty_adj = true;
py::list nodes_for_adding = py::list(args[1]);
for (int i = 0; i < py::len(nodes_for_adding); i++) {
bool newnode;
py::dict attr = kwargs;
py::dict newdict, ndict;
py::object n = nodes_for_adding[i];
try {
newnode = !self.node_to_id.contains(n);
newdict = attr;
} catch (const py::error_already_set&) {
PyObject *type, *value, *traceback;
PyErr_Fetch(&type, &value, &traceback);
if (PyErr_GivenExceptionMatches(PyExc_TypeError, type)) {
py::tuple n_pair = n.cast<py::tuple>();
n = n_pair[0];
ndict = n_pair[1].cast<py::dict>();
newnode = !self.node_to_id.contains(n);
newdict = attr.attr("copy")();
newdict.attr("update")(ndict);
} else {
PyErr_Restore(type, value, traceback);
return py::none();
}
}
if (newnode) {
if (n.is_none()) {
PyErr_Format(PyExc_ValueError, "None cannot be a node");
return py::none();
}
DiGraph_add_one_node(self, n);
}
node_t id = self.node_to_id[n].cast<node_t>();
py::list items = py::list(newdict.attr("items")());
for (int i = 0; i < len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
self.node[id].insert(std::make_pair(weight_key, value));
}
}
return py::none();
}
py::object DiGraph_remove_node(DiGraph& self, py::object node_to_remove) {
self.dirty_nodes = true;
self.dirty_adj = true;
if (!self.node_to_id.contains(node_to_remove)) {
PyErr_Format(PyExc_KeyError, "No node %R in graph.", node_to_remove.ptr());
return py::none();
}
node_t node_to_remove_id = self.node_to_id[node_to_remove].cast<node_t>();
adj_attr_dict_factory succs = self.adj[node_to_remove_id];
adj_attr_dict_factory preds = self.pred[node_to_remove_id];
self.node.erase(node_to_remove_id);
for (adj_attr_dict_factory::iterator i = succs.begin(); i != succs.end(); i++) {
self.pred[i->first].erase(node_to_remove_id);
}
for (adj_attr_dict_factory::iterator i = preds.begin(); i != preds.end(); i++) {
self.adj[i->first].erase(node_to_remove_id);
}
self.adj.erase(node_to_remove_id);
self.pred.erase(node_to_remove_id);
self.node_to_id.attr("pop")(node_to_remove);
self.id_to_node.attr("pop")(node_to_remove_id);
return py::none();
}
py::object DiGraph_remove_nodes(py::object self, py::list nodes_to_remove) {
DiGraph& self_ = self.cast<DiGraph&>();
self_.dirty_nodes = true;
self_.dirty_adj = true;
for (int i = 0; i < py::len(nodes_to_remove); i++) {
py::object node_to_remove = nodes_to_remove[i];
if (!self_.node_to_id.contains(node_to_remove)) {
PyErr_Format(PyExc_KeyError, "No node %R in graph.", node_to_remove.ptr());
return py::none();
}
}
for (int i = 0; i < py::len(nodes_to_remove); i++) {
py::object node_to_remove = nodes_to_remove[i];
self.attr("remove_node")(node_to_remove);
}
return py::none();
}
void DiGraph_add_one_edge(DiGraph& self, py::object u_of_edge,
py::object v_of_edge, py::object edge_attr) {
node_t u, v;
if (!self.node_to_id.contains(u_of_edge)) {
u = DiGraph_add_one_node(self, u_of_edge);
} else {
u = self.node_to_id[u_of_edge].cast<node_t>();
}
if (!self.node_to_id.contains(v_of_edge)) {
v = DiGraph_add_one_node(self, v_of_edge);
} else {
v = self.node_to_id[v_of_edge].cast<node_t>();
}
py::list items = py::list(edge_attr.attr("items")());
self.adj[u][v] = node_attr_dict_factory();
self.pred[v][u] = edge_attr_dict_factory();
for (int i = 0; i < len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
self.adj[u][v].insert(std::make_pair(weight_key, value));
self.pred[v][u].insert(std::make_pair(weight_key, value));
}
}
py::object DiGraph_add_edge(py::args args, py::kwargs kwargs) {
DiGraph& self = args[0].cast<DiGraph&>();
self.dirty_nodes = true;
self.dirty_adj = true;
py::object u_of_edge = args[1], v_of_edge = args[2];
py::dict edge_attr = kwargs;
DiGraph_add_one_edge(self, u_of_edge, v_of_edge, edge_attr);
return py::none();
}
py::object DiGraph_add_edges(DiGraph& self, py::list edges_for_adding, py::list edges_attr) {
self.dirty_nodes = true;
self.dirty_adj = true;
if (py::len(edges_attr) != 0) {
if (py::len(edges_for_adding) != py::len(edges_attr)) {
PyErr_Format(PyExc_AssertionError, "Edges and Attributes lists must have same length.");
return py::none();
}
}
for (int i = 0; i < py::len(edges_for_adding); i++) {
py::tuple one_edge_for_adding = edges_for_adding[i].cast<py::tuple>();
py::dict edge_attr;
if (py::len(edges_attr)) {
edge_attr = edges_attr[i].cast<py::dict>();
} else {
edge_attr = py::dict();
}
DiGraph_add_one_edge(self, one_edge_for_adding[0], one_edge_for_adding[1], edge_attr);
}
return py::none();
}
py::object DiGraph_add_edges_from(py::args args, py::kwargs attr) {
DiGraph& self = args[0].cast<DiGraph&>();
self.dirty_nodes = true;
self.dirty_adj = true;
py::list ebunch_to_add = py::list(args[1]);
for (int i = 0; i < len(ebunch_to_add); i++) {
py::list e = py::list(ebunch_to_add[i]);
py::object u, v;
py::dict dd;
switch (len(e)) {
case 2: {
u = e[0];
v = e[1];
break;
}
case 3: {
u = e[0];
v = e[1];
dd = e[2].cast<py::dict>();
break;
}
default: {
PyErr_Format(PyExc_ValueError, "Edge tuple %R must be a 2 - tuple or 3 - tuple.", e.ptr());
return py::none();
}
}
node_t u_id, v_id;
if (!self.node_to_id.contains(u)) {
if (u.is_none()) {
PyErr_Format(PyExc_ValueError, "None cannot be a node");
return py::none();
}
u_id = DiGraph_add_one_node(self, u);
} else {
u_id = self.node_to_id[u].cast<node_t>();
}
if (!self.node_to_id.contains(v)) {
if (v.is_none()) {
PyErr_Format(PyExc_ValueError, "None cannot be a node");
return py::none();
}
v_id = DiGraph_add_one_node(self, v);
} else {
v_id = self.node_to_id[v].cast<node_t>();
}
auto datadict = self.adj[u_id].count(v_id) ? self.adj[u_id][v_id] : node_attr_dict_factory();
py::list items = py::list(attr.attr("items")());
items.attr("extend")(py::list(dd.attr("items")()));
for (int j = 0; j < py::len(items); j++) {
py::tuple kv = items[j].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
datadict.insert(std::make_pair(weight_key, value));
}
// Warning: in Graph.py the edge attr is directed assigned by the dict extended from the original attr
self.adj[u_id][v_id].insert(datadict.begin(), datadict.end());
}
return py::none();
}
py::object DiGraph_add_edges_from_file(DiGraph& self, py::str file, py::object weighted, py::object is_transform) {
self.dirty_nodes = true;
self.dirty_adj = true;
bool _is_transform = is_transform.cast<bool>();
struct commactype : std::ctype<char> {
commactype() : std::ctype<char>(get_table()) {}
std::ctype_base::mask const* get_table() {
std::ctype_base::mask* rc = 0;
if (rc == 0) {
rc = new std::ctype_base::mask[std::ctype<char>::table_size];
std::fill_n(rc, std::ctype<char>::table_size, std::ctype_base::mask());
rc[','] = std::ctype_base::space;
rc[' '] = std::ctype_base::space;
rc['\t'] = std::ctype_base::space;
rc['\n'] = std::ctype_base::space;
rc['\r'] = std::ctype_base::space;
}
return rc;
}
};
std::ios::sync_with_stdio(0);
std::string file_path = file.cast<std::string>();
std::ifstream in;
in.open(file_path);
if (!in.is_open()) {
PyErr_Format(PyExc_FileNotFoundError, "Please check the file and make sure the path only contains English");
return py::none();
}
in.imbue(std::locale(std::locale(), new commactype));
std::string data, key("weight");
std::string su, sv;
weight_t weight;
while (in >> su >> sv) {
py::str pu(su), pv(sv);
node_t u, v;
if (!self.node_to_id.contains(pu)) {
u = DiGraph_add_one_node(self, pu);
} else {
u = self.node_to_id[pu].cast<node_t>();
}
if (!self.node_to_id.contains(pv)) {
v = DiGraph_add_one_node(self, pv);
} else {
v = self.node_to_id[pv].cast<node_t>();
}
if (weighted.cast<bool>()) {
in >> weight;
self.adj[u][v][key] = weight;
self.pred[v][u][key] = weight;
} else {
if (!self.adj[u].count(v)) {
self.adj[u][v] = node_attr_dict_factory();
self.pred[v][u] = node_attr_dict_factory();
}
}
}
if(_is_transform){
Graph_L g_l = graph_to_linkgraph(self,true, key, true, false);
self.linkgraph_structure = g_l;
self.linkgraph_dirty = false;
}
in.close();
return py::none();
}
py::object DiGraph_add_weighted_edge(DiGraph& self, py::object u_of_edge, py::object v_of_edge, weight_t weight) {
self.dirty_nodes = true;
self.dirty_adj = true;
py::dict edge_attr;
edge_attr["weight"] = weight;
DiGraph_add_one_edge(self, u_of_edge, v_of_edge, edge_attr);
return py::none();
}
py::object DiGraph_remove_edge(DiGraph& self, py::object u, py::object v) {
self.dirty_nodes = true;
self.dirty_adj = true;
if (self.node_to_id.contains(u) && self.node_to_id.contains(v)) {
node_t u_id = self.node_to_id[u].cast<node_t>();
node_t v_id = self.node_to_id[v].cast<node_t>();
auto& u_neighbors_info = self.adj[u_id];
if (u_neighbors_info.find(v_id) != u_neighbors_info.end()) {
u_neighbors_info.erase(v_id);
auto& v_predecessors_info = self.pred[v_id];
v_predecessors_info.erase(u_id);
return py::none();
}
}
PyErr_Format(PyExc_KeyError, "No edge %R-%R in graph.", u.ptr(), v.ptr());
return py::none();
}
py::object DiGraph_remove_edges(py::object self, py::list edges_to_remove) {
DiGraph& self_ = self.cast<DiGraph&>();
for (int i = 0; i < py::len(edges_to_remove); i++) {
py::tuple edge = edges_to_remove[i].cast<py::tuple>();
py::object u = edge[0], v = edge[1];
self.attr("remove_edge")(u, v);
}
self_.dirty_nodes = true;
self_.dirty_adj = true;
return py::none();
}
py::object DiGraph_remove_edges_from(py::object self, py::list ebunch) {
DiGraph& self_ = self.cast<DiGraph&>();
for (int i = 0; i < py::len(ebunch); i++) {
py::tuple edge = ebunch[i].cast<py::tuple>();
node_t u_id = edge[0].cast<node_t>();
node_t v_id = edge[1].cast<node_t>();
if (self_.adj[u_id].find(v_id) != self_.adj[u_id].end() && self_.adj[v_id].find(u_id) != self_.adj[v_id].end()) {
self_.adj[u_id].erase(v_id);
self_.pred[v_id].erase(u_id);
}
}
return py::none();
}
py::object DiGraph_nodes_subgraph(py::object self, py::list from_nodes) {
py::object G = self.attr("__class__")();
Graph& self_ = self.cast<Graph&>();
DiGraph& G_ = G.cast<DiGraph&>();
G_.graph.attr("update")(self_.graph);
py::object nodes = self.attr("nodes");
py::object adj = self.attr("adj");
for (int i = 0; i < py::len(from_nodes); i++) {
py::object node = from_nodes[i];
if (self_.node_to_id.contains(node)) {
py::object node_attr = nodes[node];
DiGraph_add_one_node(G_, node, node_attr);
}
py::object out_edges = adj[node];
py::list edge_items = py::list(out_edges.attr("items")());
for (int j = 0; j < py::len(edge_items); j++) {
py::tuple item = edge_items[j].cast<py::tuple>();
py::object v = item[0];
py::object edge_attr = item[1];
if (from_nodes.contains(v)) {
DiGraph_add_one_edge(G_, node, v, edge_attr);
}
}
}
return G;
}
py::object DiGraph_generate_linkgraph(py::object self, py::object weight){
DiGraph& G_ = self.cast<DiGraph&>();
std::string w = weight_to_string(weight);
Graph_L g_l = graph_to_linkgraph(G_, true, w, true, false);
G_.linkgraph_dirty = false;
G_.linkgraph_structure = g_l;
return py::none();
}
py::object DiGraph_copy(py::object self) {
DiGraph& self_ = self.cast<DiGraph&>();
py::object G = self.attr("__class__")();
DiGraph& G_ = G.cast<DiGraph&>();
G_.graph.attr("update")(self_.graph);
G_.id_to_node.attr("update")(self_.id_to_node);
G_.node_to_id.attr("update")(self_.node_to_id);
G_.node = self_.node;
G_.adj = self_.adj;
G_.pred = self_.pred;
return py::object(G);
}
py::object DiGraph_is_directed(py::object self) {
return py::cast(true);
}
py::object DiGraph_py(py::object self) {
py::object G = py::module_::import("easygraph").attr("DiGraph")();
G.attr("graph").attr("update")(self.attr("graph"));
G.attr("adj").attr("update")(self.attr("adj"));
G.attr("nodes").attr("update")(self.attr("nodes"));
G.attr("pred").attr("update")(self.attr("pred"));
// G.attr("succ").attr("update")(self.attr("succ"));
return G;
}
py::object DiGraph::get_pred() {
adj_dict_factory pred = this->pred;
py::dict predecessors = py::dict();
for (const auto& ego_edges : this->pred) {
node_t start_point = ego_edges.first;
py::dict ego_edges_dict = py::dict();
for (const auto& edge_info : ego_edges.second) {
node_t end_point = edge_info.first;
const auto& edge_attr = edge_info.second;
ego_edges_dict[this->id_to_node[py::cast(end_point)]] = attr_to_dict(edge_attr);
}
predecessors[this->id_to_node[py::cast(start_point)]] = ego_edges_dict;
}
return predecessors;
}
py::object DiGraph::get_edges() {
py::list edges = py::list();
std::set<std::pair<node_t, node_t> > seen;
for (const auto& ego_edges : this->adj) {
node_t u = ego_edges.first;
for (const auto& edge_info : ego_edges.second) {
node_t v = edge_info.first;
const auto& edge_attr = edge_info.second;
if (seen.find(std::make_pair(u, v)) == seen.end()) {
seen.insert(std::make_pair(u, v));
edges.append(py::make_tuple(this->id_to_node[py::cast(u)], this->id_to_node[py::cast(v)],
attr_to_dict(edge_attr)));
}
}
}
return edges;
}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include "../common/common.h"
#include "graph.h"
struct DiGraph : public Graph {
DiGraph();
py::object get_edges();
py::object get_pred();
adj_dict_factory pred;
};
py::object DiGraph__init__(py::args args, py::kwargs kwargs);
py::object DiGraph_out_degree(py::object self, py::object weight);
py::object DiGraph_in_degree(py::object self, py::object weight);
py::object DiGraph_degree(py::object self, py::object weight);
py::object DiGraph_size(py::object self, py::object weight);
py::object DiGraph_number_of_edges(py::object self, py::object u, py::object v);
py::object DiGraph_predecessors(py::object self, py::object node);
py::object DiGraph_add_node(py::args args, py::kwargs kwargs);
py::object DiGraph_add_nodes(DiGraph& self, py::list nodes_for_adding, py::list nodes_attr);
py::object DiGraph_add_nodes_from(py::args args, py::kwargs kwargs);
py::object DiGraph_remove_node(DiGraph& self, py::object node_to_remove);
py::object DiGraph_remove_nodes(py::object self, py::list nodes_to_remove);
py::object DiGraph_add_edge(py::args args, py::kwargs kwargs);
py::object DiGraph_add_edges(DiGraph& self, py::list edges_for_adding, py::list edges_attr);
py::object DiGraph_add_edges_from_file(DiGraph& self, py::str file, py::object weighted, py::object is_transform);
py::object DiGraph_add_edges_from(py::args args, py::kwargs attr);
py::object DiGraph_remove_edge(DiGraph& self, py::object u, py::object v);
py::object DiGraph_remove_edges(py::object self, py::list edges_to_remove);
py::object DiGraph_remove_edges_from(py::object self, py::list ebunch);
py::object DiGraph_add_weighted_edge(DiGraph& self, py::object u_of_edge, py::object v_of_edge, weight_t weight);
py::object DiGraph_nodes_subgraph(py::object self, py::list from_nodes);
py::object DiGraph_is_directed(py::object self);
py::object DiGraph_py(py::object self);
py::object DiGraph_generate_linkgraph(py::object self, py::object weight);
+990
View File
@@ -0,0 +1,990 @@
#include "graph.h"
#include "linkgraph.h"
#include "../common/utils.h"
Graph::Graph() {
this->id = 0;
this->dirty_nodes = true;
this->dirty_adj = true;
this->linkgraph_dirty = true;
this->csr_graph = nullptr;
this->node_to_id = py::dict();
this->id_to_node = py::dict();
this->graph = py::dict();
this->nodes_cache = py::dict();
this->adj_cache = py::dict();
this->coo_graph = nullptr;
}
py::object Graph__init__(py::args args, py::kwargs kwargs) {
py::object self = args[0];
self.attr("__init__")();
Graph& self_ = self.cast<Graph&>();
py::dict graph_attr = kwargs;
self_.graph.attr("update")(graph_attr);
self_.nodes_cache = py::dict();
self_.adj_cache = py::dict();
return py::none();
}
py::object Graph__iter__(py::object self) {
return self.attr("nodes").attr("__iter__")();
}
py::object Graph__len__(py::object self) {
Graph& self_ = self.cast<Graph&>();
return py::cast(py::len(self_.node_to_id));
}
py::object Graph__contains__(py::object self, py::object node) {
Graph& self_ = self.cast<Graph&>();
try {
return py::cast(self_.node_to_id.contains(node));
} catch (const py::error_already_set&) {
PyObject *type, *value, *traceback;
PyErr_Fetch(&type, &value, &traceback);
if (PyErr_GivenExceptionMatches(PyExc_TypeError, type)) {
return py::cast(false);
} else {
PyErr_Restore(type, value, traceback);
return py::none();
}
}
}
py::object Graph__getitem__(py::object self, py::object node) {
return self.attr("adj")[node];
}
node_t _add_one_node(Graph& self, py::object one_node_for_adding, py::object node_attr = py::dict()) {
node_t id;
if (self.node_to_id.contains(one_node_for_adding)) {
id = self.node_to_id[one_node_for_adding].cast<node_t>();
} else {
id = ++(self.id);
self.id_to_node[py::cast(id)] = one_node_for_adding;
self.node_to_id[one_node_for_adding] = id;
}
py::list items = py::list(node_attr.attr("items")());
self.node[id] = node_attr_dict_factory();
self.adj[id] = adj_attr_dict_factory();
for (int i = 0; i < len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
self.node[id].insert(std::make_pair(weight_key, value));
}
return id;
}
py::object Graph_add_node(py::args args, py::kwargs kwargs) {
Graph& self = args[0].cast<Graph&>();
self.drop_cache();
py::object one_node_for_adding = args[1];
py::dict node_attr = kwargs;
_add_one_node(self, one_node_for_adding, node_attr);
return py::none();
}
py::object Graph_add_nodes(Graph& self, py::list nodes_for_adding, py::list nodes_attr) {
self.drop_cache();
if (py::len(nodes_attr) != 0) {
if (py::len(nodes_for_adding) != py::len(nodes_attr)) {
PyErr_Format(PyExc_AssertionError, "Nodes and Attributes lists must have same length.");
return py::none();
}
}
for (int i = 0; i < py::len(nodes_for_adding); i++) {
py::object one_node_for_adding = nodes_for_adding[i];
py::dict node_attr;
if (py::len(nodes_attr)) {
node_attr = nodes_attr[i].cast<py::dict>();
} else {
node_attr = py::dict();
}
_add_one_node(self, one_node_for_adding, node_attr);
}
return py::none();
}
py::object Graph_add_nodes_from(py::args args, py::kwargs kwargs) {
Graph& self = args[0].cast<Graph&>();
self.drop_cache();
py::list nodes_for_adding = py::list(args[1]);
for (int i = 0; i < py::len(nodes_for_adding); i++) {
bool newnode;
py::dict attr = kwargs;
py::dict newdict, ndict;
py::object n = nodes_for_adding[i];
try {
newnode = !self.node_to_id.contains(n);
newdict = attr;
}
catch (const py::error_already_set&) {
PyObject *type, *value, *traceback;
PyErr_Fetch(&type, &value, &traceback);
if (PyErr_GivenExceptionMatches(PyExc_TypeError, type)) {
py::tuple n_pair = n.cast<py::tuple>();
n = n_pair[0];
ndict = n_pair[1].cast<py::dict>();
newnode = !self.node_to_id.contains(n);
newdict = attr.attr("copy")();
newdict.attr("update")(ndict);
} else {
PyErr_Restore(type, value, traceback);
return py::none();
}
}
if (newnode) {
if (n.is_none()) {
PyErr_Format(PyExc_ValueError, "None cannot be a node");
return py::none();
}
_add_one_node(self, n);
}
node_t id = self.node_to_id[n].cast<node_t>();
for (auto item : newdict) {
std::string weight_key = weight_to_string(item.first.cast<py::object>());
weight_t value = item.second.cast<weight_t>();
self.node[id].insert(std::make_pair(weight_key, value));
}
}
return py::none();
}
py::object Graph_remove_node(Graph& self, py::object node_to_remove) {
self.drop_cache();
if (!self.node_to_id.contains(node_to_remove)) {
PyErr_Format(PyExc_KeyError, "No node %R in graph.", node_to_remove.ptr());
return py::none();
}
node_t node_id = self.node_to_id[node_to_remove].cast<node_t>();
for (const auto& neighbor_info : self.adj[node_id]) {
node_t neighbor_id = neighbor_info.first;
self.adj[neighbor_id].erase(node_id);
}
self.adj.erase(node_id);
self.node.erase(node_id);
self.node_to_id.attr("pop")(node_to_remove);
self.id_to_node.attr("pop")(node_id);
return py::none();
}
py::object Graph_remove_nodes(py::object self, py::list nodes_to_remove) {
Graph& self_ = self.cast<Graph&>();
self_.drop_cache();
for (int i = 0; i < py::len(nodes_to_remove); i++) {
py::object node_to_remove = nodes_to_remove[i];
if (!self_.node_to_id.contains(node_to_remove)) {
PyErr_Format(PyExc_KeyError, "No node %R in graph.", node_to_remove.ptr());
return py::none();
}
}
for (int i = 0; i < py::len(nodes_to_remove); i++) {
py::object node_to_remove = nodes_to_remove[i];
self.attr("remove_node")(node_to_remove);
}
return py::none();
}
py::object Graph_number_of_nodes(Graph& self) {
return py::cast(int(self.node.size()));
}
py::object Graph_has_node(Graph& self, py::object node) {
return py::cast(self.node_to_id.contains(node));
}
py::object Graph_nbunch_iter(py::object self, py::object nbunch) {
py::object bunch = py::none();
if (nbunch.is_none()) {
bunch = self.attr("adj").attr("__iter__")();
} else if (self.contains(nbunch)) {
py::list nbunch_wrapper = py::list();
nbunch_wrapper.append(nbunch);
bunch = nbunch_wrapper.attr("__iter__")();
} else {
py::list nbunch_list = py::list(nbunch), nodes_list = py::list();
for (int i = 0; i < py::len(nbunch_list); i++) {
py::object n = nbunch_list[i];
if (self.contains(n)) {
nodes_list.append(n);
}
}
bunch = nbunch_list.attr("__iter__")();
}
return bunch;
}
void _add_one_edge(Graph& self, py::object u_of_edge, py::object v_of_edge, py::object edge_attr) {
node_t u, v;
if (!self.node_to_id.contains(u_of_edge)) {
u = _add_one_node(self, u_of_edge);
} else {
u = self.node_to_id[u_of_edge].cast<node_t>();
}
if (!self.node_to_id.contains(v_of_edge)) {
v = _add_one_node(self, v_of_edge);
} else {
v = self.node_to_id[v_of_edge].cast<node_t>();
}
py::list items = py::list(edge_attr.attr("items")());
self.adj[u][v] = node_attr_dict_factory();
self.adj[v][u] = node_attr_dict_factory();
for (int i = 0; i < len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
self.adj[u][v].insert(std::make_pair(weight_key, value));
self.adj[v][u].insert(std::make_pair(weight_key, value));
}
}
py::object Graph_add_edge(py::args args, py::kwargs kwargs) {
Graph& self = args[0].cast<Graph&>();
self.drop_cache();
py::object u_of_edge = args[1], v_of_edge = args[2];
py::dict edge_attr = kwargs;
_add_one_edge(self, u_of_edge, v_of_edge, edge_attr);
return py::none();
}
py::object Graph_add_edges(Graph& self, py::list edges_for_adding, py::list edges_attr) {
self.drop_cache();
if (py::len(edges_attr) != 0) {
if (py::len(edges_for_adding) != py::len(edges_attr)) {
PyErr_Format(PyExc_AssertionError, "Edges and Attributes lists must have same length.");
return py::none();
}
}
for (int i = 0; i < py::len(edges_for_adding); i++) {
py::tuple one_edge_for_adding = edges_for_adding[i].cast<py::tuple>();
py::dict edge_attr;
if (py::len(edges_attr)) {
edge_attr = edges_attr[i].cast<py::dict>();
} else {
edge_attr = py::dict();
}
_add_one_edge(self, one_edge_for_adding[0], one_edge_for_adding[1], edge_attr);
}
return py::none();
}
py::object Graph_add_edges_from(py::args args, py::kwargs attr) {
Graph& self = args[0].cast<Graph&>();
self.drop_cache();
py::list ebunch_to_add = py::list(args[1]);
for (int i = 0; i < len(ebunch_to_add); i++) {
py::list e = py::list(ebunch_to_add[i]);
py::object u, v;
py::dict dd;
switch (len(e)) {
case 2: {
u = e[0];
v = e[1];
break;
}
case 3: {
u = e[0];
v = e[1];
dd = e[2].cast<py::dict>();
break;
}
default: {
PyErr_Format(PyExc_ValueError, "Edge tuple %R must be a 2 - tuple or 3 - tuple.", e.ptr());
return py::none();
}
}
node_t u_id, v_id;
if (!self.node_to_id.contains(u)) {
if (u.is_none()) {
PyErr_Format(PyExc_ValueError, "None cannot be a node");
return py::none();
}
u_id = _add_one_node(self, u);
} else {
u_id = self.node_to_id[u].cast<node_t>();
}
if (!self.node_to_id.contains(v)) {
if (v.is_none()) {
PyErr_Format(PyExc_ValueError, "None cannot be a node");
return py::none();
}
v_id = _add_one_node(self, v);
} else {
v_id = (self.node_to_id[v]).cast<node_t>();
}
auto datadict = self.adj[u_id].count(v_id) ? self.adj[u_id][v_id] : node_attr_dict_factory();
py::list items = py::list(attr.attr("items")());
items.attr("extend")(py::list(dd.attr("items")()));
for (int i = 0; i < py::len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
datadict.insert(std::make_pair(weight_key, value));
}
// Warning: in Graph.py the edge attr is directed assigned by the dict extended from the original attr
self.adj[u_id][v_id].insert(datadict.begin(), datadict.end());
self.adj[v_id][u_id].insert(datadict.begin(), datadict.end());
}
return py::none();
}
py::object Graph_add_edges_from_file(Graph& self, py::str file, py::object weighted, py::object is_transform) {
self.drop_cache();
bool _is_transform = is_transform.cast<bool>();
struct commactype : std::ctype<char> {
commactype() : std::ctype<char>(get_table()) {}
std::ctype_base::mask const* get_table() {
std::ctype_base::mask* rc = 0;
if (rc == 0) {
rc = new std::ctype_base::mask[std::ctype<char>::table_size];
std::fill_n(rc, std::ctype<char>::table_size, std::ctype_base::mask());
rc[','] = std::ctype_base::space;
rc[' '] = std::ctype_base::space;
rc[' '] = std::ctype_base::space;
rc['\t'] = std::ctype_base::space;
rc['\n'] = std::ctype_base::space;
rc['\r'] = std::ctype_base::space;
}
return rc;
}
};
std::ios::sync_with_stdio(0);
std::string file_path = file.cast<std::string>();
std::ifstream in;
in.open(file_path);
if (!in.is_open()) {
PyErr_Format(PyExc_FileNotFoundError, "Please check the file and make sure the path only contains English");
return py::none();
}
in.imbue(std::locale(std::locale(), new commactype));
std::string data, key("weight");
std::string su, sv;
weight_t weight;
while (in >> su >> sv) {
py::str pu(su), pv(sv);
node_t u, v;
if (!self.node_to_id.contains(pu)) {
u = _add_one_node(self, pu);
} else {
u = self.node_to_id[pu].cast<node_t>();
}
if (!self.node_to_id.contains(pv)) {
v = _add_one_node(self, pv);
} else {
v = self.node_to_id[pv].cast<node_t>();
}
if (weighted.cast<bool>()) {
in >> weight;
self.adj[u][v][key] = self.adj[v][u][key] = weight;
} else {
if (!self.adj[u].count(v)) {
self.adj[u][v] = node_attr_dict_factory();
}
if (!self.adj[v].count(u)) {
self.adj[v][u] = node_attr_dict_factory();
}
}
}
in.close();
if(_is_transform){
Graph_L g_l = graph_to_linkgraph(self, false, key, true, false);
self.linkgraph_structure = g_l;
self.linkgraph_dirty = false;
}
return py::none();
}
py::object Graph_add_weighted_edge(Graph& self, py::object u_of_edge, py::object v_of_edge, weight_t weight) {
self.drop_cache();
py::dict edge_attr;
edge_attr["weight"] = weight;
_add_one_edge(self, u_of_edge, v_of_edge, edge_attr);
return py::none();
}
py::object Graph_remove_edge(Graph& self, py::object u, py::object v) {
self.drop_cache();
if (self.node_to_id.contains(u) && self.node_to_id.contains(v)) {
node_t u_id = self.node_to_id[u].cast<node_t>();
node_t v_id = self.node_to_id[v].cast<node_t>();
auto& v_neighbors_info = self.adj[u_id];
if (v_neighbors_info.find(v_id) != v_neighbors_info.end()) {
v_neighbors_info.erase(v_id);
if (u_id != v_id) {
self.adj[v_id].erase(u_id);
}
return py::none();
}
}
PyErr_Format(PyExc_KeyError, "No edge %R-%R in graph.", u.ptr(), v.ptr());
return py::none();
}
py::object Graph_remove_edges(py::object self, py::list edges_to_remove) {
Graph& self_ = self.cast<Graph&>();
for (int i = 0; i < py::len(edges_to_remove); i++) {
py::tuple edge = edges_to_remove[i].cast<py::tuple>();
py::object u = edge[0], v = edge[1];
self.attr("remove_edge")(u, v);
}
self_.drop_cache();
return py::none();
}
py::object Graph_number_of_edges(py::object self, py::object u, py::object v) {
if (u.is_none()) {
return self.attr("size")();
}
Graph& self_ = self.cast<Graph&>();
node_t u_id = self_.node_to_id.attr("get")(u, -1).cast<node_t>();
node_t v_id = self_.node_to_id.attr("get")(v, -1).cast<node_t>();
return py::cast(int(self_.adj.count(u_id) && self_.adj[u_id].count(v_id)));
}
py::object Graph_has_edge(Graph& self, py::object u, py::object v) {
if (self.node_to_id.contains(u) && self.node_to_id.contains(v)) {
node_t u_id = self.node_to_id[u].cast<node_t>();
node_t v_id = self.node_to_id[v].cast<node_t>();
auto& v_neighbors_info = self.adj[u_id];
if (v_neighbors_info.find(v_id) != v_neighbors_info.end()) {
return py::cast(true);
}
}
return py::cast(false);
}
py::object Graph_copy(py::object self) {
Graph& self_ = self.cast<Graph&>();
py::object G = self.attr("__class__")();
Graph& G_ = G.cast<Graph&>();
G_.graph.attr("update")(self_.graph);
G_.id_to_node.attr("update")(self_.id_to_node);
G_.node_to_id.attr("update")(self_.node_to_id);
G_.id = self_.id;
G_.node = self_.node;
G_.adj = self_.adj;
return G;
}
py::object Graph_degree(py::object self, py::object weight) {
py::dict degree;
py::list edges = self.attr("edges").cast<py::list>();
py::object u, v;
py::dict d;
for (int i = 0; i < py::len(edges); i++) {
py::tuple edge = edges[i].cast<py::tuple>();
u = edge[0];
v = edge[1];
d = edge[2].cast<py::dict>();
if (degree.contains(u)) {
degree[u] = py::object(degree[u]) + d.attr("get")(weight, 1);
} else {
degree[u] = d.attr("get")(weight, 1);
}
if (degree.contains(v)) {
degree[v] = py::object(degree[v]) + d.attr("get")(weight, 1);
} else {
degree[v] = d.attr("get")(weight, 1);
}
}
py::list nodes = py::list(self.attr("nodes"));
for (int i = 0; i < py::len(nodes); i++) {
py::object node = nodes[i];
if (!degree.contains(node)) {
degree[node] = 0;
}
}
return degree;
}
py::object Graph_neighbors(py::object self, py::object node) {
Graph& self_ = self.cast<Graph&>();
if (self_.node_to_id.contains(node)) {
return self.attr("adj")[node].attr("__iter__")();
} else {
PyErr_Format(PyExc_KeyError, "No node %R", node.ptr());
return py::none();
}
}
py::object Graph_generate_linkgraph(py::object self, py::object weight){
Graph& G_ = self.cast<Graph&>();
std::string w = weight_to_string(weight);
Graph_L g_l = graph_to_linkgraph(G_, false, w, true, false);
G_.linkgraph_dirty = false;
G_.linkgraph_structure = g_l;
return py::none();
}
py::object Graph_nodes_subgraph(py::object self, py::list from_nodes) {
py::object G = self.attr("__class__")();
Graph& self_ = self.cast<Graph&>();
Graph& G_ = G.cast<Graph&>();
G_.graph.attr("update")(self_.graph);
py::object nodes = self.attr("nodes");
py::object adj = self.attr("adj");
for (int i = 0; i < py::len(from_nodes); i++) {
py::object node = from_nodes[i];
if (self_.node_to_id.contains(node)) {
py::object node_attr = nodes[node];
_add_one_node(G_, node, node_attr);
}
py::object out_edges = adj[node];
py::list edge_items = py::list(out_edges.attr("items")());
for (int j = 0; j < py::len(edge_items); j++) {
py::tuple item = edge_items[j].cast<py::tuple>();
py::object v = item[0];
py::object edge_attr = item[1];
if (from_nodes.contains(v)) {
_add_one_edge(G_, node, v, edge_attr);
}
}
}
return G;
}
py::object Graph_ego_subgraph(py::object self, py::object center) {
py::list neighbors_of_center = py::list(self.attr("all_neighbors")(center));
neighbors_of_center.append(center);
return self.attr("nodes_subgraph")(neighbors_of_center);
}
py::object Graph_size(py::object self, py::object weight) {
py::dict degree = self.attr("degree")(weight).cast<py::dict>();
weight_t s = 0;
for (auto item : degree) {
s += item.second.cast<weight_t>();
}
return (weight.is_none()) ? py::cast(int(s) / 2) : py::cast(s / 2);
}
py::object Graph_is_directed(py::object self) {
return py::cast(false);
}
py::object Graph_is_multigraph(py::object self) {
return py::cast(false);
}
py::object Graph_to_index_node_graph(py::object self, py::object begin_index) {
py::object G = self.attr("__class__")();
G.attr("graph").attr("update")(self.attr("graph"));
py::dict index_of_node = py::dict(), node_of_index = py::dict();
int begin = begin_index.cast<int>();
int index = 0;
for (auto item : self.attr("nodes").cast<py::dict>()) {
py::object node = item.first.cast<py::object>();
py::dict node_attr = item.second.cast<py::dict>();
G.attr("add_node")(py::cast(index + begin), **node_attr);
index_of_node[node] = index + begin;
node_of_index[py::cast(index + begin)] = node;
index++;
}
for (auto item : self.attr("adj").cast<py::dict>()) {
py::object u = item.first.cast<py::object>();
py::dict nbrs = item.second.cast<py::dict>();
for (auto item_ : nbrs) {
py::object v = item_.first.cast<py::object>();
py::dict edge_data = item_.second.cast<py::dict>();
G.attr("add_edge")(index_of_node[u], index_of_node[v], **edge_data);
}
}
return py::make_tuple(G, index_of_node, node_of_index);
}
py::object Graph_py(py::object self) {
py::object G = py::module_::import("easygraph").attr("Graph")();
G.attr("graph").attr("update")(self.attr("graph"));
G.attr("adj").attr("update")(self.attr("adj"));
G.attr("nodes").attr("update")(self.attr("nodes"));
return G;
}
py::object Graph::get_nodes() {
if (this->dirty_nodes) {
py::dict nodes = py::dict();
for (const auto& node_info : node) {
node_t id = node_info.first;
const auto& node_attr = node_info.second;
nodes[this->id_to_node[py::cast(id)]] = attr_to_dict(node_attr);
}
this->nodes_cache = nodes;
this->dirty_nodes = false;
}
return this->nodes_cache;
}
py::object Graph::get_name() {
return this->graph.attr("get")("name", "");
}
py::object Graph::set_name(py::object name) {
this->graph[py::cast("name")] = name;
return py::none();
}
py::object Graph::get_node_index() {
py::dict node_index = py::dict();
int len = py::len(this->node_to_id);
for(int i = 1; i <= len; i++){
node_index[this->id_to_node[py::cast(i)]] = py::cast(i - 1);
}
return node_index;
}
py::object Graph::get_graph() {
return this->graph;
}
py::object Graph::get_adj() {
if (this->dirty_adj) {
py::dict adj = py::dict();
for (const auto& ego_edges : this->adj) {
node_t start_point = ego_edges.first;
py::dict ego_edges_dict = py::dict();
for (const auto& edge_info : ego_edges.second) {
node_t end_point = edge_info.first;
const auto& edge_attr = edge_info.second;
ego_edges_dict[this->id_to_node[py::cast(end_point)]] = attr_to_dict(edge_attr);
}
adj[this->id_to_node[py::cast(start_point)]] = ego_edges_dict;
}
this->adj_cache = adj;
this->dirty_adj = false;
}
return this->adj_cache;
}
py::object Graph::get_edges() {
py::list edges = py::list();
std::set<std::pair<node_t, node_t> > seen;
for (const auto& ego_edges : this->adj) {
node_t u = ego_edges.first;
for (const auto& edge_info : ego_edges.second) {
node_t v = edge_info.first;
const auto& edge_attr = edge_info.second;
if (seen.find(std::make_pair(u, v)) == seen.end()) {
seen.insert(std::make_pair(u, v));
seen.insert(std::make_pair(v, u));
edges.append(py::make_tuple(this->id_to_node[py::cast(u)], this->id_to_node[py::cast(v)], attr_to_dict(edge_attr)));
}
}
}
return edges;
}
Graph_L Graph::_get_linkgraph_structure() {
return this->linkgraph_structure;
}
bool Graph::is_linkgraph_dirty(){
return this->linkgraph_dirty;
}
std::vector<graph_edge> Graph::_get_edges(bool if_directed) {
std::vector<graph_edge> edges;
std::set<std::pair<node_t, node_t> > seen;
for (const auto& ego_edges : this->adj) {
node_t u = ego_edges.first;
for (const auto& edge_info : ego_edges.second) {
node_t v = edge_info.first;
const auto& edge_attr = edge_info.second;
if (seen.find(std::make_pair(u, v)) == seen.end()) {
seen.insert(std::make_pair(u, v));
if(!if_directed){
seen.insert(std::make_pair(v, u));
}
edges.emplace_back(u, v, edge_attr);
}
}
}
return edges;
}
void Graph::drop_cache() {
dirty_nodes = true;
dirty_adj = true;
linkgraph_dirty = true;
csr_graph = nullptr;
}
std::shared_ptr<CSRGraph> Graph::gen_CSR(const std::string& weight) {
if (csr_graph != nullptr) {
if (csr_graph->W_map.find(weight) == csr_graph->W_map.end()) {
auto W = std::make_shared<std::vector<double>>();
// According to C++ Standard, the iteration order of a unordered contrainer will
// not change without rehashing which only happens during a insert.
for (node_t n : csr_graph->nodes) {
// if n is not in adj, this way can raise an exception
const auto& n_adjs = adj.find(n)->second;
for (auto adj_it = n_adjs.begin(); adj_it != n_adjs.end(); ++adj_it) {
const edge_attr_dict_factory& edge_attr = adj_it->second;
auto edge_it = edge_attr.find(weight);
weight_t w = edge_it != edge_attr.end() ? edge_it->second : 1.0;
W->push_back(w);
}
}
csr_graph->W_map[weight] = W;
}
} else {
// the graph has been modified
csr_graph = std::make_shared<CSRGraph>();
std::vector<node_t>& nodes = csr_graph->nodes;
for (auto it = node.begin(); it != node.end(); ++it) {
nodes.push_back(it->first);
}
std::sort(nodes.begin(), nodes.end());
std::unordered_map<node_t, int>& node2idx = csr_graph->node2idx;
for (int i = 0; i < nodes.size(); ++i) {
node2idx[nodes[i]] = i;
}
std::vector<int>& V = csr_graph->V;
std::vector<int>& E = csr_graph->E;
auto W = std::make_shared<std::vector<double>>();
for (int idx = 0; idx < nodes.size(); ++idx) {
V.push_back(E.size());
node_t n = nodes[idx];
// if n is not in adj, this way can raise an exception
const auto& n_adjs = adj.find(n)->second;
for (auto adj_it = n_adjs.begin(); adj_it != n_adjs.end(); ++adj_it) {
const edge_attr_dict_factory& edge_attr = adj_it->second;
auto edge_it = edge_attr.find(weight);
weight_t w = edge_it != edge_attr.end() ? edge_it->second : 1.0;
W->push_back(w);
E.push_back(node2idx[adj_it->first]);
}
}
V.push_back(E.size());
csr_graph->W_map[weight] = W;
}
return csr_graph;
}
std::shared_ptr<CSRGraph> Graph::gen_CSR() {
if (csr_graph != nullptr) {
if (csr_graph->unweighted_W.size() != csr_graph->E.size()) {
csr_graph->unweighted_W = std::vector<double>(csr_graph->E.size(), 1.0);
}
} else {
// the graph has been modified
csr_graph = std::make_shared<CSRGraph>();
std::vector<node_t>& nodes = csr_graph->nodes;
for (auto it = node.begin(); it != node.end(); ++it) {
nodes.push_back(it->first);
}
std::sort(nodes.begin(), nodes.end());
std::unordered_map<node_t, int>& node2idx = csr_graph->node2idx;
for (int i = 0; i < nodes.size(); ++i) {
node2idx[nodes[i]] = i;
}
std::vector<int>& V = csr_graph->V;
std::vector<int>& E = csr_graph->E;
for (int idx = 0; idx < nodes.size(); ++idx) {
V.push_back(E.size());
node_t n = nodes[idx];
// if n is not in adj, this way can raise an exception
const auto& n_adjs = adj.find(n)->second;
for (auto adj_it = n_adjs.begin(); adj_it != n_adjs.end(); ++adj_it) {
const edge_attr_dict_factory& edge_attr = adj_it->second;
E.push_back(node2idx[adj_it->first]);
}
}
V.push_back(E.size());
csr_graph->unweighted_W = std::vector<double>(E.size(), 1.0);
}
return csr_graph;
}
std::shared_ptr<std::vector<int>> Graph::gen_CSR_sources(const py::object& py_sources) {
auto sources = std::make_shared<std::vector<int>>();
if (py_sources.is_none()) {
for (int i = 0; i < csr_graph->V.size() - 1; ++i) {
sources->push_back(i);
}
} else {
for (auto it = py_sources.begin(); it != py_sources.end(); ++it) {
sources->push_back(csr_graph->node2idx[node_to_id[*it].cast<node_t>()]);
}
}
return sources;
}
std::shared_ptr<COOGraph> Graph::gen_COO() {
if (coo_graph != nullptr) {
if (coo_graph->unweighted_W.size() != coo_graph->row.size()) {
coo_graph->unweighted_W = std::vector<double>(coo_graph->row.size(), 1.0);
}
} else {
coo_graph = std::make_shared<COOGraph>();
std::vector<node_t>& nodes = coo_graph->nodes;
for (auto it = node.begin(); it != node.end(); ++it) {
nodes.push_back(it->first);
}
std::sort(nodes.begin(), nodes.end());
std::unordered_map<node_t, int>& node2idx = coo_graph->node2idx;
for (int i = 0; i < nodes.size(); ++i) {
node2idx[nodes[i]] = i;
}
std::vector<int>& row = coo_graph->row;
std::vector<int>& col = coo_graph->col;
std::vector<double>& unweighted_W = coo_graph->unweighted_W;
for (int idx = 0; idx < nodes.size(); ++idx) {
node_t n = nodes[idx];
const auto& n_adjs = adj.find(n)->second;
for (auto adj_it = n_adjs.begin(); adj_it != n_adjs.end(); ++adj_it) {
node_t neighbor = adj_it->first;
row.push_back(idx);
col.push_back(node2idx[neighbor]);
unweighted_W.push_back(1.0);
}
}
}
return coo_graph;
}
std::shared_ptr<COOGraph> Graph::gen_COO(const std::string& weight) {
if (coo_graph != nullptr) {
if (coo_graph->W_map.find(weight) == coo_graph->W_map.end()) {
auto W = std::make_shared<std::vector<double>>();
for (node_t n : coo_graph->nodes) {
const auto& n_adjs = adj.find(n)->second;
for (auto adj_it = n_adjs.begin(); adj_it != n_adjs.end(); ++adj_it) {
const edge_attr_dict_factory& edge_attr = adj_it->second;
auto edge_it = edge_attr.find(weight);
weight_t w = edge_it != edge_attr.end() ? edge_it->second : 1.0;
W->push_back(w);
}
}
coo_graph->W_map[weight] = W;
}
} else {
coo_graph = std::make_shared<COOGraph>();
std::vector<node_t>& nodes = coo_graph->nodes;
for (auto it = node.begin(); it != node.end(); ++it) {
nodes.push_back(it->first);
}
std::sort(nodes.begin(), nodes.end());
std::unordered_map<node_t, int>& node2idx = coo_graph->node2idx;
for (int i = 0; i < nodes.size(); ++i) {
node2idx[nodes[i]] = i;
}
std::vector<int>& row = coo_graph->row;
std::vector<int>& col = coo_graph->col;
auto W = std::make_shared<std::vector<double>>();
for (int idx = 0; idx < nodes.size(); ++idx) {
node_t n = nodes[idx];
const auto& n_adjs = adj.find(n)->second;
for (auto adj_it = n_adjs.begin(); adj_it != n_adjs.end(); ++adj_it) {
const edge_attr_dict_factory& edge_attr = adj_it->second;
auto edge_it = edge_attr.find(weight);
weight_t w = edge_it != edge_attr.end() ? edge_it->second : 1.0;
row.push_back(idx);
col.push_back(node2idx[adj_it->first]);
W->push_back(w);
}
}
coo_graph->W_map[weight] = W;
}
return coo_graph;
}
std::shared_ptr<COOGraph> Graph::transfer_csr_to_coo(const std::shared_ptr<CSRGraph>& csr_graph) {
auto coo_graph = std::make_shared<COOGraph>();
coo_graph->nodes = csr_graph->nodes;
coo_graph->node2idx = csr_graph->node2idx;
const std::vector<int>& V = csr_graph->V;
const std::vector<int>& E = csr_graph->E;
int num_edges = E.size();
coo_graph->row.reserve(num_edges);
coo_graph->col.reserve(num_edges);
std::vector<int>& row = coo_graph->row;
std::vector<int>& col = coo_graph->col;
for (int i = 0; i < V.size() - 1; ++i) {
int start_idx = V[i];
int end_idx = V[i + 1];
for (int j = start_idx; j < end_idx; ++j) {
row.push_back(i);
col.push_back(E[j]);
}
}
if (!csr_graph->unweighted_W.empty()) {
coo_graph->unweighted_W = csr_graph->unweighted_W;
} else {
coo_graph->W_map = csr_graph->W_map;
}
return coo_graph;
}
+73
View File
@@ -0,0 +1,73 @@
#pragma once
#include "../common/common.h"
#include "csr_graph.h"
#include "linkgraph.h"
#include "coo_graph.h"
// old version
struct Graph {
node_dict_factory node;
adj_dict_factory adj;
Graph_L linkgraph_structure;
std::shared_ptr<CSRGraph> csr_graph;
py::kwargs node_to_id, id_to_node, graph;
node_t id;
bool dirty_nodes, dirty_adj, linkgraph_dirty;
py::object nodes_cache, adj_cache;
std::shared_ptr<COOGraph> coo_graph;
Graph();
py::object get_nodes();
py::object get_name();
py::object set_name(py::object name);
py::object get_graph();
py::object get_adj();
py::object get_edges();
py::object get_node_index();
std::vector<graph_edge> _get_edges(bool if_directed=true);
bool is_linkgraph_dirty();
Graph_L _get_linkgraph_structure();
void drop_cache();
std::shared_ptr<CSRGraph> gen_CSR(const std::string& weight);
std::shared_ptr<CSRGraph> gen_CSR();
std::shared_ptr<std::vector<int>> gen_CSR_sources(const py::object& py_sources);
std::shared_ptr<COOGraph> gen_COO();
std::shared_ptr<COOGraph> gen_COO(const std::string& weight);
std::shared_ptr<COOGraph> transfer_csr_to_coo(const std::shared_ptr<CSRGraph>& csr_graph);
};
py::object Graph__init__(py::args args, py::kwargs kwargs);
py::object Graph__iter__(py::object self);
py::object Graph__len__(py::object self);
py::object Graph__contains__(py::object self, py::object node);
py::object Graph__getitem__(py::object self, py::object node);
py::object Graph_add_node(py::args args, py::kwargs kwargs);
py::object Graph_add_nodes(Graph& self, py::list nodes_for_adding, py::list nodes_attr);
py::object Graph_add_nodes_from(py::args args, py::kwargs kwargs);
py::object Graph_remove_node(Graph& self, py::object node_to_remove);
py::object Graph_remove_nodes(py::object self, py::list nodes_to_remove);
py::object Graph_number_of_nodes(Graph& self);
py::object Graph_has_node(Graph& self, py::object node);
py::object Graph_nbunch_iter(py::object self, py::object nbunch);
py::object Graph_add_edge(py::args args, py::kwargs kwargs);
py::object Graph_add_edges(Graph& self, py::list edges_for_adding, py::list edges_attr);
py::object Graph_add_edges_from(py::args args, py::kwargs attr);
py::object Graph_add_edges_from_file(Graph& self, py::str file, py::object weighted, py::object is_transform);
py::object Graph_add_weighted_edge(Graph& self, py::object u_of_edge, py::object v_of_edge, weight_t weight);
py::object Graph_remove_edge(Graph& self, py::object u, py::object v);
py::object Graph_remove_edges(py::object self, py::list edges_to_remove);
py::object Graph_number_of_edges(py::object self, py::object u, py::object v);
py::object Graph_has_edge(Graph& self, py::object u, py::object v);
py::object Graph_copy(py::object self);
py::object Graph_degree(py::object self, py::object weight);
py::object Graph_neighbors(py::object self, py::object node);
py::object Graph_nodes_subgraph(py::object self, py::list from_nodes);
py::object Graph_ego_subgraph(py::object self, py::object center);
py::object Graph_size(py::object self, py::object weight);
py::object Graph_is_directed(py::object self);
py::object Graph_is_multigraph(py::object self);
py::object Graph_to_index_node_graph(py::object self, py::object begin_index);
py::object Graph_generate_linkgraph(py::object self, py::object weight);
py::object Graph_py(py::object self);
+91
View File
@@ -0,0 +1,91 @@
#pragma once
#include "../common/common.h"
struct LinkEdge{
// 终点
node_t to;
// 边权重
weight_t w;
// 同起点的上一条边的编号
node_t next;
};
struct Graph_L{
int n;
int e;
bool is_directed;
bool is_deg;
std::vector<int> head;
std::vector<LinkEdge> edges;
std::vector<int> degree;
int max_deg = -1;
Graph_L(int vertex_num = 0, bool directed = true, bool deg = false){
this->n = vertex_num;
this->e = 0;
this->is_deg = deg;
this->is_directed = directed;
LinkEdge le;
le.to = -1;
le.next = -1;
edges.emplace_back(le);
if(n > 0){
head.resize(vertex_num + 1);
if(deg){
degree.resize(vertex_num + 1);
for(int i = 0; i < vertex_num+1; i++){
head[i] = -1;
degree[i] = 0;
}
}
else{
for(int i = 0; i < vertex_num+1; i++){
head[i] = -1;
}
}
}
}
void add_unweighted_edge(const int &u, const int &v) {
LinkEdge le;
le.to = v;
le.next = head[u];
this->edges.emplace_back(le);
this->head[u] = this->e;
this->e += 1;
if(this->is_deg){
this->degree[u]++;
this->max_deg = std::max(this->max_deg, this->degree[u]);
}
}
void add_weighted_edge(const int &u, const int &v, const double &w) {
// printf("u:%d,v:%d w:%.2f\n",u,v,w);
LinkEdge le;
this->e += 1;
le.to = v;
le.w = w;
le.next = head[u];
// printf("next:%d\n",this->head[u]);
this->edges.emplace_back(le);
this->head[u] = this->e;
// printf("head:%d\n",this->head[u]);
if(this->is_deg){
this->degree[u]++;
this->max_deg = std::max(this->max_deg, this->degree[u]);
}
}
};
struct compare_node {
compare_node(){}
compare_node(int _x, int _d) {x = _x; d = _d;}
int x, d;
bool operator < (const compare_node &rhs) const {
return d > rhs.d;
}
};
std::vector<double> _dijkstra(const Graph_L& G_l, int source, int target);
+16
View File
@@ -0,0 +1,16 @@
#include "operation.h"
#include "graph.h"
py::object density(py::object G) {
Graph& G_ = G.cast<Graph&>();
int n = G_.node.size();
int m = G.attr("number_of_edges")().cast<int>();
if (m == 0 || n <= 1) {
return py::cast(0);
}
weight_t d = m * 1.0 / (n * (n - 1));
if(G.attr("is_directed")().equal(py::cast(false))){
d*=2;
}
return py::cast(d);
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include "../common/common.h"
py::object density(py::object G);
+42
View File
@@ -0,0 +1,42 @@
#include "../common/common.h"
class Segment_tree_zkw {
private:
int tn;
int size;
public:
std::vector<int> t;
std::vector<int> num;
Segment_tree_zkw(int N){
size = (N+1)<<2;
this->t = std::vector<int>(size+1 , INT_MAX);
this->num = std::vector<int>(size+1 , 0);
}
void init(int N) {
for(int i = 0; i < size; i++){
this->t[i] = INT_MAX;
this->num[i] = 0;
}
tn = 1;
while(tn < N) tn <<= 1;
--tn;
for (int i = 1; i <= N; ++i)
this->num[i + tn] = i;
}
void change(int p, const int &k) {
p += tn; this->t[p] = k; p >>= 1;
while (p) {
if (this->t[p<<1] < this->t[p<<1|1]) {
this->t[p] = this->t[p<<1];
this->num[p] = this->num[p<<1];
}
else {
this->t[p] = this->t[p<<1|1];
this->num[p] = this->num[p<<1|1];
}
p >>= 1;
}
}
};
+3
View File
@@ -0,0 +1,3 @@
#include "common.h"
graph_edge::graph_edge(node_t u_, node_t v_, edge_attr_dict_factory attr_) : u(u_), v(v_), attr(attr_) {}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
#include <pybind11/stl.h>
#include <iostream>
#include <stdio.h>
#include <fstream>
#include <map>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <exception>
#include <set>
#include <cmath>
#include <random>
#include <algorithm>
#include <queue>
#include <vector>
#include <thread>
#include <inttypes.h>
#include <limits>
namespace py = pybind11;
typedef int node_t;
typedef float weight_t;
typedef std::map<std::string, weight_t> node_attr_dict_factory; //(weight_key, value)
typedef std::map<std::string, weight_t> edge_attr_dict_factory; //(weight_key, value)
typedef std::unordered_map<node_t, node_attr_dict_factory> node_dict_factory; //(node, node_attr)
typedef std::unordered_map<node_t, edge_attr_dict_factory> adj_attr_dict_factory; //(out_node, (weight_key, value))
typedef std::unordered_map<node_t, adj_attr_dict_factory> adj_dict_factory; //(node, edge_attr)
struct graph_edge {
node_t u, v;
edge_attr_dict_factory attr;
graph_edge(node_t, node_t, edge_attr_dict_factory);
};
+48
View File
@@ -0,0 +1,48 @@
#include "utils.h"
#include "../classes/graph.h"
#include "../classes/linkgraph.h"
py::object attr_to_dict(const node_attr_dict_factory& attr) {
py::dict attr_dict = py::dict();
for (const auto& kv : attr) {
attr_dict[py::cast(kv.first)] = kv.second;
}
return attr_dict;
}
std::string weight_to_string(py::object weight) {
py::object warn = py::module_::import("warnings").attr("warn");
if (!py::isinstance<py::str>(weight)) {
if (!weight.is_none()) {
warn(py::str(weight) + py::str(" would be transformed into an instance of str."));
}
weight = py::str(weight);
}
std::string weight_key = weight.cast<std::string>();
return weight_key;
}
py::object py_sum(py::object o) {
py::object sum = py::module_::import("builtins").attr("sum");
return sum(o);
}
Graph_L graph_to_linkgraph(Graph &G, bool if_directed, std::string weight_key, bool is_deg, bool is_reverse){
int node_num = G.node.size();
const std::vector<graph_edge>& edges = G._get_edges(if_directed);
int edges_num = edges.size();
Graph_L G_l(node_num, if_directed, is_deg);
for(int i = 0; i < edges_num; i++){
graph_edge e = edges[i];
edge_attr_dict_factory& edge_attr = e.attr;
weight_t edge_weight = edge_attr.find(weight_key) != edge_attr.end() ? edge_attr[weight_key] : 1;
if(is_reverse){
std::swap(e.u, e.v);
}
G_l.add_weighted_edge(e.u, e.v, edge_weight);
if (!if_directed){
G_l.add_weighted_edge(e.v, e.u, edge_weight);
}
}
return G_l;
}
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include "common.h"
#include "../classes/linkgraph.h"
#include "../classes/graph.h"
py::object attr_to_dict(const node_attr_dict_factory& attr);
std::string weight_to_string(py::object weight);
py::object py_sum(py::object o);
Graph_L graph_to_linkgraph(Graph &G, bool if_directed=false, std::string weight_key = "weight", bool is_deg = false, bool is_reverse = false);
+120
View File
@@ -0,0 +1,120 @@
#include "classes/__init__.h"
#include "functions/__init__.h"
PYBIND11_MODULE(cpp_easygraph, m) {
py::class_<Graph>(m, "Graph")
.def(py::init<>())
.def("__init__", &Graph__init__)
.def("__iter__", &Graph__iter__)
.def("__len__", &Graph__len__)
.def("__contains__", &Graph__contains__, py::arg("node"))
.def("__getitem__", &Graph__getitem__, py::arg("node"))
.def("add_node", &Graph_add_node)
.def("add_nodes", &Graph_add_nodes, py::arg("nodes_for_adding"), py::arg("nodes_attr") = py::list())
.def("add_nodes_from", &Graph_add_nodes_from)
.def("remove_node", &Graph_remove_node, py::arg("node_to_remove"))
.def("remove_nodes", &Graph_remove_nodes, py::arg("nodes_to_remove"))
.def("number_of_nodes", &Graph_number_of_nodes)
.def("has_node", &Graph_has_node, py::arg("node"))
.def("nbunch_iter", &Graph_nbunch_iter, py::arg("nbunch") = py::none())
.def("add_edge", &Graph_add_edge)
.def("add_edges", &Graph_add_edges, py::arg("edges_for_adding"), py::arg("edges_attr") = py::list())
.def("add_edges_from", &Graph_add_edges_from)
.def("add_edges_from_file", &Graph_add_edges_from_file, py::arg("file"), py::arg("weighted") = false, py::arg("is_transform") = false)
.def("add_weighted_edge", &Graph_add_weighted_edge, py::arg("u_of_edge"), py::arg("v_of_edge"), py::arg("weight"))
.def("remove_edge", &Graph_remove_edge, py::arg("u"), py::arg("v"))
.def("remove_edges", &Graph_remove_edges, py::arg("edges_to_remove"))
.def("number_of_edges", &Graph_number_of_edges, py::arg("u") = py::none(), py::arg("v") = py::none())
.def("has_edge", &Graph_has_edge, py::arg("u"), py::arg("y"))
.def("copy", &Graph_copy)
.def("degree", &Graph_degree, py::arg("weight") = "weight")
.def("neighbors", &Graph_neighbors, py::arg("node"))
.def("all_neighbors", &Graph_neighbors, py::arg("node"))
.def("nodes_subgraph", &Graph_nodes_subgraph, py::arg("from_nodes"))
.def("ego_subgraph", &Graph_ego_subgraph, py::arg("center"))
.def("size", &Graph_size, py::arg("weight") = py::none())
.def("is_directed", &Graph_is_directed)
.def("is_multigraph", &Graph_is_multigraph)
.def("to_index_node_graph", &Graph_to_index_node_graph, py::arg("begin_index") = 0)
.def("py", &Graph_py)
.def_property("graph", &Graph::get_graph, nullptr)
.def_property("nodes", &Graph::get_nodes, nullptr)
.def_property("name", &Graph::get_name, &Graph::set_name)
.def_property("adj", &Graph::get_adj, nullptr)
.def_property("edges", &Graph::get_edges, nullptr)
.def_property("node_index", &Graph::get_node_index, nullptr)
.def("generate_linkgraph", &Graph_generate_linkgraph,py::arg("weight") = "weight");
py::class_<DiGraph, Graph>(m, "DiGraph")
.def(py::init<>())
.def("__init__", &DiGraph__init__)
.def("out_degree", &DiGraph_out_degree, py::arg("weight") = "weight")
.def("in_degree", &DiGraph_in_degree, py::arg("weight") = "weight")
.def("degree", &DiGraph_degree, py::arg("weight") = "weight")
.def("size", &DiGraph_size, py::arg("weight") = py::none())
.def("number_of_edges", &DiGraph_number_of_edges, py::arg("u") = py::none(), py::arg("v") = py::none())
.def("successors", &Graph_neighbors, py::arg("node"))
.def("predecessors", &DiGraph_predecessors, py::arg("node"))
.def("add_node", &DiGraph_add_node)
.def("add_nodes", &DiGraph_add_nodes, py::arg("nodes_for_adding"), py::arg("nodes_attr") = py::list())
.def("add_nodes_from", &DiGraph_add_nodes_from)
.def("remove_node", &DiGraph_remove_node, py::arg("node_to_remove"))
.def("remove_nodes", &DiGraph_remove_nodes, (py::arg("nodes_to_remove")))
.def("add_edge", &DiGraph_add_edge)
.def("add_edges", &DiGraph_add_edges, py::arg("edges_for_adding"), py::arg("edges_attr") = py::list())
.def("add_edges_from", &DiGraph_add_edges_from)
.def("add_edges_from_file", &DiGraph_add_edges_from_file, py::arg("file"), py::arg("weighted") = false, py::arg("is_transform") = false)
.def("add_weighted_edge", &DiGraph_add_weighted_edge, py::arg("u_of_edge"), py::arg("v_of_edge"), py::arg("weight"))
.def("remove_edge", &DiGraph_remove_edge, py::arg("u"), py::arg("v"))
.def("remove_edges", &DiGraph_remove_edges, py::arg("edges_to_remove"))
.def("remove_edges_from", &DiGraph_remove_edges_from, py::arg("ebunch"))
.def("nodes_subgraph", &DiGraph_nodes_subgraph, py::arg("from_nodes"))
.def("is_directed", &DiGraph_is_directed)
.def("py", &DiGraph_py)
.def_property("edges", &DiGraph::get_edges, nullptr)
.def_property("pred", &DiGraph::get_pred,nullptr)
.def("generate_linkgraph", &DiGraph_generate_linkgraph,py::arg("weight") = "weight");
m.def("cpp_degree_centrality", &degree_centrality, py::arg("G"));
m.def("cpp_in_degree_centrality", &in_degree_centrality, py::arg("G"));
m.def("cpp_out_degree_centrality", &out_degree_centrality, py::arg("G"));
m.def("cpp_closeness_centrality", &closeness_centrality, py::arg("G"), py::arg("weight") = "weight", py::arg("cutoff") = py::none(), py::arg("sources") = py::none());
m.def("cpp_betweenness_centrality", &betweenness_centrality, py::arg("G"), py::arg("weight") = "weight", py::arg("cutoff") = py::none(),py::arg("sources") = py::none(), py::arg("normalized") = py::bool_(true), py::arg("endpoints") = py::bool_(false));
m.def("cpp_katz_centrality", &cpp_katz_centrality, py::arg("G"), py::arg("alpha") = 0.1, py::arg("beta") = 1.0, py::arg("max_iter") = 1000, py::arg("tol") = 1e-6, py::arg("normalized") = true);
m.def("cpp_eigenvector_centrality", &cpp_eigenvector_centrality, py::arg("G"), py::arg("max_iter") = 100, py::arg("tol") = 1.0e-6, py::arg("nstart") = py::none(), py::arg("weight") = "weight");
m.def("cpp_k_core", &core_decomposition, py::arg("G"));
m.def("cpp_density", &density, py::arg("G"));
m.def("cpp_constraint", &constraint, py::arg("G"), py::arg("nodes") = py::none(), py::arg("weight") = py::none(), py::arg("n_workers") = py::none());
m.def("cpp_effective_size", &effective_size, py::arg("G"), py::arg("nodes") = py::none(), py::arg("weight") = py::none(), py::arg("n_workers") = py::none());
m.def("cpp_efficiency", &efficiency, py::arg("G"), py::arg("nodes") = py::none(), py::arg("weight") = py::none(), py::arg("n_workers") = py::none());
m.def("cpp_hierarchy", &hierarchy, py::arg("G"), py::arg("nodes") = py::none(), py::arg("weight") = py::none(), py::arg("n_workers") = py::none());
m.def("cpp_pagerank", &_pagerank, py::arg("G"), py::arg("alpha") = 0.85, py::arg("max_iterator") = 500, py::arg("threshold") = 1e-6, py::arg("weight") = "weight");
m.def("cpp_dijkstra_multisource", &_dijkstra_multisource, py::arg("G"), py::arg("sources"), py::arg("weight") = "weight", py::arg("target") = py::none());
m.def("cpp_spfa", &_spfa, py::arg("G"), py::arg("source"), py::arg("weight") = "weight");
m.def("cpp_clustering", &clustering, py::arg("G"), py::arg("nodes") = py::none(), py::arg("weight") = py::none());
m.def("cpp_biconnected_dfs_record_edges", &_biconnected_dfs_record_edges, py::arg("G"), py::arg("need_components") = true);
m.def("cpp_strongly_connected_components",&strongly_connected_components,py::arg("G"));
m.def("cpp_Floyd", &Floyd, py::arg("G"), py::arg("weight") = "weight");
m.def("cpp_Prim", &Prim, py::arg("G"), py::arg("weight") = "weight");
m.def("cpp_Kruskal", &Kruskal, py::arg("G"), py::arg("weight") = "weight");
m.def("cpp_plain_bfs", &plain_bfs, py::arg("G"), py::arg("source"));
m.def("cpp_kruskal_mst_edges", &kruskal_mst_edges, py::arg("G"), py::arg("minimum") = true, py::arg("weight") = "weight", py::arg("data") = true, py::arg("ignore_nan") = false);
m.def("cpp_prim_mst_edges", &prim_mst_edges, py::arg("G"), py::arg("minimum") = true, py::arg("weight") = "weight", py::arg("data") = true, py::arg("ignore_nan") = false);
m.def("cpp_boruvka_mst_edges", &boruvka_mst_edges, py::arg("G"), py::arg("minimum") = true, py::arg("weight") = "weight", py::arg("data") = true, py::arg("ignore_nan") = false);
m.def("cpp_average_shortest_path_length", &average_shortest_path_length, py::arg("G"), py::arg("weight") = py::none(), py::arg("method") = py::none());
m.def("cpp_eccentricity", &eccentricity, py::arg("G"), py::arg("v") = py::none(), py::arg("sp") = py::none());
m.def("cpp_connected_components_undirected", &connected_component_undirected, py::arg("G"));
m.def("cpp_connected_components_directed", &connected_component_directed, py::arg("G"));
// community methods
m.def("cpp_modularity", &cpp_modularity, py::arg("G"), py::arg("communities"), py::arg("weight") = py::str("weight"));
m.def("cpp_greedy_modularity_communities", &cpp_greedy_modularity_communities, py::arg("G"), py::arg("weight") = py::str("weight"));
m.def("cpp_enumerate_subgraph", &cpp_enumerate_subgraph, py::arg("G"), py::arg("k"));
m.def("cpp_random_enumerate_subgraph", &cpp_random_enumerate_subgraph, py::arg("G"), py::arg("k"), py::arg("cut_prob"));
m.def("cpp_louvain_communities", &cpp_louvain_communities, py::arg("G"), py::arg("weight") = py::str("weight"), py::arg("threshold") = py::float_(0.00002), py::arg("resolution") = py::float_(1.0));
m.def("cpp_louvain_communities_serial", &cpp_louvain_communities_serial, py::arg("G"), py::arg("weight") = py::str("weight"), py::arg("threshold") = py::float_(0.00002), py::arg("resolution") = py::float_(1.0));
m.def("cpp_LPA", &cpp_LPA, py::arg("G"));
m.def("cpp_ego_graph", &cpp_ego_graph, py::arg("G"), py::arg("n"), py::arg("radius") = py::int_(1), py::arg("center") = py::bool_(true), py::arg("undirected") = py::bool_(false), py::arg("distance") = py::none());
m.def("cpp_ego_graph_csr", &cpp_ego_graph_csr, py::arg("G"), py::arg("n"), py::arg("radius") = py::int_(1), py::arg("center") = py::bool_(true), py::arg("undirected") = py::bool_(false), py::arg("distance") = py::none());
m.def("cpp_localsearch", &cpp_localsearch, py::arg("G"), py::arg("center_num") = py::none(), py::arg("auto_choose_centers") = py::bool_(false), py::arg("maximum_tree") = py::bool_(true), py::arg("seed") = py::none(), py::arg("self_loop") = py::bool_(false));
}
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include "components/__init__.h"
#include "basic/__init__.h"
#include "path/__init__.h"
#include "structural_holes/__init__.h"
#include "cores/__init__.h"
#include "centrality/__init__.h"
#include "pagerank/__init__.h"
#include "community/__init__.h"
+4
View File
@@ -0,0 +1,4 @@
#pragma once
#include "avg_degree.h"
#include "cluster.h"
@@ -0,0 +1,9 @@
#include "avg_degree.h"
#include "../../classes/graph.h"
py::object average_degree(py::object G) {
Graph& G_ = G.cast<Graph&>();
int n = G_.node.size();
int m = G.attr("number_of_edges")().cast<int>();
return py::cast(2.0 * m / n);
}
@@ -0,0 +1,5 @@
#pragma once
#include "../../common/common.h"
py::object average_degree(py::object G);
+298
View File
@@ -0,0 +1,298 @@
#include "cluster.h"
#include "../../classes/graph.h"
#include "../../classes/directed_graph.h"
#include "../../common/utils.h"
inline weight_t wt(adj_dict_factory& adj, node_t u, node_t v, std::string weight, weight_t max_weight = 1) {
auto& attr = adj[u][v];
return (attr.count(weight) ? attr[weight] : 1) / max_weight;
}
py::list _weighted_triangles_and_degree(py::object G, py::object nodes, py::object weight) {
std::string weight_key = weight_to_string(weight);
Graph& G_ = G.cast<Graph&>();
auto& adj = G_.adj;
weight_t max_weight = 1;
if (weight.is_none() || G.attr("number_of_edges")().equal(py::cast(0))) {
max_weight = 1;
}
else {
int assigned = 0;
for (auto& u_info : G_.adj) {
for (auto& v_info : u_info.second) {
auto& d = v_info.second;
if (assigned) {
max_weight = std::max(max_weight, d.count(weight_key) ? d[weight_key] : 1);
}
else {
assigned = 1;
max_weight = d.count(weight_key) ? d[weight_key] : 1;
}
}
}
}
py::list nodes_list = py::list(G.attr("nbunch_iter")(nodes));
py::list ret = py::list();
for (int i = 0;i < py::len(nodes_list);i++) {
node_t i_id = (G_.node_to_id[nodes_list[i]]).cast<node_t>();
std::unordered_set<node_t> inbrs, seen;
for (const auto& pair : adj[i_id]) {
inbrs.insert(pair.first);
}
inbrs.erase(i_id);
weight_t weighted_triangles = 0;
for (const auto& j_id : inbrs) {
seen.insert(j_id);
weight_t wij = wt(adj, i_id, j_id, weight_key, max_weight);
for (const auto& k_id : inbrs) {
if (adj[j_id].count(k_id) && !seen.count(k_id)) {
weight_t wjk = wt(adj, j_id, k_id, weight_key, max_weight);
weight_t wki = wt(adj, k_id, i_id, weight_key, max_weight);
weighted_triangles += std::cbrt(wij * wjk * wki);
}
}
}
ret.append(py::make_tuple(G_.id_to_node[py::cast(i_id)], inbrs.size(), 2 * weighted_triangles));
}
return ret;
}
py::list _directed_weighted_triangles_and_degree(py::object G, py::object nodes, py::object weight) {
std::string weight_key = weight_to_string(weight);
DiGraph& G_ = G.cast<DiGraph&>();
auto& adj = G_.adj;
weight_t max_weight = 1;
if (weight.is_none() || G.attr("number_of_edges")().equal(py::cast(0))) {
max_weight = 1;
}
else {
int assigned = 0;
for (auto& u_info : G_.adj) {
for (auto& v_info : u_info.second) {
auto& d = v_info.second;
if (assigned) {
max_weight = std::max(max_weight, d.count(weight_key) ? d[weight_key] : 1);
}
else {
assigned = 1;
max_weight = d.count(weight_key) ? d[weight_key] : 1;
}
}
}
}
py::list nodes_list = py::list(G.attr("nbunch_iter")(nodes));
py::list ret = py::list();
for (int i = 0;i < py::len(nodes_list);i++) {
node_t i_id = (G_.node_to_id[nodes_list[i]]).cast<node_t>();
std::unordered_set<node_t> ipreds, isuccs;
for (const auto& pair : G_.pred[i_id]) {
ipreds.insert(pair.first);
}
ipreds.erase(i_id);
for (const auto& pair : G_.adj[i_id]) {
isuccs.insert(pair.first);
}
isuccs.erase(i_id);
weight_t directed_triangles = 0;
for (const auto& j_id : ipreds) {
for (const auto& k_pair : G_.pred[j_id]) {
node_t k_id = k_pair.first;
if (k_id == j_id) {
continue;
}// jpreds
if (ipreds.count(k_id)) { // ipreds & jpreds
directed_triangles += std::cbrt(wt(adj, j_id, i_id, weight_key, max_weight) * wt(adj, k_id, i_id, weight_key, max_weight) * wt(adj, k_id, j_id, weight_key, max_weight));
}
if (isuccs.count(k_id)) { // isuccs & jpreds
directed_triangles += std::cbrt(wt(adj, j_id, i_id, weight_key, max_weight) * wt(adj, i_id, k_id, weight_key, max_weight) * wt(adj, k_id, j_id, weight_key, max_weight));
}
}
for (const auto& k_pair : G_.adj[j_id]) {
node_t k_id = k_pair.first;
if (k_id == j_id) {
continue;
}// jsuccs
if (ipreds.count(k_id)) { // ipreds & jsuccs
directed_triangles += std::cbrt(wt(adj, j_id, i_id, weight_key, max_weight) * wt(adj, k_id, i_id, weight_key, max_weight) * wt(adj, j_id, k_id, weight_key, max_weight));
}
if (isuccs.count(k_id)) { // isuccs & jsuccs
directed_triangles += std::cbrt(wt(adj, j_id, i_id, weight_key, max_weight) * wt(adj, i_id, k_id, weight_key, max_weight) * wt(adj, j_id, k_id, weight_key, max_weight));
}
}
}
for (const auto& j_id : isuccs) {
for (const auto& k_pair : G_.pred[j_id]) {
node_t k_id = k_pair.first;
if (k_id == j_id) {
continue;
}// jpreds
if (ipreds.count(k_id)) { // ipreds & jpreds
directed_triangles += std::cbrt(wt(adj, i_id, j_id, weight_key, max_weight) * wt(adj, k_id, i_id, weight_key, max_weight) * wt(adj, k_id, j_id, weight_key, max_weight));
}
if (isuccs.count(k_id)) { // isuccs & jpreds
directed_triangles += std::cbrt(wt(adj, i_id, j_id, weight_key, max_weight) * wt(adj, i_id, k_id, weight_key, max_weight) * wt(adj, k_id, j_id, weight_key, max_weight));
}
}
for (const auto& k_pair : G_.adj[j_id]) {
node_t k_id = k_pair.first;
if (k_id == j_id) {
continue;
}// jsuccs
if (ipreds.count(k_id)) { // ipreds & jsuccs
directed_triangles += std::cbrt(wt(adj, i_id, j_id, weight_key, max_weight) * wt(adj, k_id, i_id, weight_key, max_weight) * wt(adj, j_id, k_id, weight_key, max_weight));
}
if (isuccs.count(k_id)) { // isuccs & jsuccs
directed_triangles += std::cbrt(wt(adj, i_id, j_id, weight_key, max_weight) * wt(adj, i_id, k_id, weight_key, max_weight) * wt(adj, j_id, k_id, weight_key, max_weight));
}
}
}
int dtotal = ipreds.size() + isuccs.size();
int dbidirectional = 0;
for (const auto& node : ipreds) {
dbidirectional += isuccs.count(node);
}
ret.append(py::make_tuple(nodes_list[i], dtotal, dbidirectional, directed_triangles));
}
return ret;
}
py::list _triangles_and_degree(py::object G, py::object nodes = py::none()) {
Graph& G_ = G.cast<Graph&>();
auto& adj = G_.adj;
py::list nodes_list = py::list(G.attr("nbunch_iter")(nodes));
py::list ret = py::list();
for (int i = 0;i < py::len(nodes_list);i++) {
node_t v = (G_.node_to_id[nodes_list[i]]).cast<node_t>();
std::unordered_set<node_t> vs;
for (const auto& pair : adj[v]) {
vs.insert(pair.first);
}
vs.erase(v);
weight_t ntriangles = 0;
for (const auto& w : vs) {
for (const auto& node : vs) {
ntriangles += node != w && adj[w].count(node);
}
}
ret.append(py::make_tuple(G_.id_to_node[py::cast(v)], vs.size(), ntriangles));
}
return ret;
}
py::list _directed_triangles_and_degree(py::object G, py::object nodes = py::none()) {
DiGraph& G_ = G.cast<DiGraph&>();
auto& adj = G_.adj;
py::list nodes_list = py::list(G.attr("nbunch_iter")(nodes));
py::list ret = py::list();
for (int i = 0;i < py::len(nodes_list);i++) {
node_t i_id = (G_.node_to_id[nodes_list[i]]).cast<node_t>();
std::unordered_set<node_t> ipreds, isuccs;
for (const auto& pair : G_.pred[i_id]) {
ipreds.insert(pair.first);
}
ipreds.erase(i_id);
for (const auto& pair : G_.adj[i_id]) {
isuccs.insert(pair.first);
}
isuccs.erase(i_id);
weight_t directed_triangles = 0;
for (const auto& j_id : ipreds) {
for (const auto& k_pair : G_.pred[j_id]) {
node_t k_id = k_pair.first;
if (k_id == j_id) {
continue;
}// jpreds
directed_triangles += ipreds.count(k_id); // ipreds & jpreds
directed_triangles += isuccs.count(k_id); // isuccs & jpreds
}
for (const auto& k_pair : G_.adj[j_id]) {
node_t k_id = k_pair.first;
if (k_id == j_id) {
continue;
}// jsuccs
directed_triangles += ipreds.count(k_id); // ipreds & jsuccs
directed_triangles += isuccs.count(k_id); // isuccs & jsuccs
}
}
for (const auto& j_id : isuccs) {
for (const auto& k_pair : G_.pred[j_id]) {
node_t k_id = k_pair.first;
if (k_id == j_id) {
continue;
}// jpreds
directed_triangles += ipreds.count(k_id); // ipreds & jpreds
directed_triangles += isuccs.count(k_id); // isuccs & jpreds
}
for (const auto& k_pair : G_.adj[j_id]) {
node_t k_id = k_pair.first;
if (k_id == j_id) {
continue;
}// jsuccs
directed_triangles += ipreds.count(k_id); // ipreds & jsuccs
directed_triangles += isuccs.count(k_id); // isuccs & jsuccs
}
}
int dtotal = ipreds.size() + isuccs.size();
int dbidirectional = 0;
for (const auto& node : ipreds) {
dbidirectional += isuccs.count(node);
}
ret.append(py::make_tuple(nodes_list[i], dtotal, dbidirectional, directed_triangles));
}
return ret;
}
py::object clustering(py::object G, py::object nodes, py::object weight) {
py::dict clusterc = py::dict();
if (G.attr("is_directed")().cast<bool>()) {
py::list td_list;
if (!weight.is_none()) {
td_list = _directed_weighted_triangles_and_degree(G, nodes, weight);
}
else {
td_list = _directed_triangles_and_degree(G, nodes);
}
for (int i = 0;i < py::len(td_list);i++) {
py::tuple tuple = td_list[i].cast<py::tuple>();
py::object v = tuple[0];
int dt = tuple[1].cast<int>(), db = tuple[2].cast<int>();
weight_t t = tuple[3].cast<weight_t>();
if (t == 0) {
clusterc[v] = 0;
}
else {
clusterc[v] = t / ((dt * (dt - 1) - 2 * db) * 2);
}
}
}
else {
py::list td_list;
if (!weight.is_none()) {
td_list = _weighted_triangles_and_degree(G, nodes, weight);
}
else {
td_list = _triangles_and_degree(G, nodes);
}
for (int i = 0;i < py::len(td_list);i++) {
py::tuple tuple = td_list[i].cast<py::tuple>();
py::object v = tuple[0];
int d = tuple[1].cast<int>();
weight_t t = tuple[2].cast<weight_t>();
if (t == 0) {
clusterc[v] = 0;
}
else {
clusterc[v] = t / (d * (d - 1));
}
}
}
if (G.contains(nodes)) {
return clusterc[nodes];
}
return clusterc;
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include "../../common/common.h"
py::object clustering(py::object G, py::object nodes, py::object weight);
@@ -0,0 +1 @@
#include "centrality.h"
@@ -0,0 +1,467 @@
#ifdef _OPENMP
#include <omp.h>
#endif
#include <vector>
#include <queue>
#include <limits.h>
#include <algorithm>
#include <string>
#include <cstdio>
#include "centrality.h"
#ifdef EASYGRAPH_ENABLE_GPU
#include <gpu_easygraph.h>
#endif
#include "../../classes/graph.h"
#include "../../common/utils.h"
#include "../../classes/linkgraph.h"
namespace py = pybind11;
void betweenness_bfs_worker(
const Graph_L& G_l, const int& S, std::vector<double>& bc, int cutoff, int endpoints_,
std::vector<int>& q, std::vector<int>& dis, std::vector<int>& head_path, std::vector<int>& St,
std::vector<long long>& count_path, std::vector<double>& delta, std::vector<LinkEdge>& E_path,
std::vector<int>& stamp, int& cur_stamp
) {
int N = G_l.n;
int edge_number_path = 0;
int cnt_St = 0;
++cur_stamp;
if ((int)q.size() < N + 1)
q.resize(N + 1);
int front = 0, back = 0;
int cutoff_int = (cutoff < 0) ? -1 : cutoff;
stamp[S] = cur_stamp;
dis[S] = 0;
count_path[S] = 1;
delta[S] = 0.0;
head_path[S] = 0;
q[back++] = S;
const std::vector<int>& head = G_l.head;
const std::vector<LinkEdge>& E = G_l.edges;
while (front < back) {
int u = q[front++];
int du = dis[u];
if (cutoff_int >= 0 && du > cutoff_int)
break;
St[cnt_St++] = u;
for (int p = head[u]; p != -1; p = E[p].next) {
int v = E[p].to;
int new_dis = du + 1;
if (cutoff_int >= 0 && new_dis > cutoff_int)
continue;
if (stamp[v] != cur_stamp) {
stamp[v] = cur_stamp;
dis[v] = new_dis;
count_path[v] = count_path[u];
delta[v] = 0.0;
head_path[v] = 0;
q[back++] = v;
E_path[++edge_number_path].next = head_path[v];
E_path[edge_number_path].to = u;
head_path[v] = edge_number_path;
} else if (dis[v] == new_dis) {
count_path[v] += count_path[u];
E_path[++edge_number_path].next = head_path[v];
E_path[edge_number_path].to = u;
head_path[v] = edge_number_path;
}
}
}
if (endpoints_)
bc[S] += cnt_St - 1;
while (cnt_St > 0) {
int u = St[--cnt_St];
double cu = count_path[u];
if (cu != 0) {
double coeff = (1.0 + delta[u]) / cu;
for (int p = head_path[u]; p; p = E_path[p].next) {
int w = E_path[p].to;
delta[w] += count_path[w] * coeff;
}
}
if (u != S)
bc[u] += delta[u] + endpoints_;
}
}
void betweenness_dijkstra_worker(
const Graph_L& G_l, const int& S, std::vector<double>& bc, double cutoff,
std::vector<int>& dis, std::vector<int>& head_path,
std::vector<int>& St, std::vector<long long>& count_path, std::vector<double>& delta,
std::vector<LinkEdge>& E_path, int endpoints_,
std::vector<int>& stamp, int& cur_stamp
) {
const int dis_inf = 0x3f3f3f3f;
int N = G_l.n;
int edge_number_path = 0;
int cnt_St = 0;
++cur_stamp;
stamp[S] = cur_stamp;
dis[S] = 0;
count_path[S] = 1;
delta[S] = 0.0;
head_path[S] = 0;
std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>, std::greater<std::pair<int, int>>> pq;
pq.push({0, S});
const std::vector<int>& head = G_l.head;
const std::vector<LinkEdge>& E = G_l.edges;
while (!pq.empty()) {
std::pair<int, int> top = pq.top();
pq.pop();
int d = top.first;
int u = top.second;
if (d > dis[u]) continue;
if (cutoff >= 0 && d > cutoff) continue;
St[cnt_St++] = u;
for (int p = head[u]; p != -1; p = E[p].next) {
int v = E[p].to;
int w = E[p].w;
int nd = dis[u] + w;
if (cutoff >= 0 && nd > cutoff) continue;
bool first_visit = (stamp[v] != cur_stamp);
if (first_visit || dis[v] > nd) {
if (first_visit) {
stamp[v] = cur_stamp;
delta[v] = 0.0;
}
dis[v] = nd;
count_path[v] = count_path[u];
head_path[v] = 0;
E_path[++edge_number_path].next = head_path[v];
E_path[edge_number_path].to = u;
head_path[v] = edge_number_path;
pq.push({nd, v});
} else if (dis[v] == nd) {
count_path[v] += count_path[u];
E_path[++edge_number_path].next = head_path[v];
E_path[edge_number_path].to = u;
head_path[v] = edge_number_path;
}
}
}
if (endpoints_)
bc[S] += cnt_St - 1;
while (cnt_St > 0) {
int u = St[--cnt_St];
double cu = count_path[u];
if (cu != 0) {
double coeff = (1.0 + delta[u]) / cu;
for (int p = head_path[u]; p; p = E_path[p].next) {
int w = E_path[p].to;
delta[w] += count_path[w] * coeff;
}
}
if (u != S)
bc[u] += delta[u] + endpoints_;
}
}
static double calc_scale(int len_V, int is_directed, int normalized, int endpoints) {
double scale = 1.0;
if (normalized) {
if (endpoints) {
if (len_V < 2) {
scale = 1.0;
} else {
scale = 1.0 / (double(len_V) * (len_V - 1));
}
} else {
if (len_V <= 2) {
scale = 1.0;
} else {
scale = 1.0 / ((double(len_V) - 1) * (len_V - 2));
}
}
} else {
if (!is_directed) {
scale = 0.5;
} else {
scale = 1.0;
}
}
return scale;
}
static py::object invoke_cpp_betweenness_centrality(
py::object G, py::object weight, py::object cutoff, py::object sources,
py::object normalized, py::object endpoints
) {
Graph& G_ = G.cast<Graph&>();
int cutoff_ = -1;
if (!cutoff.is_none()) {
cutoff_ = cutoff.cast<int>();
}
int N = G_.node.size();
bool is_directed = G.attr("is_directed")().cast<bool>();
int normalized_ = normalized.cast<bool>();
int endpoints_ = endpoints.cast<bool>();
double scale = calc_scale(N, is_directed, normalized_, endpoints_);
bool use_weights = !weight.is_none();
std::string weight_key = "";
if (use_weights) {
weight_key = weight_to_string(weight);
}
Graph_L G_l;
if (G_.linkgraph_dirty) {
G_l = graph_to_linkgraph(G_, is_directed, weight_key, false, false);
G_.linkgraph_structure = G_l;
} else {
G_l = G_.linkgraph_structure;
}
int edges_num = G_l.edges.size();
std::vector<double> bc(N + 1, 0.0);
std::vector<double> BC;
int num_threads = 1;
#ifdef _OPENMP
num_threads = omp_get_max_threads();
#endif
std::vector<std::vector<int>> dis_all(num_threads, std::vector<int>(N + 1));
std::vector<std::vector<int>> head_path_all(num_threads, std::vector<int>(N + 1));
std::vector<std::vector<int>> St_all(num_threads, std::vector<int>(N + 1));
std::vector<std::vector<long long>> count_path_all(num_threads, std::vector<long long>(N + 1));
std::vector<std::vector<double>> delta_all(num_threads, std::vector<double>(N + 1));
std::vector<std::vector<LinkEdge>> E_path_all(num_threads, std::vector<LinkEdge>(edges_num + 1));
std::vector<std::vector<int>> queue_all(num_threads, std::vector<int>(N + 1));
std::vector<std::vector<int>> stamp_all(num_threads, std::vector<int>(N + 1, 0));
std::vector<int> cur_stamp_all(num_threads, 0);
std::vector<std::vector<double>> bc_local_all(num_threads, std::vector<double>(N + 1, 0.0));
if (!sources.is_none()) {
py::list sources_list = py::list(sources);
int sources_list_len = py::len(sources_list);
std::vector<node_t> sources_vec;
sources_vec.reserve(sources_list_len);
for (int i = 0; i < sources_list_len; i++) {
if (G_.node_to_id.attr("get")(sources_list[i], py::none()).is_none()) {
printf("The node should exist in the graph!");
return py::none();
}
sources_vec.push_back(G_.node_to_id.attr("get")(sources_list[i]).cast<node_t>());
}
#ifdef _OPENMP
#pragma omp parallel for schedule(dynamic)
#endif
for (int i = 0; i < sources_list_len; i++) {
node_t source_id = sources_vec[i];
#ifdef _OPENMP
int tid = omp_get_thread_num();
#else
int tid = 0;
#endif
auto& bc_local = bc_local_all[tid];
auto& dis = dis_all[tid];
auto& head_path = head_path_all[tid];
auto& St = St_all[tid];
auto& count_path = count_path_all[tid];
auto& delta = delta_all[tid];
auto& E_path = E_path_all[tid];
auto& q = queue_all[tid];
auto& stamp = stamp_all[tid];
int& cur_stamp = cur_stamp_all[tid];
if (use_weights) {
betweenness_dijkstra_worker(
G_l, source_id, bc_local, cutoff_, dis, head_path,
St, count_path, delta, E_path, endpoints_, stamp, cur_stamp
);
} else {
betweenness_bfs_worker(
G_l, source_id, bc_local, cutoff_, endpoints_, q, dis, head_path,
St, count_path, delta, E_path, stamp, cur_stamp
);
}
}
} else {
#ifdef _OPENMP
#pragma omp parallel for schedule(dynamic)
#endif
for (int i = 1; i <= N; ++i) {
#ifdef _OPENMP
int tid = omp_get_thread_num();
#else
int tid = 0;
#endif
auto& bc_local = bc_local_all[tid];
auto& dis = dis_all[tid];
auto& head_path = head_path_all[tid];
auto& St = St_all[tid];
auto& count_path = count_path_all[tid];
auto& delta = delta_all[tid];
auto& E_path = E_path_all[tid];
auto& q = queue_all[tid];
auto& stamp = stamp_all[tid];
int& cur_stamp = cur_stamp_all[tid];
if (use_weights) {
betweenness_dijkstra_worker(
G_l, i, bc_local, cutoff_, dis, head_path,
St, count_path, delta, E_path, endpoints_, stamp, cur_stamp
);
} else {
betweenness_bfs_worker(
G_l, i, bc_local, cutoff_, endpoints_, q, dis, head_path,
St, count_path, delta, E_path, stamp, cur_stamp
);
}
}
}
#ifdef _OPENMP
#pragma omp parallel for schedule(static)
for (int j = 1; j <= N; ++j) {
double s = 0.0;
for (int tid = 0; tid < num_threads; ++tid)
s += bc_local_all[tid][j];
bc[j] += s;
}
#else
for (int j = 1; j <= N; ++j) {
bc[j] += bc_local_all[0][j];
}
#endif
BC.reserve(N);
for (int i = 1; i <= N; i++) {
BC.push_back(scale * bc[i]);
}
py::array::ShapeContainer ret_shape{(int)BC.size()};
py::array_t<double> ret(ret_shape, BC.data());
return ret;
}
#ifdef EASYGRAPH_ENABLE_GPU
static py::object invoke_gpu_betweenness_centrality(py::object G, py::object weight,
py::object py_sources, py::object normalized, py::object endpoints) {
Graph& G_ = G.cast<Graph&>();
if (weight.is_none()) {
G_.gen_CSR();
} else {
G_.gen_CSR(weight_to_string(weight));
}
auto csr_graph = G_.csr_graph;
std::vector<int>& E = csr_graph->E;
std::vector<int>& V = csr_graph->V;
std::vector<double> *W_p = weight.is_none() ? &(csr_graph->unweighted_W)
: csr_graph->W_map.find(weight_to_string(weight))->second.get();
auto sources = G_.gen_CSR_sources(py_sources);
std::vector<double> BC;
bool is_directed = G.attr("is_directed")().cast<bool>();
int gpu_r = gpu_easygraph::betweenness_centrality(V, E, *W_p, *sources,
is_directed, normalized.cast<py::bool_>(),
endpoints.cast<py::bool_>(), BC);
if (gpu_r != gpu_easygraph::EG_GPU_SUCC) {
// the code below will throw an exception
py::pybind11_fail(gpu_easygraph::err_code_detail(gpu_r));
}
py::array::ShapeContainer ret_shape{(int)BC.size()};
py::array_t<double> ret(ret_shape, BC.data());
return ret;
}
#endif
py::object betweenness_centrality(py::object G, py::object weight, py::object cutoff, py::object sources,
py::object normalized, py::object endpoints) {
#ifdef EASYGRAPH_ENABLE_GPU
return invoke_gpu_betweenness_centrality(G, weight, sources, normalized, endpoints);
#else
return invoke_cpp_betweenness_centrality(G, weight, cutoff, sources, normalized, endpoints);
#endif
}
// void betweenness_dijkstra(const Graph_L& G_l, const int &S, std::vector<double>& bc, double cutoff) {
// int N = G_l.n;
// int edge_number_path = 0;
// __gnu_pbds::priority_queue<compare_node> q;
// std::vector<double> dis(N+1, INFINITY);
// std::vector<bool> vis(N+1, false);
// std::vector<int> head_path(N+1, 0);
// const std::vector<int>& head = G_l.head;
// const std::vector<LinkEdge>& E = G_l.edges;
// int edges_num = E.size();
// std::vector<int> St(N+1, 0);
// std::vector<long long> count_path(N+1, 0);
// std::vector<double> delta(N+1, 0);
// std::vector<LinkEdge> E_path(edges_num+1);
// head_path[S] = 0;
// dis[S] = 0;
// count_path[S] = 1;
// dis[S] = 0;
// count_path[S] = 1;
// q.push(compare_node(S, 0));
// int cnt_St = 0;
// while(!q.empty()) {
// int u = q.top().x;
// q.pop();
// if (vis[u]){
// continue;
// }
// if (cutoff >= 0 && dis[u] > cutoff){
// continue;
// }
// St[cnt_St++] = u;
// vis[u] = true;
// for(int p = head[u]; p != -1; p = E[p].next) {
// int v = E[p].to;
// if(cutoff >= 0 && (dis[u] + E[p].w) > cutoff){
// continue;
// }
// if (dis[v] > dis[u] + E[p].w) {
// dis[v] = dis[u] + E[p].w;
// q.push(compare_node(v, dis[v]));
// count_path[v] = count_path[u];
// head_path[v] = 0;
// E_path[++edge_number_path].next = head_path[v];
// E_path[edge_number_path].to = u;
// head_path[v] = edge_number_path;
// }
// else if (dis[v] == dis[u] + E[p].w) {
// count_path[v] += count_path[u];
// E_path[++edge_number_path].next = head_path[v];
// E_path[edge_number_path].to = u;
// head_path[v] = edge_number_path;
// }
// }
// }
// while (cnt_St > 0) {
// int u = St[--cnt_St];
// float coeff = (1.0 + delta[u]) / count_path[u];
// for(int p = head_path[u]; p; p = E_path[p].next){
// delta[E_path[p].to] += count_path[E_path[p].to] * coeff;
// }
// if (u != S)
// bc[u] += delta[u];
// }
// }
@@ -0,0 +1,27 @@
#pragma once
#include "../../common/common.h"
py::object closeness_centrality(py::object G, py::object weight, py::object cutoff, py::object sources);
py::object betweenness_centrality(py::object G, py::object weight, py::object cutoff, py::object sources,
py::object normalized, py::object endpoints);
py::object cpp_katz_centrality(
py::object G,
py::object py_alpha,
py::object py_beta,
py::object py_max_iter,
py::object py_tol,
py::object py_normalized
);
py::object degree_centrality(py::object G);
py::object in_degree_centrality(py::object G);
py::object out_degree_centrality(py::object G);
py::object cpp_eigenvector_centrality(
py::object G,
py::object py_max_iter,
py::object py_tol,
py::object py_nstart,
py::object py_weight
);
@@ -0,0 +1,353 @@
#include "centrality.h"
#ifdef EASYGRAPH_ENABLE_GPU
#include <gpu_easygraph.h>
#endif
#include "../../classes/graph.h"
#include "../../common/utils.h"
#include "../../classes/linkgraph.h"
#include <queue>
#include <vector>
#include <limits>
#include <cmath>
#ifdef _OPENMP
#include <omp.h>
#endif
// Heap node: use negative value + max heap to implement min heap
typedef std::pair<float, int> HeapNode;
// Optimized adjacency list cache
struct FastAdjCache {
std::vector<int*> neighbor_ptrs;
std::vector<int> neighbor_counts;
std::vector<float*> weight_ptrs;
std::vector<std::vector<int>> neighbor_storage;
std::vector<std::vector<float>> weight_storage;
void init(int N) {
neighbor_ptrs.resize(N + 1, nullptr);
neighbor_counts.resize(N + 1, 0);
neighbor_storage.resize(N + 1);
}
void init_with_weights(int N) {
init(N);
weight_ptrs.resize(N + 1, nullptr);
weight_storage.resize(N + 1);
}
inline void build_if_needed(int u, const std::vector<int>& head,
const std::vector<LinkEdge>& edges, bool store_weights) {
if (neighbor_ptrs[u] != nullptr) return;
std::vector<int>& neis = neighbor_storage[u];
for (int p = head[u]; p != -1; p = edges[p].next) {
neis.push_back(edges[p].to);
if (store_weights) {
weight_storage[u].push_back(edges[p].w);
}
}
neighbor_counts[u] = neis.size();
neighbor_ptrs[u] = neis.data();
if (store_weights) {
weight_ptrs[u] = weight_storage[u].data();
}
}
inline int* get_neighbors_ptr(int u) const { return neighbor_ptrs[u]; }
inline int get_neighbor_count(int u) const { return neighbor_counts[u]; }
inline float* get_weights_ptr(int u) const { return weight_ptrs[u]; }
};
// BFS implementation - directly use raw adjacency list
double closeness_bfs_direct(const Graph_L& G_l, const int &S, int cutoff,
std::vector<int>& already_counted,
std::vector<int>& queue_storage,
int timestamp) {
int N = G_l.n;
const std::vector<LinkEdge>& E = G_l.edges;
const std::vector<int>& head = G_l.head;
int nodes_reached = 0;
long long sum_dis = 0;
queue_storage.clear();
int queue_front = 0;
already_counted[S] = timestamp;
queue_storage.push_back(S);
queue_storage.push_back(0);
while (queue_front < static_cast<int>(queue_storage.size())) {
int u = queue_storage[queue_front++];
int actdist = queue_storage[queue_front++];
if (cutoff >= 0 && actdist > cutoff) {
continue;
}
sum_dis += actdist;
nodes_reached++;
for (int p = head[u]; p != -1; p = E[p].next) {
int v = E[p].to;
if (already_counted[v] == timestamp) {
continue;
}
already_counted[v] = timestamp;
queue_storage.push_back(v);
queue_storage.push_back(actdist + 1);
}
}
if (nodes_reached == 1)
return 0.0;
else
return 1.0 * (nodes_reached - 1) * (nodes_reached - 1) / ((N - 1) * sum_dis);
}
// Check if the graph is unweighted
inline bool is_unweighted_graph(const Graph_L& G_l) {
const std::vector<LinkEdge>& E = G_l.edges;
for (const auto& edge : E) {
if (std::abs(edge.w - 1.0f) > 1e-9) {
return false;
}
}
return true;
}
// Dijkstra implementation - use on-demand adjacency cache
double closeness_dijkstra_cached(const Graph_L& G_l, const int &S, int cutoff,
std::vector<float>& dist,
std::vector<int>& which,
FastAdjCache& cache,
int timestamp) {
int N = G_l.n;
const std::vector<LinkEdge>& E = G_l.edges;
const std::vector<int>& head = G_l.head;
int nodes_reached = 0;
double sum_dis = 0.0;
std::priority_queue<HeapNode> heap;
dist[S] = 1.0f;
which[S] = timestamp;
heap.push({-1.0f, S});
while (!heap.empty()) {
HeapNode top = heap.top();
heap.pop();
float mindist = -top.first;
int minnei = top.second;
if (mindist > dist[minnei]) {
continue;
}
float actual_dist = mindist - 1.0f;
if (cutoff >= 0 && actual_dist > cutoff) {
continue;
}
sum_dis += actual_dist;
nodes_reached++;
cache.build_if_needed(minnei, head, E, true);
int* neis = cache.get_neighbors_ptr(minnei);
float* ws = cache.get_weights_ptr(minnei);
int nlen = cache.get_neighbor_count(minnei);
for (int j = 0; j < nlen; j++) {
int to = neis[j];
float altdist = mindist + ws[j];
float curdist = dist[to];
if (which[to] != timestamp) {
which[to] = timestamp;
dist[to] = altdist;
heap.push({-altdist, to});
} else if (curdist == 0.0f || altdist < curdist) {
dist[to] = altdist;
heap.push({-altdist, to});
}
}
}
if (nodes_reached == 1)
return 0.0;
else
return 1.0 * (nodes_reached - 1) * (nodes_reached - 1) / ((N - 1) * sum_dis);
}
static py::object invoke_cpp_closeness_centrality(py::object G, py::object weight,
py::object cutoff, py::object sources) {
Graph& G_ = G.cast<Graph&>();
int N = G_.node.size();
bool is_directed = G.attr("is_directed")().cast<bool>();
std::string weight_key = weight_to_string(weight);
const Graph_L& G_l = graph_to_linkgraph(G_, is_directed, weight_key, false, false);
int cutoff_ = -1;
if (!cutoff.is_none()){
cutoff_ = cutoff.cast<int>();
}
// Auto algorithm selection
bool use_bfs = (weight.is_none() || is_unweighted_graph(G_l));
std::vector<double> CC;
if(!sources.is_none()){
py::list sources_list = py::list(sources);
int sources_list_len = py::len(sources_list);
CC.resize(sources_list_len);
// Collect all source node IDs
std::vector<node_t> source_ids(sources_list_len);
for(int i = 0; i < sources_list_len; i++){
if(G_.node_to_id.attr("get")(sources_list[i],py::none()).is_none()){
printf("The node should exist in the graph!");
return py::none();
}
source_ids[i] = G_.node_to_id.attr("get")(sources_list[i]).cast<node_t>();
}
// OpenMP parallel computation
// Only enable parallelism when sources are many to avoid overhead
#pragma omp parallel if(sources_list_len > 100)
{
// Per-thread data structures (avoid race conditions)
std::vector<int> already_counted(N + 1, 0);
std::vector<int> queue_storage;
queue_storage.reserve(N * 2);
std::vector<float> dist(N + 1, 0.0f);
std::vector<int> which(N + 1, 0);
FastAdjCache cache;
if (!use_bfs) {
cache.init_with_weights(N);
}
// Assign unique timestamp start for each thread
#ifdef _OPENMP
int thread_id = omp_get_thread_num();
int num_threads = omp_get_num_threads();
int timestamp = thread_id * sources_list_len;
#else
int timestamp = 0;
#endif
// Parallel loop: each thread handles different source node
#pragma omp for schedule(dynamic, 1)
for(int i = 0; i < sources_list_len; i++){
timestamp++;
double res;
if (use_bfs) {
res = closeness_bfs_direct(G_l, source_ids[i], cutoff_,
already_counted, queue_storage, timestamp);
} else {
res = closeness_dijkstra_cached(G_l, source_ids[i], cutoff_,
dist, which, cache, timestamp);
}
CC[i] = res;
}
}
}
else{
CC.resize(N);
// OpenMP parallel computation for all nodes
// Only enable parallelism when node count is large
#pragma omp parallel if(N > 100)
{
// Per-thread data structures
std::vector<int> already_counted(N + 1, 0);
std::vector<int> queue_storage;
queue_storage.reserve(N * 2);
std::vector<float> dist(N + 1, 0.0f);
std::vector<int> which(N + 1, 0);
FastAdjCache cache;
if (!use_bfs) {
cache.init_with_weights(N);
}
// Assign unique timestamp start for each thread
#ifdef _OPENMP
int thread_id = omp_get_thread_num();
int num_threads = omp_get_num_threads();
int timestamp = thread_id * N;
#else
int timestamp = 0;
#endif
// Parallel loop: dynamic scheduling for load balancing
#pragma omp for schedule(dynamic, 10)
for(int i = 1; i <= N; i++){
timestamp++;
double res;
if (use_bfs) {
res = closeness_bfs_direct(G_l, i, cutoff_,
already_counted, queue_storage, timestamp);
} else {
res = closeness_dijkstra_cached(G_l, i, cutoff_,
dist, which, cache, timestamp);
}
CC[i - 1] = res;
}
}
}
py::array::ShapeContainer ret_shape{(int)CC.size()};
py::array_t<double> ret(ret_shape, CC.data());
return ret;
}
#ifdef EASYGRAPH_ENABLE_GPU
static py::object invoke_gpu_closeness_centrality(py::object G, py::object weight, py::object py_sources) {
Graph& G_ = G.cast<Graph&>();
if (weight.is_none()) {
G_.gen_CSR();
} else {
G_.gen_CSR(weight_to_string(weight));
}
auto csr_graph = G_.csr_graph;
std::vector<int>& E = csr_graph->E;
std::vector<int>& V = csr_graph->V;
std::vector<double> *W_p = weight.is_none() ? &(csr_graph->unweighted_W)
: csr_graph->W_map.find(weight_to_string(weight))->second.get();
auto sources = G_.gen_CSR_sources(py_sources);
std::vector<double> CC;
int gpu_r = gpu_easygraph::closeness_centrality(V, E, *W_p, *sources, CC);
if (gpu_r != gpu_easygraph::EG_GPU_SUCC) {
// the code below will throw an exception
py::pybind11_fail(gpu_easygraph::err_code_detail(gpu_r));
}
py::array::ShapeContainer ret_shape{(int)CC.size()};
py::array_t<double> ret(ret_shape, CC.data());
return ret;
}
#endif
py::object closeness_centrality(py::object G, py::object weight, py::object cutoff, py::object sources) {
#ifdef EASYGRAPH_ENABLE_GPU
return invoke_gpu_closeness_centrality(G, weight, sources);
#else
return invoke_cpp_closeness_centrality(G, weight, cutoff, sources);
#endif
}
@@ -0,0 +1,96 @@
#include "centrality.h"
#include "../../classes/graph.h"
#include "../../classes/directed_graph.h"
#include "../../common/utils.h"
#include "../../classes/linkgraph.h"
namespace py = pybind11;
py::object degree_centrality(
py::object G
) {
Graph* graph = G.cast<Graph*>();
py::dict centrality_map = py::dict();
py::object nodes = graph->get_nodes();
int n = py::len(nodes);
if (n <= 1) {
for (const auto& node_handle : nodes) {
centrality_map[node_handle] = 0.0;
}
return centrality_map;
}
double scale = 1.0 / (n - 1);
std::string class_name = G.attr("__class__").attr("__name__").cast<std::string>();
if (class_name == "DiGraphC") {
// 有向图 (DiGraph)
DiGraph* digraph = G.cast<DiGraph*>();
py::object adj = digraph->get_adj();
py::object pred = digraph->get_pred();
for (const auto& node_handle : nodes) {
int out_deg = py::len(adj[node_handle]);
int in_deg = py::len(pred[node_handle]);
centrality_map[node_handle] = (double)(out_deg + in_deg) * scale;
}
} else {
py::object adj = graph->get_adj();
for (const auto& node_handle : nodes) {
int degree = py::len(adj[node_handle]);
centrality_map[node_handle] = (double)degree * scale;
}
}
return centrality_map;
}
py::object in_degree_centrality(
py::object G
) {
DiGraph* graph = G.cast<DiGraph*>();
py::dict centrality_map = py::dict();
py::object nodes = graph->get_nodes();
int n = py::len(nodes);
if (n <= 1) {
return centrality_map;
}
double scale = 1.0 / (n - 1);
py::object pred = graph->get_pred();
for (const auto& node_handle : nodes) {
int in_degree = py::len(pred[node_handle]);
centrality_map[node_handle] = in_degree * scale;
}
return centrality_map;
}
py::object out_degree_centrality(
py::object G
) {
Graph* graph = G.cast<Graph*>();
py::dict centrality_map = py::dict();
py::object nodes = graph->get_nodes();
int n = py::len(nodes);
if (n <= 1) {
return centrality_map;
}
double scale = 1.0 / (n - 1);
py::object adj = graph->get_adj();
for (const auto& node_handle : nodes) {
int out_degree = py::len(adj[node_handle]);
centrality_map[node_handle] = out_degree * scale;
}
return centrality_map;
}
@@ -0,0 +1,198 @@
#include <vector>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
#ifdef _OPENMP
#include <omp.h>
#endif
#include "../../classes/graph.h"
#include "../../common/utils.h"
namespace py = pybind11;
class CSRMatrix {
public:
std::vector<int> indptr;
std::vector<int> indices;
std::vector<double> data; // Empty if unweighted (all 1.0)
int rows, cols;
bool is_weighted;
CSRMatrix(int r, int c) : rows(r), cols(c), is_weighted(false) {
indptr.assign(r + 1, 0);
}
};
// Power iteration with branch optimization for weighted/unweighted paths
std::vector<double> power_iteration_optimized(
const CSRMatrix& A,
int max_iter,
double tol,
std::vector<double>& x
) {
const int n = A.rows;
std::vector<double> x_next(n);
bool use_weight = A.is_weighted && !A.data.empty();
// Initial normalization
double norm = 0.0;
#pragma omp parallel for reduction(+:norm)
for (int i = 0; i < n; ++i) norm += x[i] * x[i];
norm = std::sqrt(norm);
if (norm < 1e-12) {
std::fill(x.begin(), x.end(), 1.0 / std::sqrt(n));
} else {
double inv_norm = 1.0 / norm;
#pragma omp parallel for
for (int i = 0; i < n; ++i) x[i] *= inv_norm;
}
double delta = tol + 1.0;
for (int iter = 0; iter < max_iter && delta >= tol; ++iter) {
double next_norm_sq = 0.0;
#pragma omp parallel for reduction(+:next_norm_sq) schedule(dynamic, 64)
for (int i = 0; i < n; ++i) {
double sum = 0.0;
const int start = A.indptr[i];
const int end = A.indptr[i+1];
if (use_weight) {
for (int j = start; j < end; ++j) {
sum += A.data[j] * x[A.indices[j]];
}
} else {
for (int j = start; j < end; ++j) {
sum += x[A.indices[j]];
}
}
x_next[i] = sum;
next_norm_sq += sum * sum;
}
double next_norm = std::sqrt(next_norm_sq);
if (next_norm < 1e-12) break;
double inv_next_norm = 1.0 / next_norm;
delta = 0.0;
#pragma omp parallel for reduction(+:delta) schedule(static)
for (int i = 0; i < n; ++i) {
double val = x_next[i] * inv_next_norm;
delta += std::abs(val - x[i]);
x_next[i] = val;
}
x.swap(x_next);
}
return x;
}
// Build transpose CSR with fallback logic for missing weight keys
CSRMatrix build_transpose_matrix_smart(Graph& graph, const std::vector<node_t>& nodes, const std::string& weight_key) {
std::shared_ptr<CSRGraph> csr_ptr = weight_key.empty() ? graph.gen_CSR() : graph.gen_CSR(weight_key);
int n = static_cast<int>(nodes.size());
CSRMatrix At(n, n);
if (!csr_ptr) return At;
const auto& src_indptr = csr_ptr->V;
const auto& src_indices = csr_ptr->E;
std::vector<double> src_data;
bool actually_weighted = false;
// Detect if weighted calculation is required
if (!weight_key.empty()) {
auto it = csr_ptr->W_map.find(weight_key);
if (it != csr_ptr->W_map.end() && it->second) {
src_data = *(it->second);
for (double w : src_data) {
if (std::abs(w - 1.0) > 1e-9) {
actually_weighted = true;
break;
}
}
}
}
At.is_weighted = actually_weighted;
// Calculate row counts for transpose
for (int x_idx : src_indices) {
if (x_idx >= 0 && x_idx < n) At.indptr[x_idx + 1]++;
}
for (int i = 0; i < n; ++i) At.indptr[i + 1] += At.indptr[i];
At.indices.resize(src_indices.size());
if (actually_weighted) At.data.resize(src_indices.size());
std::vector<int> cur_pos(At.indptr.begin(), At.indptr.end());
// Populate transpose CSR data
for (int r = 0; r < n; ++r) {
for (int p = src_indptr[r]; p < src_indptr[r+1]; ++p) {
int c = src_indices[p];
if (c < 0 || c >= n) continue;
int dest = cur_pos[c]++;
At.indices[dest] = r;
if (actually_weighted) At.data[dest] = src_data[p];
}
}
return At;
}
py::object cpp_eigenvector_centrality(
py::object G,
py::object py_max_iter,
py::object py_tol,
py::object py_nstart,
py::object py_weight
) {
try {
Graph& graph = G.cast<Graph&>();
int max_iter = py_max_iter.cast<int>();
double tol = py_tol.cast<double>();
std::string weight_key = py_weight.is_none() ? "" : py_weight.cast<std::string>();
if (graph.node.empty()) return py::dict();
std::vector<node_t> nodes;
for (auto& pair : graph.node) nodes.push_back(pair.first);
int n = nodes.size();
CSRMatrix A_transpose = build_transpose_matrix_smart(graph, nodes, weight_key);
// Initialize x vector (prefer degree-based or uniform)
std::vector<double> x(n, 1.0 / n);
if (!py_nstart.is_none()) {
py::dict nstart = py_nstart.cast<py::dict>();
for (int i = 0; i < n; i++) {
py::object node_obj = graph.id_to_node[py::cast(nodes[i])];
if (nstart.contains(node_obj)) x[i] = nstart[node_obj].cast<double>();
}
} else {
for (int i = 0; i < n; i++) {
int degree = A_transpose.indptr[i+1] - A_transpose.indptr[i];
x[i] = (degree > 0) ? (double)degree : 1.0/n;
}
}
std::vector<double> res = power_iteration_optimized(A_transpose, max_iter, tol, x);
py::dict result;
for (int i = 0; i < n; i++) {
py::object node_obj = graph.id_to_node[py::cast(nodes[i])];
result[node_obj] = res[i];
}
return result;
} catch (const std::exception& e) {
throw std::runtime_error(std::string("C++ Eigenvector Error: ") + e.what());
}
}
@@ -0,0 +1,194 @@
#ifdef _OPENMP
#include <omp.h>
#endif
#include <vector>
#include <cmath>
#include <algorithm>
#include <stdexcept>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "centrality.h"
#include "../../classes/graph.h"
namespace py = pybind11;
class CSRMatrix {
public:
std::vector<int> indptr; // size rows+1
std::vector<int> indices; // size nnz
std::vector<double> data; // size nnz
int rows = 0;
int cols = 0;
CSRMatrix() = default;
CSRMatrix(int r, int c) : rows(r), cols(c) {
indptr.assign(r + 1, 0);
}
};
// Build transpose CSR from EasyGraph CSR so that row i contains in-neighbors of i.
static CSRMatrix build_transpose_matrix_from_csr(const std::shared_ptr<CSRGraph>& csr_ptr) {
if (!csr_ptr) return CSRMatrix();
const int n = static_cast<int>(csr_ptr->nodes.size());
if (n == 0) return CSRMatrix(0, 0);
const auto& src_indptr = csr_ptr->V;
const auto& src_indices = csr_ptr->E;
// Unweighted: all ones.
std::vector<double> src_data(src_indices.size(), 1.0);
CSRMatrix At(n, n);
// Count nnz per column in the source (becomes nnz per row in transpose).
for (int c : src_indices) {
if (c >= 0 && c < n) At.indptr[c + 1]++;
}
// Prefix sum.
for (int i = 0; i < n; ++i) {
At.indptr[i + 1] += At.indptr[i];
}
const int nnz = static_cast<int>(src_indices.size());
At.indices.resize(nnz);
At.data.resize(nnz);
std::vector<int> cur_pos(At.indptr.begin(), At.indptr.end());
// Fill transpose.
for (int r = 0; r < n; ++r) {
const int start = src_indptr[r];
const int end = src_indptr[r + 1];
for (int p = start; p < end; ++p) {
const int c = src_indices[p];
if (c < 0 || c >= n) continue;
const int dest = cur_pos[c]++;
At.indices[dest] = r;
At.data[dest] = src_data[p];
}
}
return At;
}
static std::vector<double> katz_centrality_omp(const CSRMatrix& A,
double alpha,
const std::vector<double>& beta,
int max_iters,
double tol,
bool normalize) {
const int n = A.rows;
std::vector<double> x(n, 1.0); // initial guess
std::vector<double> x_next(n, 0.0); // next iterate
if (n == 0) return x;
for (int iter = 0; iter < max_iters; ++iter) {
double err_sq = 0.0;
double norm_sq = 0.0;
// SpMV + Katz update + error and norm in ONE pass
#pragma omp parallel for reduction(+ : err_sq, norm_sq) schedule(static)
for (int i = 0; i < n; ++i) {
double sum = 0.0;
const int row_start = A.indptr[i];
const int row_end = A.indptr[i + 1];
for (int e = row_start; e < row_end; ++e) {
sum += A.data[e] * x[A.indices[e]];
}
const double new_val = alpha * sum + beta[i];
const double diff = new_val - x[i];
x_next[i] = new_val;
err_sq += diff * diff;
norm_sq += new_val * new_val;
}
const double err = std::sqrt(err_sq);
const double norm = std::sqrt(norm_sq);
x.swap(x_next);
if (norm > 0.0 && (err / norm) < tol) {
break;
}
}
if (normalize) {
double norm_sq2 = 0.0;
#pragma omp parallel for reduction(+ : norm_sq2) schedule(static)
for (int i = 0; i < n; ++i) {
norm_sq2 += x[i] * x[i];
}
const double norm = std::sqrt(norm_sq2);
if (norm > 0.0) {
#pragma omp parallel for schedule(static)
for (int i = 0; i < n; ++i) {
x[i] /= norm;
}
}
}
return x;
}
py::object cpp_katz_centrality(py::object G,
py::object py_alpha,
py::object py_beta,
py::object py_max_iter,
py::object py_tol,
py::object py_normalized) {
Graph& graph = G.cast<Graph&>();
const double alpha = py_alpha.cast<double>();
const int max_iter = py_max_iter.cast<int>();
const double tol = py_tol.cast<double>();
const bool normalized = py_normalized.cast<bool>();
std::shared_ptr<CSRGraph> csr_ptr = graph.gen_CSR();
if (!csr_ptr || csr_ptr->nodes.empty()) {
return py::dict();
}
const int n = static_cast<int>(csr_ptr->nodes.size());
// Build transpose CSR so that we accumulate from in-neighbors.
CSRMatrix A = build_transpose_matrix_from_csr(csr_ptr);
// Process beta parameter: scalar or dict(node->beta).
std::vector<double> beta(n, 1.0);
if (py::isinstance<py::float_>(py_beta) || py::isinstance<py::int_>(py_beta)) {
const double beta_val = py_beta.cast<double>();
#pragma omp parallel for schedule(static)
for (int i = 0; i < n; ++i) {
beta[i] = beta_val;
}
} else if (py::isinstance<py::dict>(py_beta)) {
py::dict beta_dict = py_beta.cast<py::dict>();
for (int i = 0; i < n; ++i) {
node_t internal_id = csr_ptr->nodes[i];
py::object node_obj = graph.id_to_node[py::cast(internal_id)];
if (beta_dict.contains(node_obj)) {
beta[i] = beta_dict[node_obj].cast<double>();
}
}
} else {
throw py::type_error("beta must be a float/int or a dict");
}
std::vector<double> scores = katz_centrality_omp(A, alpha, beta, max_iter, tol, normalized);
// Prepare results
py::dict result;
for (int i = 0; i < n; ++i) {
node_t internal_id = csr_ptr->nodes[i];
py::object node_obj = graph.id_to_node[py::cast(internal_id)];
result[node_obj] = scores[i];
}
return result;
}
+146
View File
@@ -0,0 +1,146 @@
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <random>
#include <queue>
#include <cstdint>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "../../classes/graph.h"
#include "../../classes/linkgraph.h"
#include "../../common/utils.h"
namespace py = pybind11;
using namespace std;
py::object cpp_LPA(py::object G) {
Graph& G_ = G.cast<Graph&>();
Graph_L linkgraph = G_._get_linkgraph_structure();
int n = linkgraph.n;
py::dict id_to_node = G_.id_to_node;
if (n <= 1) {
py::dict result;
if (n == 1) {
py::list node_list;
node_list.append(id_to_node[py::cast(1)]);
result[py::cast(1)] = node_list;
}
return result;
}
vector<vector<int>> adj(n + 1);
for (int u = 1; u <= n; ++u) {
for (int e = linkgraph.head[u]; e != -1; e = linkgraph.edges[e].next) {
int v = linkgraph.edges[e].to;
if (u != v) {
adj[u].push_back(v);
}
}
}
for (int u = 1; u <= n; ++u) {
if (adj[u].size() > 1) {
sort(adj[u].begin(), adj[u].end());
adj[u].erase(unique(adj[u].begin(), adj[u].end()), adj[u].end());
}
}
vector<int> labels(n);
vector<int> nodes(n);
for (int i = 0; i < n; ++i) {
labels[i] = i;
nodes[i] = i + 1;
}
random_device rd;
mt19937 gen(rd());
const int MAX_ITERATIONS = 1000;
int iteration_count = 0;
vector<int> label_count(n, 0);
vector<int> active_labels;
active_labels.reserve(128);
while (iteration_count < MAX_ITERATIONS) {
iteration_count++;
shuffle(nodes.begin(), nodes.end(), gen);
bool changed = false;
for (int node : nodes) {
int max_count = 0;
for (int neighbor : adj[node]) {
int neighbor_label = labels[neighbor - 1];
if (label_count[neighbor_label] == 0) {
active_labels.push_back(neighbor_label);
}
label_count[neighbor_label]++;
if (label_count[neighbor_label] > max_count) {
max_count = label_count[neighbor_label];
}
}
if (max_count > 0) {
int current_label = labels[node - 1];
vector<int> best_labels;
for (int label : active_labels) {
if (label_count[label] == max_count) {
best_labels.push_back(label);
}
}
bool current_is_best = false;
for (int label : best_labels) {
if (label == current_label) {
current_is_best = true;
break;
}
}
if (!current_is_best) {
changed = true;
if (best_labels.size() == 1) {
labels[node - 1] = best_labels[0];
} else {
uniform_int_distribution<> dis(0, best_labels.size() - 1);
labels[node - 1] = best_labels[dis(gen)];
}
}
}
for (int label : active_labels) {
label_count[label] = 0;
}
active_labels.clear();
}
if (!changed) {
break;
}
}
unordered_map<int, vector<int>> label_to_nodes;
for (int i = 0; i < n; ++i) {
int label = labels[i];
label_to_nodes[label].push_back(i + 1);
}
py::dict result;
int community_id = 1;
for (const auto& pair : label_to_nodes) {
py::list node_list;
for (int internal_id : pair.second) {
node_list.append(id_to_node[py::cast(internal_id)]);
}
result[py::cast(community_id)] = node_list;
community_id++;
}
return result;
}
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <pybind11/pybind11.h>
namespace py = pybind11;
py::object cpp_LPA(py::object G);
@@ -0,0 +1,10 @@
#pragma once
#include "modularity.h"
#include "greedy_modularity.h"
#include "motif.h"
#include "louvain.h"
#include "LPA.h"
#include "ego_graph.h"
#include "graph_coloring.h"
#include "localsearch.h"
@@ -0,0 +1,155 @@
#include "ego_graph.h"
#include "subgraph.h"
#include "../../classes/graph.h"
#include "../../classes/directed_graph.h"
#include "../../classes/csr_graph.h"
#include "../../common/utils.h"
#include "../../classes/linkgraph.h"
#include "../path/path.h"
#include <algorithm>
struct _EgoGraphCore {
Graph_L G_l;
std::vector<node_t> node_ids;
std::unordered_map<node_t, int> node_to_idx;
py::dict id_to_node_py;
bool has_error = false;
};
static _EgoGraphCore _cpp_ego_graph_compute_impl(
Graph& G_,
py::object n,
double radius_val,
bool center_val,
bool undirected_val,
bool is_directed,
const std::string& weight_key) {
_EgoGraphCore out;
if (G_.node_to_id.contains(n) == 0) {
PyErr_Format(PyExc_KeyError, "Node %R is not in the graph.", n.ptr());
out.has_error = true;
return out;
}
node_t center_id = G_.node_to_id[n].cast<node_t>();
out.id_to_node_py = G_.id_to_node;
if (G_.linkgraph_dirty || G_.linkgraph_structure.max_deg == -1) {
if (undirected_val) {
out.G_l = graph_to_linkgraph(G_, false, weight_key, true, false);
} else {
out.G_l = graph_to_linkgraph(G_, is_directed, weight_key, true, false);
}
G_.linkgraph_dirty = false;
G_.linkgraph_structure = out.G_l; // also cache the freshly built linkgraph
} else {
out.G_l = G_.linkgraph_structure;
}
std::vector<double> dist = _dijkstra(out.G_l, center_id, -1);
int N = out.G_l.n;
for (int i = 1; i <= N; i++) {
if (dist[i] > radius_val) continue;
if (!center_val && i == (int)center_id) continue;
out.node_to_idx[i] = (int)out.node_ids.size();
out.node_ids.push_back(i);
}
return out;
}
static _EgoGraphCore _cpp_ego_graph_compute(
py::object G,
py::object n,
py::object radius,
py::object center,
py::object undirected,
py::object distance) {
bool is_directed = G.attr("is_directed")().cast<bool>();
bool center_val = center.cast<bool>();
bool undirected_val = undirected.cast<bool>();
double radius_val = radius.cast<double>();
std::string weight_key = weight_to_string(distance);
if (is_directed) {
DiGraph& G_ = G.cast<DiGraph&>();
return _cpp_ego_graph_compute_impl(
G_, n, radius_val, center_val, undirected_val, is_directed, weight_key);
} else {
Graph& G_ = G.cast<Graph&>();
return _cpp_ego_graph_compute_impl(
G_, n, radius_val, center_val, undirected_val, is_directed, weight_key);
}
}
py::object cpp_ego_graph(
py::object G,
py::object n,
py::object radius,
py::object center,
py::object undirected,
py::object distance) {
_EgoGraphCore core = _cpp_ego_graph_compute(G, n, radius, center, undirected, distance);
if (core.has_error) return py::none();
return nodes_subgraph_cpp(G, core.node_ids);
}
py::object cpp_ego_graph_csr(
py::object G,
py::object n,
py::object radius,
py::object center,
py::object undirected,
py::object distance) {
_EgoGraphCore core = _cpp_ego_graph_compute(G, n, radius, center, undirected, distance);
if (core.has_error) return py::none();
int n_nodes = (int)core.node_ids.size();
if (n_nodes == 0) {
return py::dict();
}
std::vector<int> V(n_nodes + 1, 0);
std::vector<int> E;
std::vector<double> W;
for (int new_u = 0; new_u < n_nodes; new_u++) {
node_t u = core.node_ids[new_u];
V[new_u + 1] = V[new_u];
int edge_idx = core.G_l.head[u];
while (edge_idx != -1 && edge_idx < core.G_l.e) {
node_t v = core.G_l.edges[edge_idx].to;
auto it = core.node_to_idx.find(v);
if (it != core.node_to_idx.end()) {
E.push_back(it->second);
W.push_back(core.G_l.edges[edge_idx].w);
V[new_u + 1]++;
}
edge_idx = core.G_l.edges[edge_idx].next;
}
}
py::list original_nodes;
for (node_t internal_id : core.node_ids) {
original_nodes.append(core.id_to_node_py[py::cast(internal_id)]);
}
py::dict result;
result["nodes"] = original_nodes;
result["V"] = py::cast(V);
result["E"] = py::cast(E);
result["W"] = py::cast(W);
return result;
}
@@ -0,0 +1,23 @@
#pragma once
#include <pybind11/pybind11.h>
namespace py = pybind11;
py::object cpp_ego_graph(
py::object G,
py::object n,
py::object radius = py::int_(1),
py::object center = py::bool_(true),
py::object undirected = py::bool_(false),
py::object distance = py::none()
);
py::object cpp_ego_graph_csr(
py::object G,
py::object n,
py::object radius = py::int_(1),
py::object center = py::bool_(true),
py::object undirected = py::bool_(false),
py::object distance = py::none()
);
@@ -0,0 +1,135 @@
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <iostream>
#include <random>
#include <numeric>
#include <cstdint>
#include <chrono>
#include <queue>
#include <cstring>
#ifdef _OPENMP
#include <omp.h>
#else
#warning "OpenMP is not available: omp_graph_coloring will fall back to single-threaded execution."
#endif
#include "../../classes/linkgraph.h"
using namespace std;
vector<int> greedy_graph_coloring(const Graph_L& G) {
int n = G.n;
if (n == 0) return vector<int>();
vector<int> degree(n + 1, 0);
for (int v = 1; v <= n; ++v) {
for (int e = G.head[v]; e != -1; e = G.edges[e].next) {
degree[v]++;
}
}
vector<int> nodes(n);
iota(nodes.begin(), nodes.end(), 1);
sort(nodes.begin(), nodes.end(), [&degree](int a, int b) {
return degree[a] > degree[b];
});
vector<int> colors(n, -1);
vector<int> used(n + 1, -1);
for (int u : nodes) {
for (int e = G.head[u]; e != -1; e = G.edges[e].next) {
int v = G.edges[e].to;
if (v == u) continue;
int neighbor_color = colors[v - 1];
if (neighbor_color != -1) {
used[neighbor_color] = u;
}
}
int cr = 0;
while (cr <= n && used[cr] == u) {
cr++;
}
colors[u - 1] = cr;
}
return colors;
}
vector<int> omp_graph_coloring(const Graph_L& G) {
int n = G.n;
if (n == 0) return vector<int>();
vector<int> degree(n + 1, 0);
for (int v = 1; v <= n; ++v) {
for (int e = G.head[v]; e != -1; e = G.edges[e].next) {
degree[v]++;
}
}
vector<int> nodes(n);
iota(nodes.begin(), nodes.end(), 1);
sort(nodes.begin(), nodes.end(), [&degree](int a, int b) {
return degree[a] > degree[b];
});
vector<int> colors(n, -1);
#pragma omp parallel
{
vector<int> local_used(n + 1, -1);
#pragma omp for schedule(guided)
for (int i = 0; i < n; ++i) {
int u = nodes[i];
fill(local_used.begin(), local_used.end(), -1);
for (int e = G.head[u]; e != -1; e = G.edges[e].next) {
int v = G.edges[e].to;
if (v == u) continue;
int neighbor_color = colors[v - 1];
if (neighbor_color != -1 && neighbor_color <= n) {
local_used[neighbor_color] = u;
}
}
int cr = 0;
while (cr <= n && local_used[cr] == u) {
cr++;
}
colors[u - 1] = cr;
}
}
return colors;
}
bool verify_coloring(const Graph_L& g, const vector<int>& colors) {
int n = g.n;
for (int v = 1; v <= n; v++) {
int v_color = colors[v - 1];
for (int e = g.head[v]; e != -1; e = g.edges[e].next) {
int u = g.edges[e].to;
if (u == v) continue;
int u_color = colors[u - 1];
if (v_color == u_color) {
return false;
}
}
}
return true;
}
int count_colors(const vector<int>& colors) {
int max_color = -1;
for (int c : colors) {
if (c > max_color) {
max_color = c;
}
}
return max_color + 1;
}
@@ -0,0 +1,12 @@
#pragma once
#include <vector>
#include "../../classes/linkgraph.h"
std::vector<int> greedy_graph_coloring(const Graph_L& G);
std::vector<int> omp_graph_coloring(const Graph_L& G);
bool verify_coloring(const Graph_L& g, const std::vector<int>& colors);
int count_colors(const std::vector<int>& colors);
@@ -0,0 +1,289 @@
#include "indexed_heap.h"
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <cmath>
#include <queue>
#include <set>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "../../classes/graph.h"
#include "../../common/utils.h"
#include "../../classes/linkgraph.h"
#include "greedy_modularity.h"
namespace py = pybind11;
using namespace std;
py::object cpp_greedy_modularity_communities(py::object G, py::object weight) {
Graph& G_ = G.cast<Graph&>();
bool is_directed = G.attr("is_directed")().cast<bool>();
if (is_directed) {
throw py::value_error("greedy_modularity_communities currently only supports undirected graphs (Graph class). "
"For directed graphs, please use other community detection algorithms.");
}
string weight_key = weight.is_none() ? "weight" : weight.cast<string>();
Graph_L GL;
bool used_cached_linkgraph = false;
if (G_.linkgraph_dirty || G_.linkgraph_structure.max_deg == -1) {
GL = graph_to_linkgraph(G_, false, weight_key, true, false);
G_.linkgraph_dirty = false;
} else {
GL = G_.linkgraph_structure;
used_cached_linkgraph = true;
}
int N = GL.n;
if (N == 0) {
return py::list();
}
double m = 0.0;
vector<double> k(N + 1, 0.0);
for (int u = 1; u <= N; ++u) {
for (int e = GL.head[u]; e != -1; e = GL.edges[e].next) {
int v = GL.edges[e].to;
double w = GL.edges[e].w;
if (v > u) {
m += w;
}
k[u] += w;
}
}
if (m == 0) {
py::list result;
py::dict id_to_node = G_.id_to_node;
for (int i = 1; i <= N; ++i) {
py::set comm;
comm.add(id_to_node[py::cast(i)]);
result.append(comm);
}
return result;
}
double q0 = 1.0 / (2.0 * m);
vector<double> a(N);
for (int i = 0; i < N; ++i) {
a[i] = k[i + 1] * q0;
}
vector<int> parent(N);
vector<vector<int>> nodes(N);
for (int i = 0; i < N; ++i) {
parent[i] = i;
nodes[i].push_back(i + 1);
}
vector<vector<int>> neigh(N);
int max_neigh_size = 0;
for (int i = 0; i < N; ++i) {
int u = i + 1;
for (int e = GL.head[u]; e != -1; e = GL.edges[e].next) {
int v = GL.edges[e].to;
if (v > 0 && v <= N && v != u) {
neigh[i].push_back(v - 1);
}
}
sort(neigh[i].begin(), neigh[i].end());
neigh[i].erase(unique(neigh[i].begin(), neigh[i].end()), neigh[i].end());
if ((int)neigh[i].size() > max_neigh_size) {
max_neigh_size = neigh[i].size();
}
}
unordered_map<long long, double> edge_weights;
for (int u = 1; u <= N; ++u) {
for (int e = GL.head[u]; e != -1; e = GL.edges[e].next) {
int v = GL.edges[e].to;
double w = GL.edges[e].w;
if (v > u) {
long long key = ((long long)(u - 1) << 32) | (unsigned int)(v - 1);
edge_weights[key] = w;
}
}
}
unordered_map<long long, double> dq;
dq.reserve(N * 4);
for (int i = 0; i < N; ++i) {
int u = i + 1;
for (int j : neigh[i]) {
if (j > i) {
int v = j + 1;
long long edge_key = ((long long)i << 32) | (unsigned int)j;
double w = edge_weights.count(edge_key) ? edge_weights[edge_key] : 0.0;
double dq_val = 2.0 * w * q0 - 2.0 * k[u] * k[v] * q0 * q0;
long long key = ((long long)i << 32) | (unsigned int)j;
dq[key] = dq_val;
}
}
}
IndexedMaxHeap H(N);
for (const auto& kv : dq) {
int i = (int)(kv.first >> 32);
int j = (int)(kv.first & 0xFFFFFFFF);
H.push(kv.second, i, j);
}
vector<char> merged(N, 0);
int merge_count = 0;
vector<int> combined;
combined.reserve(max_neigh_size * 2);
while (!H.empty()) {
auto best = H.pop();
int i = best.i;
int j = best.j;
if (merged[i] || merged[j]) {
continue;
}
if (parent[i] != i || parent[j] != j) {
continue;
}
long long key = ((long long)i << 32) | (unsigned int)j;
auto it = dq.find(key);
if (it == dq.end() || it->second != best.dq) {
continue;
}
if (best.dq <= 0) {
break;
}
nodes[i].insert(nodes[i].end(), nodes[j].begin(), nodes[j].end());
nodes[j].clear();
parent[j] = i;
merged[j] = 1;
merge_count++;
combined.clear();
const vector<int>& list_i = neigh[i];
const vector<int>& list_j = neigh[j];
size_t p1 = 0, p2 = 0;
while (p1 < list_i.size() && p2 < list_j.size()) {
int val1 = list_i[p1];
int val2 = list_j[p2];
if (val1 < val2) {
if (val1 != i && val1 != j && !merged[val1]) {
combined.push_back(val1);
}
p1++;
} else if (val1 > val2) {
if (val2 != i && val2 != j && !merged[val2]) {
combined.push_back(val2);
}
p2++;
} else {
if (val1 != i && val1 != j && !merged[val1]) {
combined.push_back(val1);
}
p1++;
p2++;
}
}
while (p1 < list_i.size()) {
int val = list_i[p1];
if (val != i && val != j && !merged[val]) {
combined.push_back(val);
}
p1++;
}
while (p2 < list_j.size()) {
int val = list_j[p2];
if (val != i && val != j && !merged[val]) {
combined.push_back(val);
}
p2++;
}
neigh[i].swap(combined);
for (int k_node : neigh[i]) {
if (k_node == i) continue;
if (parent[k_node] != k_node) continue;
long long key_ik = ((long long)min(i, k_node) << 32) | (unsigned int)max(i, k_node);
long long key_jk = ((long long)min(j, k_node) << 32) | (unsigned int)max(j, k_node);
bool has_ik = dq.find(key_ik) != dq.end();
bool has_jk = dq.find(key_jk) != dq.end();
double new_dq;
if (has_ik && has_jk) {
new_dq = dq[key_ik] + dq[key_jk];
} else if (has_jk) {
new_dq = dq[key_jk] - 2.0 * a[i] * a[k_node];
} else {
new_dq = dq[key_ik] - 2.0 * a[j] * a[k_node];
}
dq[key_ik] = new_dq;
if (has_jk) {
dq.erase(key_jk);
}
if (H.get_index(i, k_node) >= 0) {
H.update(new_dq, i, k_node);
} else {
H.push(new_dq, i, k_node);
}
}
const vector<int>& old_j_neigh = neigh[j];
for (int k_node : old_j_neigh) {
if (k_node == i || k_node == j) continue;
if (parent[k_node] != k_node) continue;
H.remove(j, k_node);
}
neigh[j].clear();
a[i] += a[j];
a[j] = 0;
}
py::dict id_to_node = G_.id_to_node;
py::list result;
for (int i = 0; i < N; ++i) {
if (parent[i] == i && !nodes[i].empty()) {
py::set comm;
for (int node_id : nodes[i]) {
comm.add(id_to_node[py::cast(node_id)]);
}
result.append(comm);
}
}
py::list sorted_result;
vector<int> sizes;
for (size_t i = 0; i < result.size(); ++i) {
sizes.push_back(py::len(result[i]));
}
vector<int> indices(result.size());
iota(indices.begin(), indices.end(), 0);
sort(indices.begin(), indices.end(), [&](int a_idx, int b_idx) {
return sizes[a_idx] > sizes[b_idx];
});
for (int idx : indices) {
sorted_result.append(result[idx]);
}
return sorted_result;
}
@@ -0,0 +1,11 @@
#pragma once
#include <pybind11/pybind11.h>
namespace py = pybind11;
py::object cpp_greedy_modularity_communities(
py::object G,
py::object weight = py::str("weight")
);
@@ -0,0 +1,191 @@
#pragma once
#include <vector>
#include <algorithm>
class IndexedMaxHeap {
public:
struct HeapEntry {
double dq;
int i;
int j;
};
private:
std::vector<HeapEntry> heap;
std::vector<std::vector<int>> pair_to_idx;
size_t heap_size;
int capacity_n;
static inline long long make_key(int i, int j) {
int a = std::min(i, j);
int b = std::max(i, j);
return ((long long)a << 32) | (unsigned int)b;
}
public:
IndexedMaxHeap(size_t capacity = 0) : heap_size(0), capacity_n((int)capacity) {
heap.reserve(capacity);
if (capacity > 0) {
pair_to_idx.assign(capacity, std::vector<int>(capacity, -1));
}
}
bool empty() const { return heap_size == 0; }
size_t size() const { return heap_size; }
int get_index(int i, int j) const {
int a = std::min(i, j);
int b = std::max(i, j);
if (a < 0 || b < 0 || a >= capacity_n || b >= capacity_n) return -1;
size_t idx = (size_t)pair_to_idx[a][b];
if (idx >= heap.size()) return -1;
if (heap[idx].i == a && heap[idx].j == b) {
return (int)idx;
}
return -1;
}
void push(double dq, int i, int j) {
int a = std::min(i, j);
int b = std::max(i, j);
if (a < 0 || b < 0 || a >= capacity_n || b >= capacity_n) return;
size_t existing = (size_t)pair_to_idx[a][b];
if (existing < heap.size() && heap[existing].i == a && heap[existing].j == b) {
update(dq, i, j);
return;
}
HeapEntry entry = {dq, a, b};
heap.push_back(entry);
size_t idx = heap.size() - 1;
pair_to_idx[a][b] = (int)idx;
sift_up(idx);
heap_size++;
}
void update(double dq, int i, int j) {
int a = std::min(i, j);
int b = std::max(i, j);
if (a < 0 || b < 0 || a >= capacity_n || b >= capacity_n) return;
size_t idx = (size_t)pair_to_idx[a][b];
if (idx >= heap.size()) return;
if (heap[idx].i != a || heap[idx].j != b) return;
if (heap[idx].dq == dq) return;
bool is_increase = dq > heap[idx].dq;
heap[idx].dq = dq;
if (is_increase) {
sift_up(idx);
} else {
sift_down(idx);
}
}
void remove(int i, int j) {
int a = std::min(i, j);
int b = std::max(i, j);
if (a < 0 || b < 0 || a >= capacity_n || b >= capacity_n) return;
size_t idx = (size_t)pair_to_idx[a][b];
if (idx >= heap.size()) return;
if (heap[idx].i != a || heap[idx].j != b) return;
pair_to_idx[a][b] = -1;
if (idx == heap.size() - 1) {
heap.pop_back();
} else {
heap[idx] = heap.back();
int last_a = heap[idx].i;
int last_b = heap[idx].j;
if (last_a >= 0 && last_a < capacity_n && last_b >= 0 && last_b < capacity_n) {
pair_to_idx[last_a][last_b] = (int)idx;
}
heap.pop_back();
sift_up(idx);
sift_down(idx);
}
if (heap_size > 0) heap_size--;
}
HeapEntry pop() {
HeapEntry result = heap[0];
pair_to_idx[result.i][result.j] = -1;
if (heap.size() > 1) {
heap[0] = heap.back();
int new_a = heap[0].i;
int new_b = heap[0].j;
if (new_a >= 0 && new_a < capacity_n && new_b >= 0 && new_b < capacity_n) {
pair_to_idx[new_a][new_b] = 0;
}
heap.pop_back();
sift_down(0);
} else {
heap.pop_back();
}
if (heap_size > 0) heap_size--;
return result;
}
const HeapEntry& top() const { return heap[0]; }
private:
void sift_up(size_t idx) {
while (idx > 0) {
size_t parent = (idx - 1) >> 1;
if (heap[parent].dq >= heap[idx].dq) break;
std::swap(heap[parent], heap[idx]);
int p_i = heap[parent].i, p_j = heap[parent].j;
int c_i = heap[idx].i, c_j = heap[idx].j;
if (p_i >= 0 && p_i < capacity_n && p_j >= 0 && p_j < capacity_n) {
pair_to_idx[p_i][p_j] = (int)parent;
}
if (c_i >= 0 && c_i < capacity_n && c_j >= 0 && c_j < capacity_n) {
pair_to_idx[c_i][c_j] = (int)idx;
}
idx = parent;
}
}
void sift_down(size_t idx) {
size_t n = heap.size();
while (true) {
size_t largest = idx;
size_t left = idx * 2 + 1;
size_t right = idx * 2 + 2;
if (left < n && heap[left].dq > heap[largest].dq) {
largest = left;
}
if (right < n && heap[right].dq > heap[largest].dq) {
largest = right;
}
if (largest == idx) break;
std::swap(heap[largest], heap[idx]);
int l_i = heap[largest].i, l_j = heap[largest].j;
int c_i = heap[idx].i, c_j = heap[idx].j;
if (l_i >= 0 && l_i < capacity_n && l_j >= 0 && l_j < capacity_n) {
pair_to_idx[l_i][l_j] = (int)largest;
}
if (c_i >= 0 && c_i < capacity_n && c_j >= 0 && c_j < capacity_n) {
pair_to_idx[c_i][c_j] = (int)idx;
}
idx = largest;
}
}
};
@@ -0,0 +1,613 @@
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <algorithm>
#include <queue>
#include <map>
#include <random>
#include <cmath>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "../../classes/graph.h"
#include "../../common/utils.h"
#include "../../classes/linkgraph.h"
#include "localsearch.h"
namespace py = pybind11;
using namespace std;
struct RootDecision {
int superior;
int path_length;
int degree;
};
int choose_center(const vector<pair<int, double>>& sorted_multi) {
if (sorted_multi.size() < 2) {
return 1;
}
vector<double> y;
for (const auto& p : sorted_multi) {
y.push_back(p.second);
}
vector<double> delta;
for (size_t i = 1; i < y.size(); ++i) {
delta.push_back(fabs(y[i] - y[i-1]));
}
if (delta.empty()) {
return 1;
}
vector<double> delta_nozero;
for (double d : delta) {
if (d != 0) {
delta_nozero.push_back(d);
}
}
if (delta_nozero.empty()) {
return 1;
}
double mean = 0;
for (double d : delta_nozero) {
mean += d;
}
mean /= delta_nozero.size();
double variance = 0;
for (double d : delta_nozero) {
variance += (d - mean) * (d - mean);
}
variance /= delta_nozero.size();
double std_dev = sqrt(variance);
double threshold = std_dev + mean;
for (size_t i = 0; i < delta.size(); ++i) {
if (delta[i] > threshold) {
return i + 1;
}
}
return 0;
}
py::object cpp_localsearch(
py::object G,
py::object center_num,
py::object auto_choose_centers,
py::object maximum_tree,
py::object seed,
py::object self_loop
) {
Graph_generate_linkgraph(G, py::str("weight"));
Graph& G_ = G.cast<Graph&>();
Graph_L GL = G_._get_linkgraph_structure();
py::dict id_to_node = G_.id_to_node;
int n = GL.n;
if (n == 0) {
return py::make_tuple(py::none(), py::list(), py::list(), py::dict(), py::dict(), py::none());
}
bool has_edges = false;
for (int u = 1; u <= n; ++u) {
if (GL.head[u] != -1) {
has_edges = true;
break;
}
}
if (!has_edges) {
py::dict result_grouped;
py::list result_center_dcd;
py::list result_y_dcd;
py::dict result_y_partition;
for (int u = 1; u <= n; ++u) {
py::object node = id_to_node[py::cast(u)];
py::list members;
members.append(node);
result_grouped[node] = members;
result_center_dcd.append(node);
result_y_dcd.append(node);
result_y_partition[node] = node;
}
return py::make_tuple(py::none(), result_center_dcd, result_y_dcd, result_y_partition, result_grouped, py::none());
}
std::mt19937 rng(42);
if (!seed.is_none()) {
try {
int seed_value = seed.cast<int>();
rng.seed(seed_value);
} catch (...) {
}
}
std::uniform_real_distribution<double> random_dist(0.0, 1.0);
unordered_set<int> selfloop_nodes;
bool has_selfloop = self_loop.cast<bool>();
if (!has_selfloop) {
selfloop_nodes.clear();
}
vector<int> degree(n + 1, 0);
for (int u = 1; u <= n; ++u) {
int deg = 0;
for (int e = GL.head[u]; e != -1; e = GL.edges[e].next) {
deg++;
}
if (selfloop_nodes.count(u)) {
degree[u] = deg + 1;
} else {
degree[u] = deg;
}
}
unordered_map<int, vector<int>> dag_adj;
unordered_map<int, vector<int>> dag_pred;
for (int v = 1; v <= n; ++v) {
if (degree[v] == 0) continue;
int kv = degree[v];
vector<pair<int, int>> neighbors;
for (int e = GL.head[v]; e != -1; e = GL.edges[e].next) {
int nn = GL.edges[e].to;
if (nn == v && selfloop_nodes.count(v)) continue;
int deg_nn = degree[nn];
neighbors.push_back({nn, deg_nn});
}
if (!neighbors.empty()) {
int knnmax = -1;
for (auto& p : neighbors) {
if (p.second > knnmax) knnmax = p.second;
}
if (knnmax >= kv) {
for (auto& p : neighbors) {
if (p.second == knnmax) {
int nn = p.first;
bool already_has = false;
bool has_reverse = false;
for (int existing : dag_adj[v]) {
if (existing == nn) {
already_has = true;
break;
}
}
for (int existing : dag_adj[nn]) {
if (existing == v) {
has_reverse = true;
break;
}
}
if (!already_has && !has_reverse) {
dag_adj[v].push_back(nn);
dag_pred[nn].push_back(v);
}
}
}
}
}
}
vector<int> out_degree_dag(n + 1, 0);
for (int u = 1; u <= n; ++u) {
out_degree_dag[u] = (int)dag_adj[u].size();
}
vector<int> roots;
for (int u = 1; u <= n; ++u) {
if (out_degree_dag[u] == 0 && degree[u] > 0) {
roots.push_back(u);
}
}
if (roots.empty()) {
for (int u = 1; u <= n; ++u) {
if (degree[u] > 0) {
roots.push_back(u);
}
}
}
if (roots.size() > 1) {
bool all_same_degree = true;
int first_degree = -1;
for (int root : roots) {
if (first_degree == -1) {
first_degree = degree[root];
} else if (degree[root] != first_degree) {
all_same_degree = false;
break;
}
}
if (all_same_degree) {
int max_root = -1;
for (int root : roots) {
if (root > max_root) {
max_root = root;
}
}
roots.clear();
roots.push_back(max_root);
}
}
unordered_map<int, int> tree_rootnode;
unordered_map<int, int> tree_parentnode;
unordered_map<int, int> tree_distancetoroot;
for (int i = 1; i <= n; ++i) {
tree_rootnode[i] = -1;
tree_parentnode[i] = -1;
tree_distancetoroot[i] = -1;
}
queue<pair<int, int>> bfs_queue;
for (int root : roots) {
bfs_queue.push({root, 0});
tree_rootnode[root] = root;
tree_parentnode[root] = -1;
tree_distancetoroot[root] = 0;
}
vector<int> visited(n + 1, 0);
for (int root : roots) {
visited[root] = 1;
}
while (!bfs_queue.empty()) {
int parent, dist;
std::tie(parent, dist) = bfs_queue.front();
bfs_queue.pop();
for (int pred : dag_pred[parent]) {
if (tree_distancetoroot[pred] != -1 && tree_distancetoroot[pred] < dist + 1) {
continue;
}
if (tree_distancetoroot[pred] == -1) {
tree_rootnode[pred] = tree_rootnode[parent];
tree_parentnode[pred] = parent;
tree_distancetoroot[pred] = dist + 1;
bfs_queue.push({pred, dist + 1});
} else if (tree_distancetoroot[pred] == dist + 1) {
if (random_dist(rng) < 0.5) {
continue;
}
tree_rootnode[pred] = tree_rootnode[parent];
tree_parentnode[pred] = parent;
}
}
}
unordered_map<int, vector<int>> root_to_node;
for (int node = 1; node <= n; ++node) {
int root = tree_rootnode[node];
if (root != -1) {
root_to_node[root].push_back(node);
}
}
vector<int> valid_roots;
for (auto& kv : root_to_node) {
if ((int)kv.second.size() > 1) {
valid_roots.push_back(kv.first);
}
}
unordered_set<int> root_set(valid_roots.begin(), valid_roots.end());
unordered_map<int, RootDecision> root_decision;
auto BFS_from_s = [&](int s) -> pair<int, int> {
queue<int> search_queue;
unordered_map<int, int> path_dict;
unordered_set<int> seen;
search_queue.push(s);
seen.insert(s);
path_dict[s] = 0;
while (!search_queue.empty()) {
int vertex = search_queue.front();
search_queue.pop();
int current_dist = path_dict[vertex];
vector<pair<int, int>> neighbors;
for (int e = GL.head[vertex]; e != -1; e = GL.edges[e].next) {
int nn = GL.edges[e].to;
int deg_nn = degree[nn];
neighbors.push_back({nn, deg_nn});
}
sort(neighbors.begin(), neighbors.end(),
[](const pair<int, int>& a, const pair<int, int>& b) {
return a.second > b.second;
});
for (auto& p : neighbors) {
int w = p.first;
if (!seen.count(w)) {
path_dict[w] = current_dist + 1;
seen.insert(w);
search_queue.push(w);
}
if (root_set.count(w) && degree[w] > degree[s]) {
return {w, path_dict[w]};
}
}
}
return {s, -1};
};
for (int root : valid_roots) {
auto result = BFS_from_s(root);
root_decision[root] = {result.first, result.second, degree[root]};
}
int max_path = -1;
for (auto& kv : root_decision) {
if (kv.second.path_length > max_path) {
max_path = kv.second.path_length;
}
}
if (max_path < 0) max_path = 2;
for (auto& kv : root_decision) {
if (kv.second.path_length == -1) {
kv.second.path_length = max_path;
}
}
unordered_map<int, RootDecision> node_plot = root_decision;
for (int node = 1; node <= n; ++node) {
if (node_plot.find(node) == node_plot.end() && degree[node] > 0) {
int parent = tree_parentnode[node];
node_plot[node] = {parent, 1, degree[node]};
}
}
vector<int> node_ids;
vector<int> degrees;
vector<int> path_lens;
for (auto& kv : node_plot) {
node_ids.push_back(kv.first);
degrees.push_back(kv.second.degree);
path_lens.push_back(kv.second.path_length);
}
for (size_t i = 0; i < path_lens.size(); ++i) {
if (degrees[i] <= 1) {
path_lens[i] = 1;
}
}
unordered_map<int, int> degree_rank;
vector<pair<int, int>> sorted_by_deg;
for (size_t i = 0; i < node_ids.size(); ++i) {
sorted_by_deg.push_back({degrees[i], node_ids[i]});
}
sort(sorted_by_deg.begin(), sorted_by_deg.end(),
[](const pair<int, int>& a, const pair<int, int>& b) {
return a.first < b.first;
});
int rank = 1;
int last_deg = -1;
for (auto& p : sorted_by_deg) {
if (p.first != last_deg) {
degree_rank[p.second] = rank;
rank++;
last_deg = p.first;
} else {
degree_rank[p.second] = rank - 1;
}
}
int min_rank = 1;
int max_rank = rank - 1;
double rank_range = (max_rank - min_rank);
if (rank_range == 0) rank_range = 1;
vector<double> square_path(node_ids.size());
double max_sq_path = 0;
double min_sq_path = 1e9;
for (size_t i = 0; i < node_ids.size(); ++i) {
square_path[i] = (double)path_lens[i] * (double)path_lens[i];
if (square_path[i] > max_sq_path) max_sq_path = square_path[i];
if (square_path[i] < min_sq_path) min_sq_path = square_path[i];
}
double sq_range = max_sq_path - min_sq_path;
if (sq_range == 0) sq_range = 1;
unordered_map<int, double> multi_dict;
for (size_t i = 0; i < node_ids.size(); ++i) {
int node = node_ids[i];
double norm_deg, norm_sq_path;
if (max_rank == min_rank) {
norm_deg = 1.0 / (double)node_ids.size();
} else {
norm_deg = (double)(degree_rank[node] - min_rank) / rank_range;
}
if (max_sq_path == min_sq_path) {
norm_sq_path = 1.0 / (double)node_ids.size();
} else {
norm_sq_path = (square_path[i] - min_sq_path) / sq_range;
}
multi_dict[node] = norm_deg * norm_sq_path;
}
vector<pair<int, double>> sorted_multi;
for (auto& kv : multi_dict) {
sorted_multi.push_back(kv);
}
sort(sorted_multi.begin(), sorted_multi.end(),
[](const pair<int, double>& a, const pair<int, double>& b) {
if (fabs(a.second - b.second) > 1e-9) return a.second > b.second;
return a.first > b.first;
});
int num_centers = (int)valid_roots.size();
bool auto_choose = auto_choose_centers.cast<bool>();
if (auto_choose && sorted_multi.size() > 0) {
int auto_centernum = choose_center(sorted_multi);
if (!center_num.is_none()) {
int user_center_num = center_num.cast<int>();
num_centers = (auto_centernum < user_center_num) ? auto_centernum : user_center_num;
} else {
num_centers = auto_centernum;
}
} else if (!center_num.is_none()) {
num_centers = center_num.cast<int>();
}
if (num_centers <= 0) {
num_centers = (int)valid_roots.size();
}
vector<int> center_dcd;
int local_cnt = 0;
for (size_t i = 0; i < sorted_multi.size() && local_cnt < num_centers; ++i) {
if (sorted_multi[i].second > 0) {
local_cnt++;
center_dcd.push_back(sorted_multi[i].first);
}
}
if (center_dcd.empty() && !sorted_multi.empty()) {
center_dcd.push_back(sorted_multi[0].first);
}
bool all_same_degree = true;
int first_deg = -1;
for (int i = 1; i <= n && all_same_degree; ++i) {
if (degree[i] > 0) {
if (first_deg == -1) {
first_deg = degree[i];
} else if (degree[i] != first_deg) {
all_same_degree = false;
}
}
}
if (all_same_degree && n > 0) {
center_dcd.clear();
center_dcd.push_back(n);
}
unordered_set<int> center_set(center_dcd.begin(), center_dcd.end());
for (int node : valid_roots) {
int superior = root_decision[node].superior;
tree_parentnode[node] = superior;
tree_rootnode[node] = superior;
}
for (int node = 0; node < n; ++node) {
if (degree[node] > 0 && center_set.count(node)) {
tree_rootnode[node] = node;
}
}
for (int node = 0; node < n; ++node) {
if (degree[node] == 0) continue;
vector<int> recent;
recent.push_back(node);
bool flag = false;
while (center_set.find(tree_rootnode[node]) == center_set.end() && !flag) {
int j = tree_rootnode[node];
if (j == -1 || find(recent.begin(), recent.end(), j) != recent.end()) {
tree_rootnode[node] = -1;
flag = true;
break;
}
recent.push_back(j);
tree_rootnode[node] = tree_rootnode[j];
}
}
unordered_map<int, int> y_partition;
vector<int> y_dcd;
for (int node = 1; node <= n; ++node) {
if (degree[node] == 0) continue;
int root = tree_rootnode[node];
if (root == -1 && !center_dcd.empty()) {
root = center_dcd[0];
tree_rootnode[node] = root;
}
y_partition[node] = root;
if (root == -1) {
y_dcd.push_back(-1);
} else {
y_dcd.push_back(root);
}
}
unordered_map<int, vector<int>> grouped;
for (auto& kv : y_partition) {
int center = kv.second;
if (center != -1) {
grouped[center].push_back(kv.first);
}
}
py::dict result_grouped;
for (auto& kv : grouped) {
py::list members;
for (int node_id : kv.second) {
members.append(id_to_node[py::cast(node_id)]);
}
result_grouped[id_to_node[py::cast(kv.first)]] = members;
}
py::list result_center_dcd;
for (int center : center_dcd) {
result_center_dcd.append(id_to_node[py::cast(center)]);
}
py::list result_y_dcd;
for (int label : y_dcd) {
if (label == -1) {
result_y_dcd.append(py::cast(-1));
} else {
result_y_dcd.append(id_to_node[py::cast(label)]);
}
}
py::dict result_y_partition;
for (auto& kv : y_partition) {
if (kv.second == -1) {
result_y_partition[id_to_node[py::cast(kv.first)]] = py::cast(-1);
} else {
result_y_partition[id_to_node[py::cast(kv.first)]] = id_to_node[py::cast(kv.second)];
}
}
return py::make_tuple(
py::none(),
result_center_dcd,
result_y_dcd,
result_y_partition,
result_grouped,
py::none()
);
}
@@ -0,0 +1,13 @@
#pragma once
#include <pybind11/pybind11.h>
namespace py = pybind11;
py::object cpp_localsearch(
py::object G,
py::object center_num = py::none(),
py::object auto_choose_centers = py::bool_(false),
py::object maximum_tree = py::bool_(true),
py::object seed = py::none(),
py::object self_loop = py::bool_(false)
);
@@ -0,0 +1,544 @@
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <random>
#include <numeric>
#include <cstdint>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#ifdef _OPENMP
#include <omp.h>
#else
#warning "OpenMP is not available: cpp_louvain_communities will fall back to single-threaded execution."
#endif
#include "../../classes/graph.h"
#include "../../common/utils.h"
#include "../../classes/linkgraph.h"
#include "../../functions/community/graph_coloring.h"
namespace py = pybind11;
using namespace std;
const double epsilon = 1e-10;
const int MAX_ITERATIONS = 50;
const double CONVERGENCE_THRESHOLD = 1e-7;
struct CommunityState {
int size = 0;
double weight_inside = 0;
double weight_all = 0;
};
double calculate_norm(const Graph_L& G) {
double total_weight = 0.0;
for (int u = 1; u < G.n + 1; ++u) {
for (int i = G.head[u]; i != -1; i = G.edges[i].next) {
total_weight += G.edges[i].w;
}
}
return total_weight;
}
double calculate_actual_modularity(const Graph_L& G, const vector<int>& membership,
double norm, double resolution) {
unordered_map<int, double> comm_weight_inside;
unordered_map<int, double> comm_weight_total;
for (int u = 1; u <= G.n; ++u) {
int comm_u = membership[u - 1];
for (int e_idx = G.head[u]; e_idx != -1; e_idx = G.edges[e_idx].next) {
int v = G.edges[e_idx].to;
double w = G.edges[e_idx].w;
int comm_v = membership[v - 1];
comm_weight_total[comm_u] += w;
if (comm_u == comm_v) {
comm_weight_inside[comm_u] += w;
}
}
}
double modularity = 0.0;
for (const auto& kv : comm_weight_inside) {
int comm = kv.first;
double inside = kv.second;
double total = comm_weight_total[comm];
modularity += (inside / 2.0) - resolution * (total * total) / (4.0 * norm * norm);
}
return modularity;
}
inline double calculate_modularity_gain(double comm_weight_without_u, double norm,
double node_weight_all, double weight_to_comm, double resolution) {
double gain = weight_to_comm - resolution * (comm_weight_without_u * node_weight_all) / norm;
return 2 * gain / norm;
}
double modularity_optimization_serial(const Graph_L& G, vector<int>& membership, double resolution_val, double norm) {
int n = G.n;
double total_modularity_gain = 0.0;
vector<CommunityState> comms(n);
for (int i = 0; i < n; ++i) {
membership[i] = i;
comms[i].size = 1;
comms[i].weight_all = 0.0;
comms[i].weight_inside = 0.0;
for (int j = G.head[i + 1]; j != -1; j = G.edges[j].next) {
int v = G.edges[j].to;
double w = G.edges[j].w;
comms[i].weight_all += w;
if (v == i + 1) comms[i].weight_inside += w;
}
}
vector<double> node_weight(n + 1, 0.0);
vector<double> node_loop(n + 1, 0.0);
for (int i = 0; i < n; ++i) {
node_weight[i + 1] = comms[i].weight_all;
node_loop[i + 1] = comms[i].weight_inside;
}
vector<double> neigh_weight(n, 0.0);
vector<int> timestamp(n, 0);
vector<int> touched;
touched.reserve(512);
int version = 0;
std::random_device rd;
std::mt19937 rng(rd());
vector<int> node_order(n);
for (int i = 0; i < n; ++i) node_order[i] = i + 1;
int num_moved_nodes = 1;
int iteration = 0;
double previous_modularity = -1e100;
do {
num_moved_nodes = 0;
iteration++;
std::shuffle(node_order.begin(), node_order.end(), rng);
for (int idx = 0; idx < n; ++idx) {
int u = node_order[idx];
touched.clear();
version++;
if (version == 0) version = 1;
double weight_all = node_weight[u];
double weight_loop = node_loop[u];
double weight_inside = 0.0;
int my_comm = membership[u - 1];
for (int e = G.head[u]; e != -1; e = G.edges[e].next) {
int v = G.edges[e].to;
double w = G.edges[e].w;
if (u == v) continue;
int v_comm = membership[v - 1];
if (timestamp[v_comm] != version) {
timestamp[v_comm] = version;
neigh_weight[v_comm] = 0.0;
touched.push_back(v_comm);
}
neigh_weight[v_comm] += w;
if (v_comm == my_comm) {
weight_inside += w;
}
}
if (weight_all < epsilon) continue;
int old_comm = my_comm;
double comm_old_w = comms[old_comm].weight_all - weight_all;
double old_gain = calculate_modularity_gain(comm_old_w, norm, weight_all, weight_inside, resolution_val);
double max_gain = std::max(old_gain, 0.0);
int best_comm = old_comm;
double best_weight_to_comm = weight_inside;
for (int target_comm : touched) {
if (target_comm == old_comm) continue;
double w_to_target = neigh_weight[target_comm];
double comm_target_w = comms[target_comm].weight_all;
double gain = calculate_modularity_gain(comm_target_w, norm, weight_all, w_to_target, resolution_val);
if (gain > max_gain + epsilon) {
max_gain = gain;
best_comm = target_comm;
best_weight_to_comm = w_to_target;
}
}
if (best_comm != old_comm) {
comms[old_comm].size--;
comms[old_comm].weight_all -= weight_all;
comms[old_comm].weight_inside -= (2.0 * weight_inside + weight_loop);
comms[best_comm].size++;
comms[best_comm].weight_all += weight_all;
comms[best_comm].weight_inside += (2.0 * best_weight_to_comm + weight_loop);
membership[u - 1] = best_comm;
num_moved_nodes++;
}
}
if (num_moved_nodes == 0) {
return calculate_actual_modularity(G, membership, norm, resolution_val);
}
if (iteration >= MAX_ITERATIONS) {
return calculate_actual_modularity(G, membership, norm, resolution_val);
}
} while (true);
for (int i = 0; i < n; ++i) {
comms[i].weight_all = node_weight[i + 1];
}
return calculate_actual_modularity(G, membership, norm, resolution_val);
}
double modularity_optimization_parallel_simplified(const Graph_L& G, vector<int>& membership, double resolution_val, double norm) {
int n = G.n;
vector<CommunityState> comms(n);
for (int i = 0; i < n; ++i) {
membership[i] = i;
comms[i].size = 1;
comms[i].weight_all = 0.0;
comms[i].weight_inside = 0.0;
for (int j = G.head[i + 1]; j != -1; j = G.edges[j].next) {
int v = G.edges[j].to;
double w = G.edges[j].w;
comms[i].weight_all += w;
if (v == i + 1) comms[i].weight_inside += w;
}
}
vector<double> node_weight(n + 1, 0.0);
vector<double> node_loop(n + 1, 0.0);
for (int i = 0; i < n; ++i) {
node_weight[i + 1] = comms[i].weight_all;
node_loop[i + 1] = comms[i].weight_inside;
}
std::random_device rd;
std::mt19937 rng(rd());
vector<int> node_order(n);
iota(node_order.begin(), node_order.end(), 0);
int num_moved_nodes = 1;
int iteration = 0;
vector<double> comm_weights(n, 0.0);
for (int i = 0; i < n; ++i) {
comm_weights[i] = comms[i].weight_all;
}
do {
num_moved_nodes = 0;
iteration++;
std::shuffle(node_order.begin(), node_order.end(), rng);
#pragma omp parallel
{
#pragma omp for reduction(+:num_moved_nodes) schedule(guided)
for (int idx = 0; idx < n; ++idx) {
int u = node_order[idx];
double weight_all = node_weight[u];
int my_comm = membership[u - 1];
if (weight_all < epsilon) continue;
unordered_map<int, double> neigh_weight;
neigh_weight.reserve(32);
vector<int> touched;
touched.reserve(32);
double weight_inside = 0.0;
for (int e = G.head[u]; e != -1; e = G.edges[e].next) {
int v = G.edges[e].to;
double w = G.edges[e].w;
if (u == v) continue;
int v_comm = membership[v - 1];
auto it = neigh_weight.find(v_comm);
if (it == neigh_weight.end()) {
touched.push_back(v_comm);
neigh_weight[v_comm] = w;
} else {
it->second += w;
}
if (v_comm == my_comm) {
weight_inside += w;
}
}
int old_comm = my_comm;
double comm_old_w = comm_weights[old_comm] - weight_all;
double old_gain = calculate_modularity_gain(comm_old_w, norm, weight_all, weight_inside, resolution_val);
double max_gain = std::max(old_gain, 0.0);
int best_comm = old_comm;
for (int target_comm : touched) {
if (target_comm == old_comm) continue;
double w_to_target = neigh_weight.at(target_comm);
double comm_target_w = comm_weights[target_comm];
double gain = calculate_modularity_gain(comm_target_w, norm, weight_all, w_to_target, resolution_val);
if (gain > max_gain + epsilon) {
max_gain = gain;
best_comm = target_comm;
}
}
if (best_comm != old_comm) {
#pragma omp atomic
comm_weights[old_comm] -= weight_all;
#pragma omp atomic
comm_weights[best_comm] += weight_all;
membership[u - 1] = best_comm;
num_moved_nodes++;
}
}
}
if (num_moved_nodes == 0) break;
if (iteration >= MAX_ITERATIONS) break;
} while (true);
for (int i = 0; i < n; ++i) {
comms[i].weight_all = comm_weights[i];
}
return calculate_actual_modularity(G, membership, norm, resolution_val);
}
struct SuperEdge {
int u, v;
double w;
};
Graph_L* graph_compress(const Graph_L* G, vector<int>& membership) {
unordered_map<int, int> new_comm_id;
int new_vcount = 0;
for (int i = 0; i < G->n; i++) {
int c = membership[i];
if (new_comm_id.find(c) == new_comm_id.end()) {
new_comm_id[c] = new_vcount++;
}
membership[i] = new_comm_id[c];
}
vector<SuperEdge> edges;
for (int u = 1; u < G->n + 1; ++u) {
int su = membership[u-1] + 1;
for (int e = G->head[u]; e != -1; e = G->edges[e].next) {
int v = G->edges[e].to;
double w = G->edges[e].w;
int sv = membership[v-1] + 1;
edges.push_back({su, sv, w});
}
}
sort(edges.begin(), edges.end(), [](const SuperEdge& a, const SuperEdge& b) {
if (a.u != b.u) return a.u < b.u;
return a.v < b.v;
});
Graph_L* super_g = new Graph_L(new_vcount, G->is_directed, G->is_deg);
if (edges.empty()) return super_g;
int curr_u = edges[0].u;
int curr_v = edges[0].v;
double curr_w = edges[0].w;
for (size_t i = 1; i < edges.size(); ++i) {
if (edges[i].u == curr_u && edges[i].v == curr_v) {
curr_w += edges[i].w;
} else {
super_g->add_weighted_edge(curr_u, curr_v, curr_w);
curr_u = edges[i].u;
curr_v = edges[i].v;
curr_w = edges[i].w;
}
}
super_g->add_weighted_edge(curr_u, curr_v, curr_w);
return super_g;
}
py::object cpp_louvain_communities_serial(py::object G, py::object weight, py::object threshold, py::object resolution) {
Graph& G_ = G.cast<Graph&>();
{
if (G_.is_linkgraph_dirty()) {
G_._get_linkgraph_structure();
}
}
Graph_L original_GL = G_._get_linkgraph_structure();
Graph_L* current_GL = &original_GL;
double threshold_val = threshold.cast<double>();
double resolution_val = resolution.cast<double>();
int vcount = current_GL->n;
if (vcount == 0) return py::list();
double norm = calculate_norm(*current_GL);
vector<int> membership(vcount);
iota(membership.begin(), membership.end(), 0);
vector<Graph_L*> allocated_graphs;
int iteration = 0;
double previous_modularity = -1e100;
if (norm > 0.0) {
while (true) {
iteration++;
int curr_vcount = current_GL->n;
vector<int> curr_membership(curr_vcount);
double current_modularity;
current_modularity = modularity_optimization_serial(*current_GL, curr_membership, resolution_val, norm);
double modularity_gain = current_modularity - previous_modularity;
if (modularity_gain <= threshold_val || iteration > 100) {
break;
}
Graph_L* next_GL = graph_compress(current_GL, curr_membership);
allocated_graphs.push_back(next_GL);
current_GL = next_GL;
for (int i = 0; i < vcount; i++) {
membership[i] = curr_membership[membership[i]];
}
previous_modularity = current_modularity;
}
}
unordered_map<int, vector<node_t>> cpp_groups;
for (int i = 0; i < vcount; ++i) {
int comm_id = membership[i];
node_t node_id = i + 1;
cpp_groups[comm_id].push_back(node_id);
}
py::dict id_to_node = G_.id_to_node;
py::list final_result;
for (auto& kv : cpp_groups) {
py::set py_comm;
for (node_t node_id : kv.second) {
py::object node_obj = id_to_node[py::cast(node_id)];
py_comm.add(node_obj);
}
final_result.append(py_comm);
}
for (auto g : allocated_graphs) {
delete g;
}
return final_result;
}
py::object cpp_louvain_communities(py::object G, py::object weight, py::object threshold, py::object resolution) {
Graph& G_ = G.cast<Graph&>();
#ifdef _OPENMP
omp_set_num_threads(8);
#endif
Graph_L original_GL = G_._get_linkgraph_structure();
Graph_L* current_GL = &original_GL;
double threshold_val = threshold.cast<double>();
double resolution_val = resolution.cast<double>();
int vcount = current_GL->n;
if (vcount == 0) return py::list();
double norm = calculate_norm(*current_GL);
vector<int> membership(vcount);
iota(membership.begin(), membership.end(), 0);
vector<Graph_L*> allocated_graphs;
int iteration = 0;
double previous_modularity = 0.0;
if (norm > 0.0) {
while (true) {
iteration++;
int curr_vcount = current_GL->n;
vector<int> curr_membership(curr_vcount);
double current_modularity;
current_modularity = modularity_optimization_parallel_simplified(*current_GL, curr_membership, resolution_val, norm);
double modularity_gain = current_modularity - previous_modularity;
previous_modularity = current_modularity;
if (modularity_gain <= threshold_val || iteration > 100) {
break;
}
Graph_L* next_GL = graph_compress(current_GL, curr_membership);
allocated_graphs.push_back(next_GL);
current_GL = next_GL;
for (int i = 0; i < vcount; i++) {
membership[i] = curr_membership[membership[i]];
}
}
}
unordered_map<int, vector<node_t>> cpp_groups;
for (int i = 0; i < vcount; ++i) {
int comm_id = membership[i];
node_t node_id = i + 1;
cpp_groups[comm_id].push_back(node_id);
}
py::dict id_to_node = G_.id_to_node;
py::list final_result;
for (auto& kv : cpp_groups) {
py::set py_comm;
for (node_t node_id : kv.second) {
py::object node_obj = id_to_node[py::cast(node_id)];
py_comm.add(node_obj);
}
final_result.append(py_comm);
}
for (auto g : allocated_graphs) {
delete g;
}
return final_result;
}
@@ -0,0 +1,20 @@
#pragma once
#include <pybind11/pybind11.h>
namespace py = pybind11;
py::object cpp_louvain_communities(
py::object G,
py::object weight = py::str("weight"),
py::object threshold = py::float_(0.0),
py::object resolution = py::float_(1.0)
);
py::object cpp_louvain_communities_serial(
py::object G,
py::object weight = py::str("weight"),
py::object threshold = py::float_(0.0),
py::object resolution = py::float_(1.0)
);
@@ -0,0 +1,213 @@
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <string>
#include <cstddef>
#ifdef _OPENMP
#include <omp.h>
#else
#warning "OpenMP is not available: modularity utility functions will fall back to single-threaded execution."
#endif
#include "../../classes/graph.h"
#include "../../common/utils.h"
using namespace std;
void addVectorsInPlace(std::vector<double>& v1, std::vector<double>& v2) {
if (v1.size() != v2.size()) {
throw std::invalid_argument("Vectors must have the same size for element-wise addition.");
}
const std::ptrdiff_t n = static_cast<std::ptrdiff_t>(v1.size());
#pragma omp parallel for
for (std::ptrdiff_t i = 0; i < n; ++i) {
double sum = v1[i] + v2[i];
v1[i] = sum;
v2[i] = sum;
}
}
double dotProduct(const std::vector<double>& v1, const std::vector<double>& v2) {
if (v1.size() != v2.size()) {
throw std::invalid_argument("Vectors must have the same size for dot product.");
}
double result = 0.0;
const std::ptrdiff_t n = static_cast<std::ptrdiff_t>(v1.size());
#pragma omp parallel for reduction(+:result)
for (std::ptrdiff_t i = 0; i < n; ++i) {
result += v1[i] * v2[i];
}
return result;
}
void calculate_degrees_and_edges_adj_parallel(
const adj_dict_factory& adj,
const std::vector<int>& membership,
bool directed,
int num_communities,
double& e,
double& m,
std::vector<double>& k_out,
std::vector<double>& k_in
)
{
const int N = membership.size();
#pragma omp parallel
{
double local_e = 0.0;
double local_m = 0.0;
std::vector<double> local_k_out(num_communities, 0.0);
std::vector<double> local_k_in(num_communities, 0.0);
double directed_factor = directed ? 1.0 : 2.0;
#pragma omp for
for (int i = 0; i < N; i++) {
node_t u = i + 1;
int c1 = membership[i];
auto adj_it = adj.find(u);
if (adj_it == adj.end()) continue;
auto& u_neighbors = adj_it->second;
for (auto& v_pair : u_neighbors) {
node_t v = v_pair.first;
if (!directed && u > v) continue;
int c2 = membership[v - 1];
double w = 1.0;
if (!v_pair.second.empty()) {
auto it = v_pair.second.begin();
w = it->second;
}
if (c1 == c2) {
local_e += directed_factor * w;
}
local_k_out[c1] += w;
local_k_in[c2] += w;
local_m += w;
}
}
#pragma omp critical
{
e += local_e;
m += local_m;
for (int i = 0; i < num_communities; i++) {
k_out[i] += local_k_out[i];
k_in[i] += local_k_in[i];
}
}
}
}
// The input `communities` may be either:
// (a) a membership list: a flat sequence of ints, membership[i] = community id of node (i+1); or
// (b) a community list: a sequence of iterables of node ids
py::object cpp_modularity(py::object G, py::object communities, py::object weight=py::str("weight")) {
Graph& G_ = G.cast<Graph&>();
bool directed = G.attr("is_directed")().cast<bool>();
adj_dict_factory& adj = G_.adj;
const int N = G_.node.size();
{
bool is_empty_seq = false;
try {
py::sequence seq = communities.cast<py::sequence>();
is_empty_seq = (seq.size() == 0);
} catch (const py::cast_error&) {
is_empty_seq = false;
}
if (is_empty_seq) {
py::module warnings = py::module::import("warnings");
warnings.attr("warn")(
"cpp_modularity: received an empty community list; returning Q = 0.0."
);
return py::float_(0.0);
}
}
std::vector<int> membership_vec;
bool is_membership = false;
py::sequence outer_seq;
try {
outer_seq = communities.cast<py::sequence>();
if (outer_seq.size() > 0) {
try {
outer_seq[0].cast<int>();
is_membership = true;
} catch (const py::cast_error&) {
is_membership = false;
}
}
} catch (const py::cast_error&) {
is_membership = true;
}
if (is_membership) {
// Already a membership vector: membership[i] is the community id of the (i+1)-th node.
membership_vec = communities.cast<std::vector<int>>();
} else {
membership_vec = std::vector<int>(N, -1);
int comm_id = 0;
for (auto community_handle : py::iter(communities)) {
for (auto node_handle : py::iter(community_handle)) {
py::object node = py::reinterpret_borrow<py::object>(node_handle.ptr());
if (G_.node_to_id.contains(node)) {
int node_id = G_.node_to_id[node].cast<node_t>() - 1;
if (node_id >= 0 && node_id < N) {
membership_vec[node_id] = comm_id;
}
}
}
comm_id++;
}
}
int num_communities = membership_vec.size();
double e = 0.0;
double m = 0.0;
std::vector<double> k_out(num_communities, 0.0);
std::vector<double> k_in(num_communities, 0.0);
calculate_degrees_and_edges_adj_parallel(adj, membership_vec, directed, num_communities, e, m, k_out, k_in);
if (!directed) addVectorsInPlace(k_out, k_in);
// Handle empty graph / zero total edge weight: m == 0 makes
// `norm = 1.0 / (directed_factor * m)` divide by zero and produces
// inf / nan. Define Q = 0.0 in this degenerate case (no edges -> no
// community structure to measure), with a Python warning.
if (m == 0.0) {
py::module warnings = py::module::import("warnings");
warnings.attr("warn")(
"cpp_modularity: graph has no edges (m == 0); returning Q = 0.0."
);
return py::float_(0.0);
}
double directed_factor = directed ? 1.0 : 2.0;
double norm = 1.0 / (directed_factor * m);
e *= norm;
double sum_products = dotProduct(k_out, k_in);
sum_products *= norm * norm;
double Q = e - sum_products;
return py::float_(Q);
}
@@ -0,0 +1,11 @@
#pragma once
#include <vector>
#include <unordered_set>
#include <string>
#include "../../classes/graph.h"
using namespace std;
py::object cpp_modularity(py::object G, py::object communities, py::object weight = py::str("weight"));
+553
View File
@@ -0,0 +1,553 @@
#include <vector>
#include <unordered_set>
#include <unordered_map>
#include <random>
#include <algorithm>
#include <queue>
#include <bitset>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
#ifdef _OPENMP
#include <omp.h>
#else
#warning "OpenMP is not available: motif counting functions will fall back to single-threaded execution."
#endif
#include "../../classes/graph.h"
#include "../../classes/linkgraph.h"
#include "motif.h"
namespace py = pybind11;
using namespace std;
const int MAX_NODES = 65536;
struct MotifResult {
vector<int> nodes;
};
class MotifEnumerator {
private:
Graph_L GL;
int k;
int v_start;
vector<int>& id_to_node;
mt19937 rng;
uniform_real_distribution<double> dist;
vector<double> cut_prob;
vector<int> temp_nexcl;
vector<MotifResult> results;
bitset<MAX_NODES> nvp_bitset;
public:
vector<int> node_list;
vector<int> neighbor_offsets;
vector<int> neighbor_data;
int n_edges;
void build_neighbor_structure() {
neighbor_offsets.resize(GL.n + 2, 0);
for (int i = 1; i <= GL.n; ++i) {
int count = 0;
for (int e = GL.head[i]; e != -1; e = GL.edges[e].next) {
count++;
}
neighbor_offsets[i + 1] = neighbor_offsets[i] + count;
}
n_edges = neighbor_offsets[GL.n + 1];
neighbor_data.resize(n_edges);
for (int i = 1; i <= GL.n; ++i) {
int idx = neighbor_offsets[i];
for (int e = GL.head[i]; e != -1; e = GL.edges[e].next) {
neighbor_data[idx++] = GL.edges[e].to;
}
}
node_list.reserve(GL.n);
for (int i = 1; i <= GL.n; ++i) {
if (GL.head[i] != -1) {
node_list.push_back(i);
}
}
}
int get_neighbor_count(int v) const {
return neighbor_offsets[v + 1] - neighbor_offsets[v];
}
const int* get_neighbors_ptr(int v) const {
return neighbor_data.data() + neighbor_offsets[v];
}
void exclusive_neighborhood(int v, const vector<int>& vp, vector<int>& result) {
result.clear();
const int* nbr_ptr = get_neighbors_ptr(v);
int nbr_count = get_neighbor_count(v);
nvp_bitset.reset();
for (int node : vp) {
nvp_bitset.set(node);
const int* node_nbr_ptr = get_neighbors_ptr(node);
int node_nbr_count = get_neighbor_count(node);
for (int i = 0; i < node_nbr_count; ++i) {
nvp_bitset.set(node_nbr_ptr[i]);
}
}
for (int i = 0; i < nbr_count; ++i) {
int neighbor = nbr_ptr[i];
if (!nvp_bitset.test(neighbor)) {
result.push_back(neighbor);
}
}
}
void extend_subgraph_local(const vector<int>& Vsubgraph, const vector<int>& Vextension, int local_v_start,
vector<int>& local_temp_nexcl, bitset<MAX_NODES>& local_nvp_bitset,
vector<MotifResult>& local_results) {
if ((int)Vsubgraph.size() == k) {
local_results.push_back({Vsubgraph});
return;
}
int original_size = Vextension.size();
for (int i = original_size - 1; i >= 0; --i) {
int w = Vextension[i];
if (!cut_prob.empty() && (int)cut_prob.size() > (int)Vsubgraph.size() && dist(rng) > cut_prob[Vsubgraph.size()]) {
continue;
}
vector<int> new_subgraph;
new_subgraph.reserve(Vsubgraph.size() + 1);
new_subgraph.assign(Vsubgraph.begin(), Vsubgraph.end());
new_subgraph.push_back(w);
exclusive_neighborhood_local(w, Vsubgraph, local_temp_nexcl, local_nvp_bitset);
vector<int> next_ext;
next_ext.reserve(original_size + local_temp_nexcl.size());
for (int j = 0; j < original_size; ++j) {
if (j != i) {
int u = Vextension[j];
if (u > local_v_start) {
next_ext.push_back(u);
}
}
}
for (int u : local_temp_nexcl) {
if (u > local_v_start) {
next_ext.push_back(u);
}
}
extend_subgraph_local(new_subgraph, next_ext, local_v_start, local_temp_nexcl, local_nvp_bitset, local_results);
}
}
int extend_subgraph_count_local(const vector<int>& Vsubgraph, const vector<int>& Vextension, int local_v_start, vector<int>& local_temp_nexcl, bitset<MAX_NODES>& local_nvp_bitset) {
if ((int)Vsubgraph.size() == k) {
return 1;
}
int count = 0;
int original_size = Vextension.size();
for (int i = original_size - 1; i >= 0; --i) {
int w = Vextension[i];
vector<int> new_subgraph;
new_subgraph.reserve(Vsubgraph.size() + 1);
new_subgraph.assign(Vsubgraph.begin(), Vsubgraph.end());
new_subgraph.push_back(w);
exclusive_neighborhood_local(w, Vsubgraph, local_temp_nexcl, local_nvp_bitset);
vector<int> next_ext;
next_ext.reserve(original_size + local_temp_nexcl.size());
for (int j = 0; j < original_size; ++j) {
if (j != i) {
int u = Vextension[j];
if (u > local_v_start) {
next_ext.push_back(u);
}
}
}
for (int u : local_temp_nexcl) {
if (u > local_v_start) {
next_ext.push_back(u);
}
}
count += extend_subgraph_count_local(new_subgraph, next_ext, local_v_start, local_temp_nexcl, local_nvp_bitset);
}
return count;
}
int extend_subgraph_count(const vector<int>& Vsubgraph, const vector<int>& Vextension) {
return extend_subgraph_count_local(Vsubgraph, Vextension, v_start, temp_nexcl, nvp_bitset);
}
void exclusive_neighborhood_local(int v, const vector<int>& vp, vector<int>& result, bitset<MAX_NODES>& local_nvp_bitset) {
result.clear();
const int* nbr_ptr = get_neighbors_ptr(v);
int nbr_count = get_neighbor_count(v);
local_nvp_bitset.reset();
for (int node : vp) {
local_nvp_bitset.set(node);
const int* node_nbr_ptr = get_neighbors_ptr(node);
int node_nbr_count = get_neighbor_count(node);
for (int i = 0; i < node_nbr_count; ++i) {
local_nvp_bitset.set(node_nbr_ptr[i]);
}
}
for (int i = 0; i < nbr_count; ++i) {
int neighbor = nbr_ptr[i];
if (!local_nvp_bitset.test(neighbor)) {
result.push_back(neighbor);
}
}
}
public:
MotifEnumerator(Graph_L gl, int k, vector<int>& id_to_node)
: GL(gl), k(k), v_start(0), id_to_node(id_to_node), rng(42), dist(0.0, 1.0) {
build_neighbor_structure();
temp_nexcl.reserve(gl.n);
}
MotifEnumerator(Graph_L gl, int k, vector<int>& id_to_node, const vector<double>& cut_prob_vec, int seed)
: GL(gl), k(k), v_start(0), id_to_node(id_to_node), cut_prob(cut_prob_vec), rng(seed), dist(0.0, 1.0) {
build_neighbor_structure();
temp_nexcl.reserve(gl.n);
}
py::array_t<int> enumerate() {
results.clear();
if (k == 2) {
for (int v : node_list) {
if (!cut_prob.empty() && dist(rng) > cut_prob[0]) {
continue;
}
const int* nbr_ptr = get_neighbors_ptr(v);
int nbr_count = get_neighbor_count(v);
for (int j = 0; j < nbr_count; ++j) {
int u = nbr_ptr[j];
if (u > v) {
results.push_back({vector<int>{v, u}});
}
}
}
return convert_results_to_numpy();
}
vector<vector<MotifResult>> thread_results(8);
#ifdef _OPENMP
omp_set_num_threads(8);
#endif
#pragma omp parallel
{
int thread_id = 0;
#ifdef _OPENMP
thread_id = omp_get_thread_num();
#endif
vector<MotifResult>& local_results = thread_results[thread_id];
local_results.reserve(10000);
#pragma omp for schedule(dynamic)
for (int i = 0; i < (int)node_list.size(); ++i) {
int v = node_list[i];
if (!cut_prob.empty() && dist(rng) > cut_prob[0]) {
continue;
}
int local_v_start = v;
vector<int> Vsubgraph;
Vsubgraph.reserve(k);
Vsubgraph.push_back(v);
vector<int> Vextension;
Vextension.reserve(GL.n);
const int* nbr_ptr = get_neighbors_ptr(v);
int nbr_count = get_neighbor_count(v);
for (int j = 0; j < nbr_count; ++j) {
int u = nbr_ptr[j];
if (u > v) {
Vextension.push_back(u);
}
}
vector<int> local_temp_nexcl;
local_temp_nexcl.reserve(GL.n);
bitset<MAX_NODES> local_nvp_bitset;
extend_subgraph_local(Vsubgraph, Vextension, local_v_start, local_temp_nexcl, local_nvp_bitset, local_results);
}
}
for (const auto& tr : thread_results) {
results.insert(results.end(), tr.begin(), tr.end());
}
return convert_results_to_numpy();
}
void extend_subgraph(const vector<int>& Vsubgraph, const vector<int>& Vextension) {
if ((int)Vsubgraph.size() == k) {
results.push_back({Vsubgraph});
return;
}
int original_size = Vextension.size();
for (int i = original_size - 1; i >= 0; --i) {
int w = Vextension[i];
if (w <= v_start) {
continue;
}
vector<int> new_subgraph;
new_subgraph.reserve(Vsubgraph.size() + 1);
new_subgraph.assign(Vsubgraph.begin(), Vsubgraph.end());
new_subgraph.push_back(w);
exclusive_neighborhood(w, Vsubgraph, temp_nexcl);
vector<int> next_ext;
next_ext.reserve(original_size + temp_nexcl.size());
for (int j = 0; j < original_size; ++j) {
if (j != i) {
int u = Vextension[j];
if (u > v_start) {
next_ext.push_back(u);
}
}
}
for (int u : temp_nexcl) {
if (u > v_start) {
next_ext.push_back(u);
}
}
extend_subgraph(new_subgraph, next_ext);
}
}
int count_enumerate() {
int count = 0;
if (k == 2) {
for (int v : node_list) {
const int* nbr_ptr = get_neighbors_ptr(v);
int nbr_count = get_neighbor_count(v);
for (int j = 0; j < nbr_count; ++j) {
int u = nbr_ptr[j];
if (u > v) {
count++;
}
}
}
return count;
}
#ifdef _OPENMP
omp_set_num_threads(8);
#endif
#pragma omp parallel reduction(+:count)
{
#pragma omp for schedule(dynamic)
for (int i = 0; i < (int)node_list.size(); ++i) {
int v = node_list[i];
int local_v_start = v;
vector<int> Vsubgraph;
Vsubgraph.reserve(k);
Vsubgraph.push_back(v);
vector<int> Vextension;
Vextension.reserve(GL.n);
const int* nbr_ptr = get_neighbors_ptr(v);
int nbr_count = get_neighbor_count(v);
for (int j = 0; j < nbr_count; ++j) {
int u = nbr_ptr[j];
if (u > v) {
Vextension.push_back(u);
}
}
vector<int> local_temp_nexcl;
local_temp_nexcl.reserve(GL.n);
bitset<MAX_NODES> local_nvp_bitset;
count += extend_subgraph_count_local(Vsubgraph, Vextension, local_v_start, local_temp_nexcl, local_nvp_bitset);
}
}
return count;
}
py::list convert_results_to_python() {
py::list py_results;
for (const auto& result : results) {
py::set py_set;
for (int node_id : result.nodes) {
if (node_id > 0 && node_id < (int)id_to_node.size()) {
py_set.add(py::cast(id_to_node[node_id]));
}
}
py_results.append(py_set);
}
return py_results;
}
py::array_t<int> convert_results_to_numpy() {
size_t num_results = results.size();
size_t k_size = k;
int* data = new int[num_results * k_size];
size_t idx = 0;
for (const auto& result : results) {
for (size_t i = 0; i < k_size; ++i) {
if (i < result.nodes.size() && result.nodes[i] > 0 && result.nodes[i] < (int)id_to_node.size()) {
data[idx++] = id_to_node[result.nodes[i]];
} else {
data[idx++] = -1;
}
}
}
// Create a capsule to manage the memory
py::capsule capsule(data, [](void* ptr) {
delete[] static_cast<int*>(ptr);
});
// Create numpy array with the capsule
py::array_t<int> result_array = py::array_t<int>(
{num_results, k_size}, // shape
{k_size * sizeof(int), sizeof(int)}, // strides
data,
capsule
);
return result_array;
}
const vector<MotifResult>& get_results() const {
return results;
}
size_t get_k() const {
return k;
}
};
py::list cpp_enumerate_subgraph(py::object G, int k) {
Graph& G_ = G.cast<Graph&>();
Graph_L GL = G_.linkgraph_structure;
py::dict id_to_node_py = G_.id_to_node;
vector<int> id_to_node_vec;
id_to_node_vec.reserve(id_to_node_py.size() + 1);
id_to_node_vec.push_back(0);
for (int i = 1; i <= (int)id_to_node_py.size(); ++i) {
py::object node_obj = id_to_node_py[py::cast(i)];
id_to_node_vec.push_back(node_obj.cast<int>());
}
MotifEnumerator enumerator(GL, k, id_to_node_vec);
enumerator.enumerate();
return enumerator.convert_results_to_python();
}
py::int_ cpp_count_enumerate_subgraph(py::object G, int k) {
Graph& G_ = G.cast<Graph&>();
Graph_L GL = G_.linkgraph_structure;
py::dict id_to_node_py = G_.id_to_node;
vector<int> id_to_node_vec;
id_to_node_vec.reserve(id_to_node_py.size() + 1);
id_to_node_vec.push_back(0);
for (int i = 1; i <= (int)id_to_node_py.size(); ++i) {
py::object node_obj = id_to_node_py[py::cast(i)];
id_to_node_vec.push_back(node_obj.cast<int>());
}
MotifEnumerator enumerator(GL, k, id_to_node_vec);
int count = enumerator.count_enumerate();
return count;
}
py::list cpp_random_enumerate_subgraph(py::object G, int k, py::object cut_prob) {
Graph& G_ = G.cast<Graph&>();
Graph_L GL = G_.linkgraph_structure;
py::dict id_to_node_py = G_.id_to_node;
vector<int> id_to_node_vec;
id_to_node_vec.reserve(id_to_node_py.size() + 1);
id_to_node_vec.push_back(0);
for (int i = 1; i <= (int)id_to_node_py.size(); ++i) {
py::object node_obj = id_to_node_py[py::cast(i)];
id_to_node_vec.push_back(node_obj.cast<int>());
}
vector<double> cut_prob_vec;
if (py::isinstance<py::list>(cut_prob)) {
py::list cut_prob_list = cut_prob.cast<py::list>();
cut_prob_vec.reserve(cut_prob_list.size());
for (size_t i = 0; i < cut_prob_list.size(); ++i) {
cut_prob_vec.push_back(cut_prob_list[i].cast<double>());
}
} else {
throw std::runtime_error("cut_prob must be a list");
}
if (cut_prob_vec.size() != k) {
throw py::value_error("length of cut_prob invalid, should equal to k");
}
MotifEnumerator enumerator(GL, k, id_to_node_vec, cut_prob_vec, 42);
enumerator.enumerate();
return enumerator.convert_results_to_python();
}
py::list cpp_random_enumerate_subgraph_with_seed(py::object G, int k, py::object cut_prob, int seed) {
Graph& G_ = G.cast<Graph&>();
Graph_L GL = G_.linkgraph_structure;
py::dict id_to_node_py = G_.id_to_node;
vector<int> id_to_node_vec;
id_to_node_vec.reserve(id_to_node_py.size() + 1);
id_to_node_vec.push_back(0);
for (int i = 1; i <= (int)id_to_node_py.size(); ++i) {
py::object node_obj = id_to_node_py[py::cast(i)];
id_to_node_vec.push_back(node_obj.cast<int>());
}
vector<double> cut_prob_vec;
if (py::isinstance<py::list>(cut_prob)) {
py::list cut_prob_list = cut_prob.cast<py::list>();
cut_prob_vec.reserve(cut_prob_list.size());
for (size_t i = 0; i < cut_prob_list.size(); ++i) {
cut_prob_vec.push_back(cut_prob_list[i].cast<double>());
}
} else {
throw std::runtime_error("cut_prob must be a list");
}
if (cut_prob_vec.size() != k) {
throw py::value_error("length of cut_prob invalid, should equal to k");
}
MotifEnumerator enumerator(GL, k, id_to_node_vec, cut_prob_vec, seed);
enumerator.enumerate();
return enumerator.convert_results_to_python();
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
py::list cpp_enumerate_subgraph(py::object G, int k);
py::int_ cpp_count_enumerate_subgraph(py::object G, int k);
py::list cpp_random_enumerate_subgraph(py::object G, int k, py::object cut_prob);
@@ -0,0 +1,179 @@
#include "subgraph.h"
#include "../../classes/graph.h"
#include "../../classes/directed_graph.h"
#include "../../common/utils.h"
static node_t _add_one_node_for_subgraph(Graph& self, py::object one_node_for_adding, py::object node_attr = py::dict()) {
node_t id;
if (self.node_to_id.contains(one_node_for_adding)) {
id = self.node_to_id[one_node_for_adding].cast<node_t>();
} else {
id = ++(self.id);
self.id_to_node[py::cast(id)] = one_node_for_adding;
self.node_to_id[one_node_for_adding] = id;
}
py::list items = py::list(node_attr.attr("items")());
self.node[id] = node_attr_dict_factory();
for (int i = 0; i < len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
self.node[id].insert(std::make_pair(weight_key, value));
}
return id;
}
static void _add_one_edge_for_subgraph(Graph& self, node_t u, node_t v, py::object edge_attr) {
py::list items = py::list(edge_attr.attr("items")());
self.adj[u][v] = node_attr_dict_factory();
self.adj[v][u] = node_attr_dict_factory();
for (int i = 0; i < len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
self.adj[u][v].insert(std::make_pair(weight_key, value));
self.adj[v][u].insert(std::make_pair(weight_key, value));
}
}
static py::object _nodes_subgraph_cpp_graph(py::object self, std::vector<node_t>& node_ids) {
Graph& self_ = self.cast<Graph&>();
py::object G = self.attr("__class__")();
Graph& G_ = G.cast<Graph&>();
G_.graph.attr("update")(self_.graph);
py::object nodes = self.attr("nodes");
py::object adj = self.attr("adj");
// 修复:维护原图内部索引到新图内部索引的映射
std::unordered_map<node_t, node_t> old_id_to_new_id;
// 第一遍:添加所有节点并建立映射
for (node_t node_id : node_ids) {
py::object node = self_.id_to_node[py::cast(node_id)];
py::object node_attr = nodes[node];
node_t new_id = _add_one_node_for_subgraph(G_, node, node_attr);
old_id_to_new_id[node_id] = new_id;
}
// 第二遍:添加边(使用映射后的索引)
for (node_t node_id : node_ids) {
py::object node = self_.id_to_node[py::cast(node_id)];
py::object out_edges = adj[node];
py::list edge_items = py::list(out_edges.attr("items")());
// 获取当前节点的新索引
node_t new_u = old_id_to_new_id[node_id];
for (int j = 0; j < py::len(edge_items); j++) {
py::tuple item = edge_items[j].cast<py::tuple>();
py::object v = item[0];
py::object edge_attr = item[1];
if (self_.node_to_id.contains(v)) {
node_t v_id = self_.node_to_id[v].cast<node_t>();
bool v_in_subgraph = std::find(node_ids.begin(), node_ids.end(), v_id) != node_ids.end();
if (v_in_subgraph) {
// 使用映射后的索引
node_t new_v = old_id_to_new_id[v_id];
_add_one_edge_for_subgraph(G_, new_u, new_v, edge_attr);
}
}
}
}
return G;
}
static node_t DiGraph_add_one_node_for_subgraph(DiGraph& self, py::object one_node_for_adding, py::object node_attr = py::dict()) {
node_t id;
if (self.node_to_id.contains(one_node_for_adding)) {
id = self.node_to_id[one_node_for_adding].cast<node_t>();
} else {
id = ++(self.id);
self.id_to_node[py::cast(id)] = one_node_for_adding;
self.node_to_id[one_node_for_adding] = id;
}
py::list items = py::list(node_attr.attr("items")());
self.node[id] = node_attr_dict_factory();
for (int i = 0; i < len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
self.node[id].insert(std::make_pair(weight_key, value));
}
return id;
}
static void DiGraph_add_one_edge_for_subgraph(DiGraph& self, node_t u, node_t v, py::object edge_attr) {
py::list items = py::list(edge_attr.attr("items")());
self.adj[u][v] = node_attr_dict_factory();
for (int i = 0; i < len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
self.adj[u][v].insert(std::make_pair(weight_key, value));
}
}
static py::object _nodes_subgraph_cpp_digraph(py::object self, std::vector<node_t>& node_ids) {
DiGraph& self_ = self.cast<DiGraph&>();
py::object G = self.attr("__class__")();
DiGraph& G_ = G.cast<DiGraph&>();
G_.graph.attr("update")(self_.graph);
py::object nodes = self.attr("nodes");
py::object adj = self.attr("adj");
// 修复:维护原图内部索引到新图内部索引的映射
std::unordered_map<node_t, node_t> old_id_to_new_id;
// 第一遍:添加所有节点并建立映射
for (node_t node_id : node_ids) {
py::object node = self_.id_to_node[py::cast(node_id)];
py::object node_attr = nodes[node];
node_t new_id = DiGraph_add_one_node_for_subgraph(G_, node, node_attr);
old_id_to_new_id[node_id] = new_id;
}
// 第二遍:添加边(使用映射后的索引)
for (node_t node_id : node_ids) {
py::object node = self_.id_to_node[py::cast(node_id)];
py::object out_edges = adj[node];
py::list edge_items = py::list(out_edges.attr("items")());
// 获取当前节点的新索引
node_t new_u = old_id_to_new_id[node_id];
for (int j = 0; j < py::len(edge_items); j++) {
py::tuple item = edge_items[j].cast<py::tuple>();
py::object v = item[0];
py::object edge_attr = item[1];
if (self_.node_to_id.contains(v)) {
node_t v_id = self_.node_to_id[v].cast<node_t>();
bool v_in_subgraph = std::find(node_ids.begin(), node_ids.end(), v_id) != node_ids.end();
if (v_in_subgraph) {
// 使用映射后的索引
node_t new_v = old_id_to_new_id[v_id];
DiGraph_add_one_edge_for_subgraph(G_, new_u, new_v, edge_attr);
}
}
}
}
return G;
}
py::object nodes_subgraph_cpp(py::object self, std::vector<node_t>& node_ids) {
bool is_directed = self.attr("is_directed")().cast<bool>();
if (is_directed) {
return _nodes_subgraph_cpp_digraph(self, node_ids);
} else {
return _nodes_subgraph_cpp_graph(self, node_ids);
}
}
@@ -0,0 +1,10 @@
#pragma once
#include <vector>
#include <memory>
#include "../../common/common.h"
#include "../../classes/graph.h"
#include "../../classes/directed_graph.h"
#include "../../classes/csr_graph.h"
py::object nodes_subgraph_cpp(py::object self, std::vector<node_t>& node_ids);
@@ -0,0 +1 @@
# TEST package init
@@ -0,0 +1,139 @@
import unittest
import easygraph as eg
def _get_cpp_module():
try:
import cpp_easygraph
return cpp_easygraph
except ImportError:
return None
def _to_undirected_cpp(G_cpp_digraph):
import cpp_easygraph
G_cpp = cpp_easygraph.Graph()
G_cpp.graph.update(G_cpp_digraph.graph)
for node, node_attr in G_cpp_digraph.nodes.items():
G_cpp.add_node(node, **node_attr)
seen_edges = set()
for u, v, edge_data in G_cpp_digraph.edges:
edge = (min(u, v), max(u, v))
if edge not in seen_edges:
seen_edges.add(edge)
G_cpp.add_edge(u, v, **edge_data)
return G_cpp
class TestLPA(unittest.TestCase):
def setUp(self):
self.G_simple = eg.Graph()
self.G_simple.add_edges_from([(0, 1), (1, 2), (3, 4)])
self.G_weighted = eg.Graph()
self.G_weighted.add_edges_from([
(0, 1, {"weight": 3}),
(1, 2, {"weight": 2}),
(2, 0, {"weight": 4}),
(3, 4, {"weight": 1}),
])
self.G_disconnected = eg.Graph()
self.G_disconnected.add_edges_from([(0, 1), (2, 3), (4, 5)])
self.G_single = eg.Graph()
self.G_single.add_node(42)
self.G_empty = eg.Graph()
def _get_cpp_module(self):
return _get_cpp_module()
def test_lpa_simple(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
result = cpeg.cpp_LPA(self.G_simple.cpp())
self.assertIsInstance(result, dict)
all_nodes = set()
for community in result.values():
all_nodes.update(community)
self.assertEqual(all_nodes, set(self.G_simple.nodes))
def test_lpa_weighted(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
result = cpeg.cpp_LPA(self.G_weighted.cpp())
self.assertIsInstance(result, dict)
all_nodes = set(self.G_weighted.nodes)
result_nodes = set()
for community in result.values():
result_nodes.update(community)
self.assertEqual(all_nodes, result_nodes)
def test_lpa_disconnected(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
result = cpeg.cpp_LPA(self.G_disconnected.cpp())
self.assertIsInstance(result, dict)
all_nodes = set(self.G_disconnected.nodes)
result_nodes = set()
for community in result.values():
result_nodes.update(community)
self.assertEqual(all_nodes, result_nodes)
def test_lpa_single_node(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
result = cpeg.cpp_LPA(self.G_single.cpp())
self.assertIsInstance(result, dict)
self.assertEqual(len(result), 1)
for community in result.values():
self.assertIn(42, community)
def test_lpa_empty_graph(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
result = cpeg.cpp_LPA(self.G_empty.cpp())
self.assertIsInstance(result, dict)
self.assertEqual(result, {})
def test_python_cpp_consistency(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
result_cpp = cpeg.cpp_LPA(self.G_simple.cpp())
result_py = eg.functions.community.LPA(self.G_simple)
self.assertEqual(len(result_cpp), len(result_py))
cpp_nodes = set()
for community in result_cpp.values():
cpp_nodes.update(community)
py_nodes = set()
for community in result_py.values():
py_nodes.update(community)
self.assertEqual(cpp_nodes, py_nodes)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,254 @@
import unittest
import easygraph as eg
def _get_cpp_module():
"""获取cpp_easygraph模块"""
try:
import cpp_easygraph
return cpp_easygraph
except ImportError:
return None
def _to_undirected_cpp(G_cpp_digraph):
"""将C++ DiGraph转换为C++ Graph无向图"""
import cpp_easygraph
G_cpp = cpp_easygraph.Graph()
G_cpp.graph.update(G_cpp_digraph.graph)
for node, node_attr in G_cpp_digraph.nodes.items():
G_cpp.add_node(node, **node_attr)
seen_edges = set()
for u, v, edge_data in G_cpp_digraph.edges:
edge = (min(u, v), max(u, v))
if edge not in seen_edges:
seen_edges.add(edge)
G_cpp.add_edge(u, v, **edge_data)
return G_cpp
def _csr_dict_to_graph(csr_dict):
import cpp_easygraph
if not csr_dict or 'nodes' not in csr_dict:
return cpp_easygraph.Graph()
G = cpp_easygraph.Graph()
nodes = csr_dict['nodes']
for node in nodes:
G.add_node(node)
V = csr_dict['V']
E = csr_dict['E']
W = csr_dict.get('W', [1.0] * len(E))
for u in range(len(V) - 1):
start = V[u]
end = V[u + 1]
for i in range(start, end):
v_idx = E[i]
weight = W[i] if i < len(W) else 1.0
G.add_edge(nodes[u], nodes[v_idx], weight=weight)
return G
class TestEgoGraph(unittest.TestCase):
def setUp(self):
self.simple_graph = eg.Graph()
self.simple_graph.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 4)])
self.directed_graph = eg.DiGraph()
self.directed_graph.add_edges_from([(0, 1), (1, 2), (2, 3)])
self.weighted_graph = eg.Graph()
self.weighted_graph.add_edges_from(
[(0, 1, {"weight": 1}), (1, 2, {"weight": 2}), (2, 3, {"weight": 3})]
)
self.disconnected_graph = eg.Graph()
self.disconnected_graph.add_edges_from([(0, 1), (2, 3)])
self.single_node_graph = eg.Graph()
self.single_node_graph.add_node(42)
def _get_cpp_module(self):
return _get_cpp_module()
def test_ego_graph_simple_radius_1(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.simple_graph.cpp()
ego = cpeg.cpp_ego_graph(G_cpp, 2, radius=1)
self.assertIsInstance(ego, type(G_cpp))
self.assertIn(2, ego.nodes)
self.assertIn(1, ego.nodes)
self.assertIn(3, ego.nodes)
def test_ego_graph_simple_radius_2(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.simple_graph.cpp()
ego = cpeg.cpp_ego_graph(G_cpp, 2, radius=2)
self.assertIsInstance(ego, type(G_cpp))
self.assertIn(0, ego.nodes)
self.assertIn(1, ego.nodes)
self.assertIn(2, ego.nodes)
self.assertIn(3, ego.nodes)
self.assertIn(4, ego.nodes)
def test_ego_graph_directed(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.directed_graph.cpp()
ego = cpeg.cpp_ego_graph(G_cpp, 1, radius=1)
self.assertIsInstance(ego, type(G_cpp))
self.assertIn(1, ego.nodes)
self.assertIn(2, ego.nodes)
def test_ego_graph_weighted_with_distance(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.weighted_graph.cpp()
G_cpp.generate_linkgraph('weight')
ego = cpeg.cpp_ego_graph(G_cpp, 0, radius=2, distance="weight")
self.assertIsInstance(ego, type(G_cpp))
self.assertIn(0, ego.nodes)
self.assertIn(1, ego.nodes)
def test_ego_graph_disconnected(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.disconnected_graph.cpp()
ego = cpeg.cpp_ego_graph(G_cpp, 0, radius=1)
self.assertIsInstance(ego, type(G_cpp))
self.assertIn(0, ego.nodes)
self.assertIn(1, ego.nodes)
def test_ego_graph_single_node(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.single_node_graph.cpp()
ego = cpeg.cpp_ego_graph(G_cpp, 42, radius=1)
self.assertIsInstance(ego, type(G_cpp))
self.assertIn(42, ego.nodes)
def test_ego_graph_center_false(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.simple_graph.cpp()
ego = cpeg.cpp_ego_graph(G_cpp, 2, radius=1, center=False)
self.assertIsInstance(ego, type(G_cpp))
self.assertNotIn(2, ego.nodes)
self.assertIn(1, ego.nodes)
self.assertIn(3, ego.nodes)
def test_python_cpp_consistency(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.simple_graph.cpp()
ego_py = eg.functions.community.ego_graph(self.simple_graph, 2, radius=1)
ego_cpp = cpeg.cpp_ego_graph(G_cpp, 2, radius=1)
self.assertEqual(set(ego_py.nodes), set(ego_cpp.nodes))
class TestEgoGraphCSR(unittest.TestCase):
def setUp(self):
self.simple_graph = eg.Graph()
self.simple_graph.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 4)])
self.disconnected_graph = eg.Graph()
self.disconnected_graph.add_edges_from([(0, 1), (2, 3)])
self.single_node_graph = eg.Graph()
self.single_node_graph.add_node(42)
def _get_cpp_module(self):
return _get_cpp_module()
def test_ego_graph_csr_simple(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.simple_graph.cpp()
G_cpp.generate_linkgraph('weight')
ego_csr_dict = cpeg.cpp_ego_graph_csr(G_cpp, 2, radius=1)
ego = _csr_dict_to_graph(ego_csr_dict)
self.assertTrue(hasattr(ego, 'nodes'))
self.assertIn(2, ego.nodes)
def test_ego_graph_csr_disconnected(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.disconnected_graph.cpp()
G_cpp.generate_linkgraph('weight')
ego_csr_dict = cpeg.cpp_ego_graph_csr(G_cpp, 0, radius=1)
ego = _csr_dict_to_graph(ego_csr_dict)
self.assertTrue(hasattr(ego, 'nodes'))
self.assertIn(0, ego.nodes)
self.assertIn(1, ego.nodes)
def test_ego_graph_csr_single_node(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.single_node_graph.cpp()
G_cpp.generate_linkgraph('weight')
ego_csr_dict = cpeg.cpp_ego_graph_csr(G_cpp, 42, radius=1)
ego = _csr_dict_to_graph(ego_csr_dict)
self.assertTrue(hasattr(ego, 'nodes'))
self.assertIn(42, ego.nodes)
def test_csr_vs_original_consistency(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.simple_graph.cpp()
G_cpp.generate_linkgraph('weight')
ego_original = cpeg.cpp_ego_graph(G_cpp, 2, radius=1, undirected=False)
ego_csr_dict = cpeg.cpp_ego_graph_csr(G_cpp, 2, radius=1)
ego_csr = _csr_dict_to_graph(ego_csr_dict)
self.assertIn(2, ego_original.nodes)
self.assertIn(2, ego_csr.nodes)
self.assertTrue(set(ego_csr.nodes).issubset(set(ego_original.nodes) | {4}))
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,143 @@
import random
import unittest
import easygraph as eg
def _get_cpp_module():
try:
import cpp_easygraph
return cpp_easygraph
except ImportError:
return None
def _to_undirected_cpp(G_cpp_digraph):
import cpp_easygraph
G_cpp = cpp_easygraph.Graph()
G_cpp.graph.update(G_cpp_digraph.graph)
for node, node_attr in G_cpp_digraph.nodes.items():
G_cpp.add_node(node, **node_attr)
seen_edges = set()
for u, v, edge_data in G_cpp_digraph.edges:
edge = (min(u, v), max(u, v))
if edge not in seen_edges:
seen_edges.add(edge)
G_cpp.add_edge(u, v, **edge_data)
return G_cpp
class TestEnumerateSubgraph(unittest.TestCase):
def setUp(self):
self.G_triangle = eg.Graph()
self.G_triangle.add_edges_from([(1, 2), (2, 3), (3, 1)])
self.G_complex = eg.Graph()
self.G_complex.add_edges_from([
(1, 3), (2, 3), (3, 4), (4, 5), (3, 5)
])
self.G_empty = eg.Graph()
self.G_small = eg.Graph()
self.G_small.add_edges_from([(1, 2)])
def _get_cpp_module(self):
return _get_cpp_module()
def test_enumerate_subgraph_k3(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
motifs = cpeg.cpp_enumerate_subgraph(self.G_complex.cpp(), 3)
self.assertIsInstance(motifs, list)
for m in motifs:
self.assertEqual(len(m), 3)
def test_enumerate_subgraph_k4(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
motifs = cpeg.cpp_enumerate_subgraph(self.G_complex.cpp(), 4)
for m in motifs:
self.assertEqual(len(m), 4)
def test_enumerate_subgraph_empty_graph(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
motifs = cpeg.cpp_enumerate_subgraph(self.G_empty.cpp(), 3)
self.assertEqual(motifs, [])
def test_enumerate_subgraph_k_larger_than_graph(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
motifs = cpeg.cpp_enumerate_subgraph(self.G_small.cpp(), 3)
self.assertEqual(motifs, [])
def test_python_cpp_consistency(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
motifs_py = eg.enumerate_subgraph(self.G_complex, 3)
motifs_py = [frozenset(m) for m in motifs_py]
motifs_cpp = cpeg.cpp_enumerate_subgraph(self.G_complex.cpp(), 3)
motifs_cpp = [frozenset(m) for m in motifs_cpp]
self.assertEqual(set(motifs_py), set(motifs_cpp))
class TestRandomEnumerateSubgraph(unittest.TestCase):
def setUp(self):
self.G_complex = eg.Graph()
self.G_complex.add_edges_from([
(1, 3), (2, 3), (3, 4), (4, 5), (3, 5)
])
self.G_empty = eg.Graph()
def _get_cpp_module(self):
return _get_cpp_module()
def test_random_enumerate_valid_cut_prob(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
cut_prob = [1.0, 1.0, 1.0]
motifs = cpeg.cpp_random_enumerate_subgraph(self.G_complex.cpp(), 3, cut_prob)
self.assertIsInstance(motifs, list)
def test_random_enumerate_zero_cut_prob(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
cut_prob = [0.0, 0.0, 0.0]
motifs = cpeg.cpp_random_enumerate_subgraph(self.G_complex.cpp(), 3, cut_prob)
self.assertEqual(motifs, [])
def test_random_enumerate_different_results(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
cut_prob = [1.0, 1.0, 1.0]
motifs1 = cpeg.cpp_random_enumerate_subgraph(self.G_complex.cpp(), 3, cut_prob)
motifs2 = cpeg.cpp_random_enumerate_subgraph(self.G_complex.cpp(), 3, cut_prob)
self.assertIsInstance(motifs1, list)
self.assertIsInstance(motifs2, list)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,117 @@
import unittest
import easygraph as eg
def _get_cpp_module():
try:
import cpp_easygraph
return cpp_easygraph
except ImportError:
return None
def _to_undirected_cpp(G_cpp_digraph):
import cpp_easygraph
G_cpp = cpp_easygraph.Graph()
G_cpp.graph.update(G_cpp_digraph.graph)
for node, node_attr in G_cpp_digraph.nodes.items():
G_cpp.add_node(node, **node_attr)
seen_edges = set()
for u, v, edge_data in G_cpp_digraph.edges:
edge = (min(u, v), max(u, v))
if edge not in seen_edges:
seen_edges.add(edge)
G_cpp.add_edge(u, v, **edge_data)
return G_cpp
class TestGreedyModularity(unittest.TestCase):
def setUp(self):
self.G_simple = eg.Graph()
self.G_simple.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 0)])
self.G_disconnected = eg.Graph()
self.G_disconnected.add_edges_from([(0, 1), (2, 3), (4, 5)])
self.G_weighted = eg.Graph()
self.G_weighted.add_edge(0, 1, weight=5)
self.G_weighted.add_edge(1, 2, weight=3)
self.G_weighted.add_edge(2, 0, weight=2)
self.G_weighted.add_edge(3, 4, weight=1)
self.G_single = eg.Graph()
self.G_single.add_node(42)
self.G_empty = eg.Graph()
def _get_cpp_module(self):
return _get_cpp_module()
def test_greedy_simple(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_greedy_modularity_communities(self.G_simple.cpp())
self.assertIsInstance(communities, list)
flat = {node for comm in communities for node in comm}
self.assertSetEqual(flat, set(self.G_simple.nodes))
def test_greedy_weighted(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_greedy_modularity_communities(self.G_weighted.cpp(), weight="weight")
self.assertIsInstance(communities, list)
flat = {node for comm in communities for node in comm}
self.assertSetEqual(flat, set(self.G_weighted.nodes))
def test_greedy_disconnected(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_greedy_modularity_communities(self.G_disconnected.cpp())
self.assertIsInstance(communities, list)
flat = {node for comm in communities for node in comm}
self.assertSetEqual(flat, set(self.G_disconnected.nodes))
def test_greedy_single_node(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_greedy_modularity_communities(self.G_single.cpp())
self.assertEqual(len(communities), 1)
self.assertIn(42, communities[0])
def test_greedy_empty_graph(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_greedy_modularity_communities(self.G_empty.cpp())
self.assertEqual(communities, [])
def test_python_cpp_consistency(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities_cpp = cpeg.cpp_greedy_modularity_communities(self.G_simple.cpp())
communities_py = eg.functions.community.greedy_modularity_communities(self.G_simple)
self.assertEqual(len(communities_cpp), len(communities_py))
flat_cpp = {node for comm in communities_cpp for node in comm}
flat_py = {node for comm in communities_py for node in comm}
self.assertEqual(flat_cpp, flat_py)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,188 @@
import unittest
import easygraph as eg
def _get_cpp_module():
try:
import cpp_easygraph
return cpp_easygraph
except ImportError:
return None
def _to_undirected_cpp(G_cpp_digraph):
import cpp_easygraph
G_cpp = cpp_easygraph.Graph()
G_cpp.graph.update(G_cpp_digraph.graph)
for node, node_attr in G_cpp_digraph.nodes.items():
G_cpp.add_node(node, **node_attr)
seen_edges = set()
for u, v, edge_data in G_cpp_digraph.edges:
edge = (min(u, v), max(u, v))
if edge not in seen_edges:
seen_edges.add(edge)
G_cpp.add_edge(u, v, **edge_data)
return G_cpp
class TestLocalsearch(unittest.TestCase):
def setUp(self):
self.G_simple = eg.Graph()
self.G_simple.add_edges_from([(0, 2), (0, 3), (0, 4), (0, 5), (1, 2), (1, 3), (1, 4), (1, 5)])
self.G_disconnected = eg.Graph()
self.G_disconnected.add_edges_from([(0, 1), (2, 3), (4, 5)])
self.G_single = eg.Graph()
self.G_single.add_node(42)
self.G_empty = eg.Graph()
def _get_cpp_module(self):
return _get_cpp_module()
def test_localsearch_simple(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.G_simple.cpp()
G_cpp.generate_linkgraph('weight')
result = cpeg.cpp_localsearch(G_cpp, seed=163)
self.assertIsInstance(result, tuple)
self.assertEqual(len(result), 6)
y_partition = result[3]
self.assertIsInstance(y_partition, dict)
all_nodes = set(self.G_simple.nodes)
result_nodes = set(y_partition.keys())
self.assertEqual(all_nodes, result_nodes)
def test_localsearch_disconnected(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.G_disconnected.cpp()
G_cpp.generate_linkgraph('weight')
result = cpeg.cpp_localsearch(G_cpp, seed=163)
self.assertIsInstance(result, tuple)
self.assertEqual(len(result), 6)
y_partition = result[3]
self.assertIsInstance(y_partition, dict)
all_nodes = set(self.G_disconnected.nodes)
result_nodes = set(y_partition.keys())
self.assertEqual(all_nodes, result_nodes)
def test_localsearch_single_node(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.G_single.cpp()
G_cpp.generate_linkgraph('weight')
result = cpeg.cpp_localsearch(G_cpp, seed=163)
self.assertIsInstance(result, tuple)
self.assertEqual(len(result), 6)
y_partition = result[3]
self.assertIsInstance(y_partition, dict)
self.assertIn(42, y_partition)
def test_localsearch_empty_graph(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.G_empty.cpp()
G_cpp.generate_linkgraph('weight')
result = cpeg.cpp_localsearch(G_cpp, seed=163)
self.assertIsInstance(result, tuple)
self.assertEqual(len(result), 6)
y_partition = result[3]
self.assertIsInstance(y_partition, dict)
def test_localsearch_maximum_tree_true(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.G_simple.cpp()
G_cpp.generate_linkgraph('weight')
result = cpeg.cpp_localsearch(G_cpp, maximum_tree=True, seed=163)
self.assertIsInstance(result, tuple)
self.assertEqual(len(result), 6)
y_partition = result[3]
self.assertIsInstance(y_partition, dict)
def test_localsearch_maximum_tree_false(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.G_simple.cpp()
G_cpp.generate_linkgraph('weight')
result = cpeg.cpp_localsearch(G_cpp, maximum_tree=False, seed=163)
self.assertIsInstance(result, tuple)
self.assertEqual(len(result), 6)
y_partition = result[3]
self.assertIsInstance(y_partition, dict)
def test_localsearch_auto_choose_centers(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.G_simple.cpp()
G_cpp.generate_linkgraph('weight')
result = cpeg.cpp_localsearch(G_cpp, auto_choose_centers=True, seed=163)
self.assertIsInstance(result, tuple)
self.assertEqual(len(result), 6)
y_partition = result[3]
self.assertIsInstance(y_partition, dict)
def test_localsearch_center_num(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.G_simple.cpp()
G_cpp.generate_linkgraph('weight')
result = cpeg.cpp_localsearch(G_cpp, center_num=2, seed=163)
self.assertIsInstance(result, tuple)
self.assertEqual(len(result), 6)
y_partition = result[3]
self.assertIsInstance(y_partition, dict)
def test_localsearch_with_seed_reproducibility(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.G_simple.cpp()
G_cpp.generate_linkgraph('weight')
result1 = cpeg.cpp_localsearch(G_cpp, seed=42)
result2 = cpeg.cpp_localsearch(G_cpp, seed=42)
self.assertIsInstance(result1, tuple)
self.assertIsInstance(result2, tuple)
y_partition1 = result1[3]
y_partition2 = result2[3]
self.assertEqual(y_partition1.keys(), y_partition2.keys())
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,178 @@
import unittest
import easygraph as eg
def _get_cpp_module():
try:
import cpp_easygraph
return cpp_easygraph
except ImportError:
return None
def _to_undirected_cpp(G_cpp_digraph):
import cpp_easygraph
G_cpp = cpp_easygraph.Graph()
G_cpp.graph.update(G_cpp_digraph.graph)
for node, node_attr in G_cpp_digraph.nodes.items():
G_cpp.add_node(node, **node_attr)
seen_edges = set()
for u, v, edge_data in G_cpp_digraph.edges:
edge = (min(u, v), max(u, v))
if edge not in seen_edges:
seen_edges.add(edge)
G_cpp.add_edge(u, v, **edge_data)
return G_cpp
class TestLouvainCommunities(unittest.TestCase):
def setUp(self):
self.G_simple = eg.Graph()
self.G_simple.add_edges_from([(0, 1), (1, 2), (3, 4)])
self.G_weighted = eg.Graph()
self.G_weighted.add_edge(0, 1, weight=5)
self.G_weighted.add_edge(1, 2, weight=3)
self.G_weighted.add_edge(3, 4, weight=2)
self.G_disconnected = eg.Graph()
self.G_disconnected.add_edges_from([(0, 1), (2, 3), (4, 5)])
self.G_single = eg.Graph()
self.G_single.add_node(42)
self.G_empty = eg.Graph()
def _get_cpp_module(self):
return _get_cpp_module()
def test_louvain_simple(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_louvain_communities(self.G_simple.cpp())
self.assertIsInstance(communities, list)
flat = {node for comm in communities for node in comm}
self.assertSetEqual(flat, set(self.G_simple.nodes))
def test_louvain_weighted(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_louvain_communities(self.G_weighted.cpp(), weight="weight")
self.assertIsInstance(communities, list)
flat = {node for comm in communities for node in comm}
self.assertSetEqual(flat, set(self.G_weighted.nodes))
def test_louvain_disconnected(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_louvain_communities(self.G_disconnected.cpp())
self.assertIsInstance(communities, list)
flat = {node for comm in communities for node in comm}
self.assertSetEqual(flat, set(self.G_disconnected.nodes))
def test_louvain_single_node(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_louvain_communities(self.G_single.cpp())
self.assertEqual(len(communities), 1)
self.assertIn(42, communities[0])
def test_louvain_empty_graph(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_louvain_communities(self.G_empty.cpp())
self.assertEqual(communities, [])
def test_louvain_resolution_parameter(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities1 = cpeg.cpp_louvain_communities(self.G_simple.cpp(), resolution=1.0)
communities2 = cpeg.cpp_louvain_communities(self.G_simple.cpp(), resolution=0.5)
self.assertIsInstance(communities1, list)
self.assertIsInstance(communities2, list)
flat1 = {node for comm in communities1 for node in comm}
flat2 = {node for comm in communities2 for node in comm}
self.assertEqual(flat1, flat2)
def test_python_cpp_consistency(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities_cpp = cpeg.cpp_louvain_communities(self.G_simple.cpp())
communities_py = eg.functions.community.louvain_communities(self.G_simple)
self.assertEqual(len(communities_cpp), len(communities_py))
flat_cpp = {node for comm in communities_cpp for node in comm}
flat_py = {node for comm in communities_py for node in comm}
self.assertEqual(flat_cpp, flat_py)
class TestLouvainCommunitiesSerial(unittest.TestCase):
def setUp(self):
self.G_simple = eg.Graph()
self.G_simple.add_edges_from([(0, 1), (1, 2), (3, 4)])
self.G_weighted = eg.Graph()
self.G_weighted.add_edge(0, 1, weight=5)
self.G_weighted.add_edge(1, 2, weight=3)
self.G_weighted.add_edge(3, 4, weight=2)
def _get_cpp_module(self):
return _get_cpp_module()
def test_louvain_serial_simple(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_louvain_communities_serial(self.G_simple.cpp())
self.assertIsInstance(communities, list)
flat = {node for comm in communities for node in comm}
self.assertSetEqual(flat, set(self.G_simple.nodes))
def test_louvain_serial_weighted(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_louvain_communities_serial(self.G_weighted.cpp(), weight="weight")
self.assertIsInstance(communities, list)
flat = {node for comm in communities for node in comm}
self.assertSetEqual(flat, set(self.G_weighted.nodes))
def test_parallel_vs_serial_consistency(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities_parallel = cpeg.cpp_louvain_communities(self.G_simple.cpp())
communities_serial = cpeg.cpp_louvain_communities_serial(self.G_simple.cpp())
flat_parallel = {frozenset(comm) for comm in communities_parallel}
flat_serial = {frozenset(comm) for comm in communities_serial}
self.assertEqual(flat_parallel, flat_serial)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,161 @@
import unittest
import easygraph as eg
def _get_cpp_module():
"""获取cpp_easygraph模块"""
try:
import cpp_easygraph
return cpp_easygraph
except ImportError:
return None
def _to_undirected_cpp(G_cpp_digraph):
"""将C++ DiGraph转换为C++ Graph无向图"""
import cpp_easygraph
G_cpp = cpp_easygraph.Graph()
G_cpp.graph.update(G_cpp_digraph.graph)
for node, node_attr in G_cpp_digraph.nodes.items():
G_cpp.add_node(node, **node_attr)
seen_edges = set()
for u, v, edge_data in G_cpp_digraph.edges:
edge = (min(u, v), max(u, v))
if edge not in seen_edges:
seen_edges.add(edge)
G_cpp.add_edge(u, v, **edge_data)
return G_cpp
def _communities_to_membership(G, communities):
if not isinstance(communities, list):
communities = list(communities)
N = G.number_of_nodes()
membership = [-1] * N
node_index = G.node_index
for comm_id, community in enumerate(communities):
for node in community:
if node in node_index:
node_id = node_index[node]
membership[node_id] = comm_id
return membership
class TestModularity(unittest.TestCase):
def setUp(self):
self.G = eg.Graph()
self.G.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 0)])
self.DG = eg.DiGraph()
self.DG.add_edges_from([(0, 1), (1, 2), (2, 0)])
self.G_weighted = eg.Graph()
self.G_weighted.add_edge(0, 1, weight=2)
self.G_weighted.add_edge(1, 2, weight=3)
self.G_weighted.add_edge(2, 0, weight=1)
self.G_selfloop = eg.Graph()
self.G_selfloop.add_edges_from([(0, 0), (1, 1), (0, 1)])
self.G_empty = eg.Graph()
self.config = type('Config', (), {
'is_directed': False
})()
def _get_cpp_module(self):
return _get_cpp_module()
def test_undirected_modularity(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = [{0, 1}, {2, 3}]
membership = _communities_to_membership(self.G, communities)
q = cpeg.cpp_modularity(self.G.cpp(), membership)
self.assertIsInstance(q, float)
self.assertGreaterEqual(q, -1.0)
self.assertLessEqual(q, 1.0)
def test_directed_modularity(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = [{0, 1, 2}]
membership = _communities_to_membership(self.DG, communities)
q = cpeg.cpp_modularity(self.DG.cpp(), membership)
self.assertIsInstance(q, float)
def test_weighted_graph(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = [{0, 1}, {2}]
membership = _communities_to_membership(self.G_weighted, communities)
q = cpeg.cpp_modularity(self.G_weighted.cpp(), membership, weight="weight")
self.assertIsInstance(q, float)
def test_self_loops(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = [{0, 1}]
membership = _communities_to_membership(self.G_selfloop, communities)
q = cpeg.cpp_modularity(self.G_selfloop.cpp(), membership)
self.assertIsInstance(q, float)
def test_single_community(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = [{0, 1, 2, 3}]
membership = _communities_to_membership(self.G, communities)
q = cpeg.cpp_modularity(self.G.cpp(), membership)
self.assertIsInstance(q, float)
def test_each_node_its_own_community(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = [{0}, {1}, {2}, {3}]
membership = _communities_to_membership(self.G, communities)
q = cpeg.cpp_modularity(self.G.cpp(), membership)
self.assertIsInstance(q, float)
def test_empty_community_list(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
q = cpeg.cpp_modularity(self.G.cpp(), [])
self.assertEqual(q, 0.0)
def test_python_cpp_consistency(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities_py = [{0, 1}, {2, 3}]
q_py = eg.functions.community.modularity(self.G, communities_py)
q_cpp = cpeg.cpp_modularity(self.G.cpp(), communities_py)
self.assertAlmostEqual(q_py, q_cpp, places=5)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,55 @@
import unittest
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from cpp_modularity_test import TestModularity
from cpp_greedy_modularity_test import TestGreedyModularity
from cpp_enumerate_subgraph_test import (
TestEnumerateSubgraph,
TestRandomEnumerateSubgraph
)
from cpp_louvain_test import TestLouvainCommunities, TestLouvainCommunitiesSerial
from cpp_LPA_test import TestLPA
from cpp_ego_graph_test import TestEgoGraph, TestEgoGraphCSR
from cpp_localsearch_test import TestLocalsearch
def create_test_suite():
suite = unittest.TestSuite()
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestModularity))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestGreedyModularity))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestEnumerateSubgraph))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestRandomEnumerateSubgraph))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestLouvainCommunities))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestLouvainCommunitiesSerial))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestLPA))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestEgoGraph))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestEgoGraphCSR))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestLocalsearch))
return suite
if __name__ == "__main__":
print("=" * 70)
print("Easy-Graph C++ Module Test Suite")
print("=" * 70)
print()
suite = create_test_suite()
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
print()
print("=" * 70)
print("Test Summary")
print("=" * 70)
print(f"Tests run: {result.testsRun}")
print(f"Successes: {result.testsRun - len(result.failures) - len(result.errors)}")
print(f"Failures: {len(result.failures)}")
print(f"Errors: {len(result.errors)}")
print(f"Skipped: {len(result.skipped)}")
print("=" * 70)
sys.exit(0 if result.wasSuccessful() else 1)
@@ -0,0 +1,5 @@
#pragma once
#include "biconnected.h"
#include "connected.h"
#include "strongly_connected.h"
@@ -0,0 +1,109 @@
#include "biconnected.h"
#include "../../classes/graph.h"
#include "../../common/utils.h"
node_t index_edge(std::vector<std::pair<node_t, node_t>>& edges, const std::pair<node_t, node_t>& target) {
for (int i = edges.size() - 1;i >= 0;i--) {
if ((edges[i].first == target.first) && (edges[i].second == target.second)) {
return i;
}
}
return -1;
}
py::object _biconnected_dfs_record_edges(py::object G, py::object need_components) {
py::list ret = py::list();
std::unordered_set<node_t> visited;
Graph& G_ = G.cast<Graph&>();
node_dict_factory &nodes_list = G_.node;
for (node_dict_factory::iterator iter = nodes_list.begin();iter != nodes_list.end();iter++) {
node_t start_id = iter->first;
if (visited.find(start_id) != visited.end()) {
continue;
}
std::unordered_map<node_t, int> discovery;
std::unordered_map<node_t, int> low;
node_t root_children = 0;
discovery.emplace(start_id, 0);
low.emplace(start_id, 0);
visited.emplace(start_id);
std::vector<std::pair<node_t, node_t>> edge_stack;
std::vector<stack_node> stack;
adj_attr_dict_factory& start_adj = G_.adj[start_id];
NeighborIterator neighbors_iter = NeighborIterator(start_adj);
stack_node initial_stack_node(start_id, start_id, neighbors_iter);
stack.emplace_back(initial_stack_node);
while (!stack.empty()) {
stack_node& node_info = stack.back();
node_t node_grandparent_id = node_info.grandparent;
node_t node_parent_id = node_info.parent;
try {
node_t node_child_id = node_info.neighbors_iter.next();
if (node_grandparent_id == node_child_id) {
continue;
}
if (visited.find(node_child_id) != visited.end()) {
if (discovery[node_child_id] <= discovery[node_parent_id]) {
low[node_parent_id] = std::min(low[node_parent_id], discovery[node_child_id]);
if (need_components.cast<bool>()) {
edge_stack.emplace_back(std::make_pair(node_parent_id, node_child_id));
}
}
}
else {
low[node_child_id] = discovery[node_child_id] = discovery.size();
visited.emplace(node_child_id);
adj_attr_dict_factory& node_child_adj = G_.adj[node_child_id];
NeighborIterator child_neighbors_iter = NeighborIterator(G_.adj[node_child_id]);
stack.emplace_back(node_parent_id, node_child_id, child_neighbors_iter);
if (need_components.cast<bool>()) {
edge_stack.emplace_back(std::make_pair(node_parent_id, node_child_id));
}
}
}
catch (int) {
stack.pop_back();
if (stack.size() > 1) {
if (low[node_parent_id] >= discovery[node_grandparent_id]) {
if (need_components.cast<bool>()) {
py::list tmp_ret = py::list();
std::pair<node_t, node_t> iter_edge = std::make_pair(-1, -1);
while ((iter_edge.first != node_grandparent_id || iter_edge.second != node_parent_id)) {
iter_edge = edge_stack.back();
edge_stack.pop_back();
tmp_ret.append(py::make_tuple(G_.id_to_node[py::cast(iter_edge.first)], G_.id_to_node[py::cast(iter_edge.second)]));
}
ret.append(tmp_ret);
}
else {
ret.append(G_.id_to_node[py::cast(node_grandparent_id)]);
}
}
low[node_grandparent_id] = std::min(low[node_grandparent_id], low[node_parent_id]);
}
else if (stack.size() > 0) {
root_children += 1;
if (need_components.cast<bool>()) {
std::pair<node_t, node_t> target = std::make_pair(node_grandparent_id, node_parent_id);
node_t ind = index_edge(edge_stack, target);
if (ind != -1) {
py::list tmp_ret = py::list();
for (node_t z = ind;z < edge_stack.size();z++) {
tmp_ret.append(py::make_tuple(G_.id_to_node[py::cast(edge_stack[z].first)], G_.id_to_node[py::cast(edge_stack[z].second)]));
}
ret.append(tmp_ret);
}
}
}
}
}
if (!need_components.cast<bool>()) {
if (root_children > 1) {
ret.append(G_.id_to_node(start_id));
}
}
}
return ret;
}
@@ -0,0 +1,34 @@
#pragma once
#include "../../common/common.h"
class NeighborIterator {
public:
NeighborIterator() {
}
NeighborIterator(adj_attr_dict_factory& neighbor_map) {
now = neighbor_map.begin();;
end = neighbor_map.end();
}
node_t next() {
if (now == end) {
throw -1;
}
else {
return (now++)->first;
}
}
private:
adj_attr_dict_factory::iterator now, end;
};
typedef struct stackNode {
node_t grandparent, parent;
NeighborIterator neighbors_iter;
stackNode(node_t grandparent, node_t parent, NeighborIterator neighbors_iter) {
this->grandparent = grandparent;
this->parent = parent;
this->neighbors_iter = neighbors_iter;
}
}stack_node;
py::object _biconnected_dfs_record_edges(py::object G, py::object need_components);
@@ -0,0 +1,205 @@
#include "connected.h"
#include <pybind11/stl.h>
#include "../../classes/graph.h"
#include "../../classes/directed_graph.h"
#include "../../common/utils.h"
#include "time.h"
#define gmin(x, y) x = x < y? x: y
py::object plain_bfs(py::object G, py::object source) {
Graph& G_ = G.cast<Graph&>();
node_t source_id = G_.node_to_id.attr("get")(source).cast<node_t>();
adj_dict_factory& G_adj = G_.adj;
std::unordered_set<node_t> seen;
std::unordered_set<node_t> nextlevel;
nextlevel.emplace(source_id);
py::list res = py::list();
while (nextlevel.size()) {
std::unordered_set<node_t> thislevel = nextlevel;
nextlevel = std::unordered_set<node_t>();
for (std::unordered_set<node_t>::iterator i = thislevel.begin(); i != thislevel.end(); i++) {
node_t v_id = *i;
if (seen.find(v_id) == seen.end()) {
seen.emplace(v_id);
adj_attr_dict_factory& v_adj = G_adj[v_id];
for (adj_attr_dict_factory::iterator j = v_adj.begin(); j != v_adj.end(); j++) {
node_t neighbor_id = j->first;
nextlevel.emplace(neighbor_id);
}
}
}
}
for (std::unordered_set<node_t>::iterator i = seen.begin(); i != seen.end(); i++) {
node_t res_id = *(i);
res.append(G_.id_to_node.attr("get")(res_id));
}
return res;
}
py::object connected_component_undirected(py::object G) {
Graph& G_ = G.cast<Graph&>();
bool is_directed = G.attr("is_directed")().cast<bool>();
if (is_directed == true) {
printf("connected_component_undirected is designed for undirected graphs.\n");
return py::dict();
}
int N = G_.node.size();
int M = G.attr("number_of_edges")().cast<int>();
std::vector<Edge_weighted> E_res(N+5);
for(int i=0; i < N+5; i++) {
E_res[i].toward = 0;
E_res[i].next = 0;
}
int edge_number_res = 0;
std::vector<int> parent(N+5, 0);
std::vector<int> rank_node(N+5, 0);
std::vector<int> color(N+5, 0);
std::vector<int> head_res(N+5, 0);
std::vector<bool> has_edge(N+5, false);
py::list nodes_list = py::list(G.attr("nodes"));
for (int i = 0;i < py::len(nodes_list);i++) {
node_t i_id = (G_.node_to_id[nodes_list[i]]).cast<node_t>();
parent[i_id] = i_id;
}
for (graph_edge& edge : G_._get_edges()) {
node_t u = edge.u, v = edge.v;
has_edge[u] = true; has_edge[v] = true;
_union_node(u, v, parent, rank_node);
}
int Tot = 0;
for(int i = 1; i < N + 1; ++i) {
if (!has_edge[i])
continue;
int fx = _getfa(i, parent);
if(fx == i){
color[++Tot] = fx;
}
}
for (int i = 1; i < N + 1; ++i) {
int fx = _getfa(i, parent);
_add_edge_res(fx, i, E_res, head_res, &edge_number_res);
}
py::dict ret = py::dict();
for (int i = 1; i <= Tot; ++i) {
py::list tmp = py::list();
for(int p = head_res[color[i]]; p; p = E_res[p].next){
tmp.append(py::cast(E_res[p].toward));
}
ret[py::cast(i)] = tmp;
}
return ret;
}
inline void _union_node(const int &u, const int &v, std::vector<int> &parent, std::vector<int> &rank_node) {
int x = _getfa(u, parent), y = _getfa(v, parent); //先找到两个根节点
if (rank_node[x] <= rank_node[y])
parent[x] = y;
else
parent[y] = x;
if (rank_node[x] == rank_node[y] && x != y)
rank_node[y]++; //如果深度相同且根节点不同,则新的根节点的深度+1
}
int _getfa(const int & x, std::vector<int> &parent) {
int r,k,t;
r=x;
while(parent[r]!=r)
r=parent[r];
k=r;
r=x;
while(parent[r]!=k) {
t=parent[r];
parent[r]=k;
r=t;
}
return k;
}
py::object connected_component_directed(py::object G) {
bool is_directed = G.attr("is_directed")().cast<bool>();
if (is_directed == false) {
printf("connected_component_directed is designed for directed graphs.\n");
return py::list();
}
DiGraph& G_ = G.cast<DiGraph&>();
int N = G_.node.size();
Graph_L G_l;
if(G_.linkgraph_dirty || G_.linkgraph_structure.max_deg == -1){
G_l = graph_to_linkgraph(G_, is_directed, "", true, false);
G_.linkgraph_dirty = false;
}
else{
G_l = G_.linkgraph_structure;
}
std::vector<LinkEdge>& E = G_l.edges;
std::vector<int> outDegree = G_l.degree;
std::vector<int> head = G_l.head;
int Time = 0, cnt = 0, Tot = 0, edge_number_res = 0;
std::vector<int> dfn(N+5, 0);
std::vector<int> low(N+5, 0);
std::vector<int> st(N+5, 0);
std::vector<int> color(N+5, 0);
std::vector<int> head_res(N+5, 0);
std::vector<bool> in_stack(N+5, false);
std::vector<bool> has_edge(N+5, false);
std::vector<Edge_weighted> E_res(N+5);
for(int i=0; i < N+5; i++) {
E_res[i].toward = 0;
E_res[i].next = 0;
}
for (graph_edge& edge : G_._get_edges()) {
node_t u = edge.u, v = edge.v;
has_edge[u] = true; has_edge[v] = true;
}
for (int i = 1; i < N + 1; ++i)
if (!dfn[i] && has_edge[i])
_tarjan(i, &Time, &cnt, &Tot, E, head, dfn, low, st, color, in_stack, E_res, head_res, &edge_number_res);
py::list ret = py::list();
for (int i = 1; i <= Tot; ++i) {
py::set tmp;
for(int p = head_res[i]; p; p = E_res[p].next)
tmp.add(G_.id_to_node.attr("get")(E_res[p].toward));
ret.append(tmp);
}
return ret;
}
void _add_edge_res(const int &u, const int &v, std::vector<Edge_weighted> &E_res, std::vector<int> &head_res, int *edge_number_res) {
E_res[++(*edge_number_res)].next = head_res[u];
E_res[*edge_number_res].toward = v;
head_res[u] = *edge_number_res;
}
void _tarjan(const int &u, int *Time, int *cnt, int *Tot, std::vector<LinkEdge>& E, std::vector<int>& head, std::vector<int> &dfn, std::vector<int> &low, std::vector<int> &st, std::vector<int> &color, std::vector<bool> &in_stack, std::vector<Edge_weighted> &E_res, std::vector<int> &head_res, int *edge_number_res) {
dfn[u] = low[u] = ++(*Time); st[++(*cnt)] = u; in_stack[u] = true;
for(int p = head[u]; p != -1; p = E[p].next){
int v = E[p].to;
if (!dfn[v]) _tarjan(v, Time, cnt, Tot, E, head, dfn, low, st, color, in_stack, E_res, head_res, edge_number_res), gmin(low[u], low[v]);
else if (in_stack[v]) gmin(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
for (++(*Tot); st[*cnt] != u; --(*cnt)) {
_add_edge_res(*Tot, st[*cnt], E_res, head_res, edge_number_res);
in_stack[st[*cnt]] = false, color[st[*cnt]] = *Tot;
}
_add_edge_res(*Tot, st[*cnt], E_res, head_res, edge_number_res);
in_stack[u] = false; color[u] = *Tot; --(*cnt);
}
}
@@ -0,0 +1,16 @@
#pragma once
#include "../../common/common.h"
#include "../../classes/linkgraph.h"
struct Edge_weighted{
int toward, next;
};
py::object plain_bfs(py::object G, py::object source);
py::object connected_component_undirected(py::object G);
inline void _union_node(const int &u, const int &v, std::vector<int>& parent, std::vector<int> &rank_node);
int _getfa(const int & x, std::vector<int> &parent);
py::object connected_component_directed(py::object G);
void _tarjan(const int &u, int *Time, int *cnt, int *Tot, std::vector<LinkEdge>& E, std::vector<int>& head, std::vector<int> &dfn, std::vector<int> &low, std::vector<int> &st, std::vector<int> &color, std::vector<bool> &in_stack, std::vector<Edge_weighted> &E_res, std::vector<int> &head_res, int *edge_number_res);
void _add_edge_res(const int &u, const int &v, std::vector<Edge_weighted> &E_res, std::vector<int> &head_res, int *edge_number_res);
@@ -0,0 +1,90 @@
#include "strongly_connected.h"
#include "connected.h"
#include "../../classes/directed_graph.h"
#define MAX_NODES_NUM4RECURSION_METHOD 100000
py::object strongly_connected_components(py::object G) {
bool is_directed = G.attr("is_directed")().cast<bool>();
if (is_directed == false) {
printf("connected_component_directed is designed for directed graphs.\n");
return py::list();
}
int N = G.attr("number_of_nodes")().cast<int>();
if(N < MAX_NODES_NUM4RECURSION_METHOD){
return connected_component_directed(G);
}
return strongly_connected_components_iteration_impl(G);
}
py::object strongly_connected_components_iteration_impl(py::object G) {
py::list res = py::list();
DiGraph& G_ = py::cast<DiGraph&>(G);
adj_dict_factory& adj = G_.adj;
std::unordered_map<node_t, node_t> preorder;
std::unordered_map<node_t, node_t> lowlink;
std::set<node_t> scc_found;
std::vector<node_t> scc_queue;
int i = 0;
node_dict_factory& nodes_list = G_.node;
for (node_dict_factory::iterator source = nodes_list.begin(); source != nodes_list.end(); source++) {
node_t source_id = source->first;
if (scc_found.find(source_id) == scc_found.end()) {
std::vector<node_t> que;
que.emplace_back(source_id);
while (!que.empty()) {
node_t v_id = que.back();
if (preorder.find(v_id) == preorder.end()) {
i += 1;
preorder[v_id] = i;
}
bool done = true;
adj_attr_dict_factory& v_neighbors = adj[v_id];
for (adj_attr_dict_factory::iterator w = v_neighbors.begin(); w != v_neighbors.end(); w++) {
node_t w_id = w->first;
if (preorder.find(w_id) == preorder.end()) {
que.emplace_back(w_id);
done = false;
break;
}
}
if (done) {
lowlink[v_id] = preorder[v_id];
for (adj_attr_dict_factory::iterator w = v_neighbors.begin(); w != v_neighbors.end(); w++) {
node_t w_id = w->first;
if (scc_found.find(w_id) == scc_found.end()) {
if (preorder[w_id] > preorder[v_id]) {
lowlink[v_id] = std::min(lowlink[v_id], lowlink[w_id]);
} else {
lowlink[v_id] = std::min(lowlink[v_id], preorder[w_id]);
}
}
}
que.pop_back();
if (lowlink[v_id] == preorder[v_id]) {
std::unordered_set<node_t> scc;
scc.emplace(v_id);
while (!scc_queue.empty() && (preorder[scc_queue.back()] > preorder[v_id])) {
node_t k = scc_queue.back();
scc_queue.pop_back();
scc.emplace(k);
}
py::set tmp_res;
for (std::unordered_set<node_t>::iterator z = scc.begin(); z != scc.end(); z++) {
scc_found.emplace(*z);
tmp_res.add(G_.id_to_node.attr("get")(*z));
}
res.append(tmp_res);
} else {
scc_queue.emplace_back(v_id);
}
}
}
}
}
return res;
}
@@ -0,0 +1,6 @@
#pragma once
#include "../../common/common.h"
py::object strongly_connected_components(py::object G);
py::object strongly_connected_components_iteration_impl(py::object G);
+3
View File
@@ -0,0 +1,3 @@
#pragma once
#include "k_cores.h"

Some files were not shown because too many files have changed in this diff Show More