Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e71a193
Add CLAUDE.md and apply minor cleanups
claude Apr 19, 2026
e963dfc
Migrate linting and formatting to ruff
claude Apr 19, 2026
34f2a27
Migrate project metadata from setup.py to pyproject.toml (PEP 621)
claude Apr 19, 2026
e500a21
Add type hints to public API
claude Apr 19, 2026
04c73ee
Loosen flaky RNG-dependent equality asserts in test_pygenstability
claude Apr 19, 2026
b0f7ac2
Ignore results.pkl run artifact
claude Apr 19, 2026
75c2eda
Assorted cleanups: numpy warnings, URLs, PDF, spectral inverse
claude Apr 19, 2026
55a6fef
Refactor run() into smaller helpers
claude Apr 19, 2026
d500e31
Finish remaining backlog: upper bounds, warnings, lint CI
claude Apr 19, 2026
a43390c
Speed up _assign_increasing_ids; explicit __all__; py.typed; pre-commit
claude Apr 19, 2026
0361d7a
Make Louvain RNG deterministic end-to-end
Apr 28, 2026
72abccb
Fix E501 line-length on _optimise signature
Apr 28, 2026
55b6c09
Bump submodule to portable Fisher-Yates, regenerate fixtures
Apr 28, 2026
347d284
Stop plots from popping during tests
Apr 28, 2026
835d0a8
Re-enable strict dataclustering fixtures and add determinism regressi…
Apr 28, 2026
f012b7c
Point submodule at upstream now that the RNG fix has merged
Apr 28, 2026
67a282e
Address PR review: trim Claude-style comments, type graph, rename _fi…
May 28, 2026
a2431fa
Address review nits: typo, n_workers param test, gitignore, regen com…
May 28, 2026
cec2162
Add user-controllable seed to run() and DataClustering, fix typos
May 28, 2026
ef49c47
Wire test fixtures to the new seed parameter
May 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/run-tox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ on:
- master

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- run: pip install ruff
- run: ruff check src/pygenstability
- run: ruff format --check src/pygenstability

build:

runs-on: ubuntu-latest
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ test/model/

test/data/

# pygenstability run artifacts
results.pkl

# Auto-generated Sphinx PDF (regenerated on release via `tox -e docs`)
pygenstability_doc.pdf

.DS_Store

# vscode
Expand Down
17 changes: 17 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.8
hooks:
- id: ruff
args: [--fix]
- id: ruff-format

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-added-large-files
args: [--maxkb=1024]
56 changes: 0 additions & 56 deletions .pylintrc

This file was deleted.

60 changes: 60 additions & 0 deletions CLAUDE.md
Comment thread
juni-schindler marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# CLAUDE.md

Orientation for Claude Code sessions working in this repo.

## Project

PyGenStability is a Python package for multiscale community detection via
**generalized Markov Stability**. The core loop scans a range of Markov times,
runs many generalized-Louvain (or Leiden) optimizations per scale, and
post-processes the partitions (robustness, NVI, optimal scale selection).

A C++ Louvain implementation is shipped as a pybind11 extension.

## Layout

- `src/pygenstability/` — package source
- `tests/` — pytest suite (100% coverage enforced, see `tox.ini`)
- `examples/` — notebooks and scripts, including `real_examples/` (powergrid, protein)
- `docs/` — Sphinx + nbsphinx
- `extra/lemon/`, `generalizedLouvain/` — C++ headers / sources used by the extension

## Key modules (`src/pygenstability/`)

- `pygenstability.py` — `run()` orchestrates the scale scan, parallelism, and post-processing
- `constructors.py` — quality-matrix / null-model builders (linearized, continuous, directed, signed, …)
- `optimal_scales.py` — robustness-based optimal scale selection
- `plotting.py` — matplotlib plots; optional plotly/networkx branches
- `data_clustering.py` — end-to-end data-to-graph clustering workflow
- `app.py` — Click CLI; console script `pygenstability=pygenstability.app:cli`
- `io.py` — pickle save/load helpers
- `contrib/sankey.py` — interactive Sankey diagram (optional plotly)

## Build / test

```bash
pip install -e . # builds pybind11 C++ extension (-std=c++11)
pytest # needs the extension built first
tox # full matrix
tox -e py310 | py311 | py312 # single interpreter
tox -e lint # pylint + pycodestyle + pydocstyle + isort + black --check
tox -e format # black + isort (writes)
tox -e docs # Sphinx build
```

Supported Python: **3.10 / 3.11 / 3.12** (see `tox.ini`).

## Gotchas

- The C++ extension is declared `optional=True` in `setup.py`. If it fails to
compile, `import pygenstability.generalized_louvain` silently fails and
Louvain is unavailable; Leiden fallback only kicks in if `leidenalg` is
installed (see `_check_method` in `pygenstability.py`).
- BLAS/OpenMP thread counts are pinned to 1 inside worker processes
(`_limit_numpy` in `constructors.py`) to avoid multiprocessing contention.
Don't remove this without understanding the parallelism model.
- Optional extras: `[plotly]`, `[leiden]` (igraph + leidenalg), `[networkx]`,
`[all]`.
- Tests write expected-output YAMLs under `tests/data/`. When changing
numerics, regenerate fixtures deliberately rather than blindly accepting
diffs.
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ include extra/lemon/concepts/*
include generalizedLouvain/*
include generalizedLouvain/CPP/*
include generalizedLouvain/CPP/cliques/*
include src/pygenstability/py.typed
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ We further provide specific analysis tools to process and analyse the results fr

## Documentation

A documentation of all features of the *PyGenStability* package is available here: https://barahona-research-group.github.io/PyGenStability/, or in pdf [here](pygenstability_doc.pdf).
A documentation of all features of the *PyGenStability* package is available here: https://barahona-research-group.github.io/PyGenStability/.

## Installation

Expand Down Expand Up @@ -54,7 +54,7 @@ pip install pygenstability[all]

You can also install the source code of this package from GitHub directly by first cloning this repo with:
```bash
git clone --recurse-submodules https://github.com/ImperialCollegeLondon/PyGenStability.git
git clone --recurse-submodules https://github.com/barahona-research-group/PyGenStability.git
```

(if the `--recurse-submodules` has not been used, just do `git submodule update --init --recursive` to fetch the submodule with M. Schaub's code).
Expand Down
2 changes: 1 addition & 1 deletion docs/index_readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pip install pygenstability[all]

You can also install the source code of this package from GitHub directly by first cloning this repo with:
```bash
git clone --recurse-submodules https://github.com/ImperialCollegeLondon/PyGenStability.git
git clone --recurse-submodules https://github.com/barahona-research-group/PyGenStability.git
```

(if the `--recurse-submodules` has not been used, just do `git submodule update --init --recursive` to fetch the submodule with M. Schaub's code).
Expand Down
Binary file removed pygenstability_doc.pdf
Binary file not shown.
93 changes: 93 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,96 @@
[build-system]
requires = ["setuptools", "numpy", "cython", "pybind11>=2.6.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "PyGenStability"
version = "0.2.7"
description = "Python binding of generalised Louvain with Markov Stability"
readme = "README.md"
requires-python = ">=3.10"
authors = [
{name = "Alexis Arnaudon", email = "alexis.arnaudon@epfl.ch"},
{name = "Juni Schindler", email = "juni.schindler19@imperial.ac.uk"},
]
dependencies = [
"numpy>=1.18.1,<3",
"scipy>=1.4.1,<2",
"matplotlib>=3.1.3",
"scikit-learn",
"cmake>=3.16.3",
"click>=7.0",
"tqdm>=4.45.0",
"pybind11>=2.10.0",
"pandas>=1.0.0",
"threadpoolctl",
]

[project.urls]
Homepage = "https://github.com/barahona-research-group/PyGenStability"

[project.optional-dependencies]
plotly = ["plotly>=3.6.0"]
leiden = ["igraph", "leidenalg"]
networkx = ["networkx>=3.0"]
all = [
"plotly>=3.6.0",
"igraph",
"leidenalg",
"networkx>=3.0",
"pyyaml",
"dictdiffer",
"pytest",
"pytest-cov",
"pytest-html",
"diff-pdf-visually",
"ipython!=8.7.0",
]

[project.scripts]
pygenstability = "pygenstability.app:cli"

[tool.setuptools]
zip-safe = false
include-package-data = true

[tool.setuptools.packages.find]
where = ["src"]
namespaces = true

[tool.setuptools.package-dir]
"" = "src"

[tool.ruff]
line-length = 100
target-version = "py310"

[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"D", # pydocstyle
"PLE", # pylint errors
]
ignore = [
"E731", # do not assign a lambda expression
"E741", # ambiguous variable name
"E203", # whitespace before ':' (black-compatible)
"W605", # invalid escape sequence
"D102", # missing docstring in public method
"D105", # missing docstring in magic method
"D107", # missing docstring in __init__
"D416", # section name should end with a colon (numpy-style headers used)
]

[tool.ruff.lint.pydocstyle]
convention = "google"

[tool.ruff.lint.isort]
force-single-line = true

[tool.ruff.lint.per-file-ignores]
"tests/**" = ["D"]
"docs/**" = ["D"]
"examples/**" = ["D", "E", "F"]
76 changes: 15 additions & 61 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,66 +1,20 @@
"""Setup."""
"""Setup for the pybind11 C++ extension.

from pybind11.setup_helpers import Pybind11Extension
from setuptools import find_namespace_packages, setup

__version__ = "0.2.7"
All project metadata lives in pyproject.toml (PEP 621). This file
only declares the optional C++ extension.
"""

ext_modules = [
Pybind11Extension(
"pygenstability.generalized_louvain",
["src/pygenstability/generalized_louvain/generalized_louvain.cpp"],
include_dirs=["extra", "generalizedLouvain"],
extra_compile_args=["-std=c++11"],
optional=True,
),
]
plotly_require = ["plotly>=3.6.0"]

test_require = [
"pyyaml",
"dictdiffer",
"pytest",
"pytest-cov",
"pytest-html",
"diff-pdf-visually",
"ipython!=8.7.0", # see https://github.com/spatialaudio/nbsphinx/issues/687
]
install_requires = [
"numpy>=1.18.1",
"scipy>=1.4.1",
"matplotlib>=3.1.3",
"scikit-learn",
"cmake>=3.16.3",
"click>=7.0",
"tqdm>=4.45.0",
"pybind11>=2.10.0",
"pandas>=1.0.0",
"threadpoolctl",
]
leiden_install = ["igraph", "leidenalg"]
networkx_install = ["networkx>=3.0"]
from pybind11.setup_helpers import Pybind11Extension
from setuptools import setup

setup(
name="PyGenStability",
version=__version__,
author="Alexis Arnaudon, Juni Schindler",
author_email="alexis.arnaudon@epfl.ch, juni.schindler19@imperial.ac.uk",
url="https://github.com/ImperialCollegeLondon/PyGenStability",
description="Python binding of generalised Louvain with Markov Stability",
long_description=open("README.md", "r").read(),
long_description_content_type="text/markdown",
ext_modules=ext_modules,
setup_requires=["pybind11>=2.10.0"],
install_requires=install_requires,
zip_safe=False,
extras_require={
"plotly": plotly_require,
"leiden": leiden_install,
"networkx": networkx_install,
"all": plotly_require + test_require + leiden_install + networkx_install,
},
entry_points={"console_scripts": ["pygenstability=pygenstability.app:cli"]},
packages=find_namespace_packages("src"),
include_package_data=True,
package_dir={"": "src"},
ext_modules=[
Pybind11Extension(
"pygenstability.generalized_louvain",
["src/pygenstability/generalized_louvain/generalized_louvain.cpp"],
include_dirs=["extra", "generalizedLouvain"],
extra_compile_args=["-std=c++11"],
optional=True,
),
],
)
Loading
Loading