diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index fdfd62b39..0f7f5a185 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,44 +5,51 @@ on: branches: [main, github-actions-test] pull_request: branches: [main] + permissions: contents: write jobs: build: - runs-on: ${{matrix.os}} + runs-on: ubuntu-latest strategy: matrix: - os: [ubuntu-latest] - python-version: [3.11.3] - torch-version: [2.0.1] - include: - - torch-version: 2.0.1 + python-version: ["3.11"] steps: - - uses: actions/checkout@v3 - - name: Build using Python ${{matrix.python-version}} - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + + # 1. Setup uv + - name: Install uv + uses: astral-sh/setup-uv@v5 with: - python-version: ${{matrix.python-version}} - cache: "pip" - cache-dependency-path: "pyproject.toml" - - name: Install dependencies [pip] - run: | - python -m pip install --upgrade pip - pip install pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - source env_setup.sh - - name: Install Pandoc [apt-get] + python-version: ${{ matrix.python-version }} + enable-cache: true + cache-dependency-glob: "uv.lock" + + # 2. Run your centralized installation script + - name: Install dependencies (via script) run: | - sudo apt-get -y install pandoc + # This installs '.[all]' which includes doc dependencies + source uv_env_setup.sh + + # 3. Install System Dependencies + - name: Install Pandoc + run: sudo apt-get -y install pandoc + + # 4. Generate Docs - name: Generate API Documentation run: | + source .venv/bin/activate cd docs bash generate_api_docs.sh + - name: Generate Docs [Sphinx] run: | - sphinx-build -b html -D version=latest -D release=latest docs docs/_build + source .venv/bin/activate + sphinx-build -b html -D version=latest -D release=latest docs docs/_build + + # 5. Deploy - name: Deploy Docs uses: JamesIves/github-pages-deploy-action@v4 if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'geometric-intelligence/TopoBench' }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5576b269d..69b8efd86 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,11 +6,21 @@ on: pull_request: branches: [ main ] +permissions: + contents: read + jobs: ruff: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: chartboost/ruff-action@v1 + - uses: actions/checkout@v4 + + # We use the official action from Astral (creators of uv & ruff) + - name: Run Ruff + uses: astral-sh/ruff-action@v3 with: - src: './topobench' \ No newline at end of file + src: "./topobench" + # This ensures the ruff version matches what you have in pyproject.toml + version-file: "pyproject.toml" + # Optional: fails the build if the code changes (e.g. formatting) + changed-files: "true" \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 20ebf7d8f..471a159f8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,3 @@ -# This workflow will install Python dependencies, run tests and lint with a single version of Python -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python - name: Testing on: @@ -14,33 +11,33 @@ permissions: jobs: build: - runs-on: ${{matrix.os}} + runs-on: ubuntu-latest strategy: matrix: - os: [ubuntu-latest] - python-version: [3.11.3] + python-version: ["3.11"] steps: - uses: actions/checkout@v4 - - name: Set up Python ${{matrix.python-version}} - uses: actions/setup-python@v5 - with: - python-version: ${{matrix.python-version}} - - uses: actions/cache@v4 + # 1. Setup uv + - name: Install uv + uses: astral-sh/setup-uv@v5 with: - path: ~/.cache/pip - key: ${{matrix.os}}-${{matrix.python-version}}-${{ hashFiles('pyproject.toml') }} + python-version: ${{ matrix.python-version }} + enable-cache: true + cache-dependency-glob: "uv.lock" - - name: Install dependencies + # 2. Run your centralized installation script + - name: Install dependencies (via script) run: | - python -m pip install --upgrade pip - pip install pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - source env_setup.sh + # This script handles the .venv creation and PyTorch/CUDA logic + source uv_env_setup.sh + # 3. Run Tests - name: Test with pytest run: | + # We must activate the venv created by your script + source .venv/bin/activate pytest --cov --cov-report=xml:coverage.xml test/ - name: Upload coverage reports to Codecov @@ -48,4 +45,4 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} file: coverage.xml - fail_ci_if_error: false + fail_ci_if_error: false \ No newline at end of file diff --git a/.gitignore b/.gitignore index e618d5221..3c3826cb2 100755 --- a/.gitignore +++ b/.gitignore @@ -194,3 +194,4 @@ wandb/ # test temporary .test_tmp/ +uv.lock \ No newline at end of file diff --git a/README.md b/README.md index 17dc5f29f..4d32d0ace 100755 --- a/README.md +++ b/README.md @@ -52,44 +52,58 @@ The main pipeline trains and evaluates a wide range of state-of-the-art TNNs and ## :jigsaw: Get Started -### Create Environment +### ๐Ÿš€ Quick Install (Recommended) -First, ensure `conda` is installed: -```bash -conda --version -``` -If not, we recommend intalling Miniconda [following the official command line instructions](https://www.anaconda.com/docs/getting-started/miniconda/install). +TopoBench now uses [**uv**](https://docs.astral.sh/uv/), an extremely fast Python package manager and resolver. This allows for nearly instantaneous environment setup and reproducible builds. -Then, clone and navigate to the `TopoBench` repository -```bash -git clone git@github.com:geometric-intelligence/topobench.git -cd TopoBench -``` +1. [**Install uv**](https://docs.astral.sh/uv/getting-started/installation/#standalone-installer) -Next, set up and activate a conda environment `tb` with Python 3.11.3: -```bash -conda create -n tb python=3.11.3 -conda activate tb -``` +2. **Clone and Navigate**: + ```bash + git clone git@github.com:geometric-intelligence/topobench.git + cd TopoBench + ``` -If working with GPUs, check the CUDA version of your machine: -```bash -which nvcc && nvcc --version -``` -and ensure that it matches the CUDA version specified in the `env_setup.sh` file (`CUDA=cpu` by default for a broader compatibility). If it does not match, update `env_setup.sh` accordingly by changing both the `CUDA` and `TORCH` environment variables to compatible values as specified on [this website](https://github.com/pyg-team/pyg-lib). +3. **Initialize Environment**: + Use our centralized setup script to handle Python 3.11 virtualization and specialized hardware (CUDA) mapping. + ```bash + # Usage: source uv_env_setup.sh [cpu|cu118|cu121] + source uv_env_setup.sh cpu + ``` + *This script performs the following:* + * Creates a `.venv` using Python 3.11. + * Dynamically configures `pyproject.toml` to point to the correct **PyTorch** and **PyG** (PyTorch Geometric) wheels for your platform. + * Generates a precise `uv.lock` file and syncs all dependencies. + +--- + +### ๐Ÿ› ๏ธ Manual Environment Setup + +If you prefer to manage the environment manually or are integrating into an existing workflow: -Next, set up the environment with the following command. ```bash -source env_setup.sh +# Create a virtual environment with strict versioning +uv venv --python 3.11 +source .venv/bin/activate + +# Sync dependencies including all extras (dev, test, and doc) +uv sync --all-extras ``` -This command installs the `TopoBench` library and its dependencies. -### Run Training Pipeline +๐Ÿš„ Run Training Pipeline +Once the environment is active, you can launch the TopoBench pipeline: +```bash +# Using the activated virtual environment +python -m topobench -Once the setup is completed, train and evaluate a neural network by running the following command: +# Or execute directly via uv without manual activation +uv run python -m topobench +``` +โœ… Verify Installation +You can verify that the correct versions of Torch and CUDA are detected by running: ```bash -python -m topobench +python -c "import torch; print(f'Torch: {torch.__version__} | CUDA: {torch.version.cuda}')" ``` --- @@ -390,14 +404,15 @@ Specially useful in pre-processing steps, these are the general data manipulatio To learn more about `TopoBench`, we invite you to read the paper: ``` -@article{telyatnikov2024topobench, - title={TopoBench: A Framework for Benchmarking Topological Deep Learning}, - author={Lev Telyatnikov and Guillermo Bernardez and Marco Montagna and Pavlo Vasylenko and Ghada Zamzmi and Mustafa Hajij and Michael T Schaub and Nina Miolane and Simone Scardapane and Theodore Papamarkou}, - year={2024}, - eprint={2406.06642}, - archivePrefix={arXiv}, - primaryClass={cs.LG}, - url={https://arxiv.org/abs/2406.06642}, +@article{ +telyatnikov2025topobench, +title={TopoBench: A Framework for Benchmarking Topological Deep Learning}, +author={Lev Telyatnikov and Guillermo Bernardez and Marco Montagna and Mustafa Hajij and Martin Carrasco and Pavlo Vasylenko and Mathilde Papillon and Ghada Zamzmi and Michael T Schaub and Jonas Verhellen and Pavel Snopov and Bertran Miquel-Oliver and Manel Gil-Sorribes and Alexis Molina and VICTOR GUALLAR and Theodore Long and Julian Suk and Patryk Rygiel and Alexander V Nikitin and Giordan Escalona and Michael Banf and Dominik Filipiak and Liliya Imasheva and Max Schattauer and Alvaro L. Martinez and Halley Fritze and Marissa Masden and Valentina S{\'a}nchez and Manuel Lecha and Andrea Cavallo and Claudio Battiloro and Matthew Piekenbrock and Mauricio Tec and George Dasoulas and Nina Miolane and Simone Scardapane and Theodore Papamarkou}, +journal={Journal of Data-centric Machine Learning Research}, +issn={XXXX-XXXX}, +year={2025}, +url={https://openreview.net/forum?id=07sTzyEVtY}, +note={} } ``` If you find `TopoBench` useful, we would appreciate if you cite us! diff --git a/env_setup.sh b/env_setup.sh deleted file mode 100755 index 8dfd41344..000000000 --- a/env_setup.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -l -pip install --upgrade pip -pip install -e '.[all]' - -# Note that not all combinations of torch and CUDA are available -# See https://github.com/pyg-team/pyg-lib to check the configuration that works for you -TORCH="2.3.0" # available options: 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, ... -CUDA="cpu" # if available, select the CUDA version suitable for your system - # available options: cpu, cu102, cu113, cu116, cu117, cu118, cu121, ... -pip install torch==${TORCH} --extra-index-url https://download.pytorch.org/whl/${CUDA} -pip install torch-scatter torch-sparse torch-cluster -f https://data.pyg.org/whl/torch-${TORCH}+${CUDA}.html - -#pytest - -pre-commit install diff --git a/pyproject.toml b/pyproject.toml index 392c45ce9..aee92b477 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,11 +20,13 @@ classifiers = [ "Topic :: Scientific/Engineering :: Artificial Intelligence", "Natural Language :: English", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11" ] -requires-python = ">= 3.10" +# STRICTLY PYTHON 3.11 +requires-python = ">=3.11, <3.12" + dependencies=[ + "torch==2.3.0", "tqdm", "charset-normalizer", "numpy", @@ -54,6 +56,9 @@ dependencies=[ "topomodelx @ git+https://github.com/pyt-team/TopoModelX.git", "toponetx @ git+https://github.com/pyt-team/TopoNetX.git@c378925", "lightning==2.4.0", + "torch-scatter", + "torch-sparse", + "torch-cluster", ] [project.optional-dependencies] @@ -62,7 +67,8 @@ doc = [ "nbsphinx", "nbsphinx_link", "numpydoc", - "sphinx", + "sphinx>=7.0,<9.0", # Sphinx 9 is incompatible with the required docutils + "docutils<0.21", # Required for nbsphinx-link "sphinx_gallery", "pydata-sphinx-theme", "myst_parser" @@ -87,48 +93,58 @@ all = ["TopoBench[dev, doc]"] homepage="https://geometric-intelligence.github.io/topobench/index.html" repository="https://github.com/geometric-intelligence/TopoBench" +# ============================================================================== +# UV CONFIGURATION +# ============================================================================== + +[[tool.uv.index]] +name = "pytorch-cpu" +url = "https://download.pytorch.org/whl/cpu" +explicit = true + +[[tool.uv.index]] +name = "pytorch-cu118" +url = "https://download.pytorch.org/whl/cu118" +explicit = true + +[[tool.uv.index]] +name = "pytorch-cu121" +url = "https://download.pytorch.org/whl/cu121" +explicit = true + +# Default find-links (will be overwritten by bash script) +[tool.uv] +find-links = ["https://data.pyg.org/whl/torch-2.3.0+cpu.html"] + +[tool.uv.sources] +torch = [ + { index = "pytorch-cpu", marker = "sys_platform == 'darwin' or sys_platform == 'win32'" }, + { index = "pytorch-cpu", marker = "sys_platform == 'linux'" }, +] + +# ============================================================================== +# TOOL CONFIGS +# ============================================================================== [tool.black] -line-length = 79 # PEP 8 standard for maximum line length -target-version = ['py310'] +line-length = 79 +target-version = ['py311'] [tool.docformatter] wrap-summaries = 79 wrap-descriptions = 79 [tool.ruff] -target-version = "py310" -#extend-include = ["*.ipynb"] +target-version = "py311" extend-exclude = ["test", "tutorials", "notebooks"] -line-length = 79 # PEP 8 standard for maximum line length +line-length = 79 [tool.ruff.format] docstring-code-format = false [tool.ruff.lint] -select = [ - "F", # pyflakes errors - "E", # code style - "W", # warnings - "I", # import order - "UP", # pyupgrade rules - "B", # bugbear rules - "PIE", # pie rules - "Q", # quote rules - "RET", # return rules - "SIM", # code simplifications - "NPY", # numpy rules - "PERF", # performance rules -] +select = ["F", "E", "W", "I", "UP", "B", "PIE", "Q", "RET", "SIM", "NPY", "PERF"] fixable = ["ALL"] -ignore = [ - "E501", # line too long - "RET504", # Unnecessary assignment before return - "RET505", # Unnecessary `elif` after `return` statement - "NPY002", # Replace legacy `np.random.seed` call with `np.random.Generator` - "UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)` -- not compatible with python 3.9 (even with __future__ import) - "W293", # Does not allow to have empty lines in multiline comments - "PERF203", # [TODO: fix all such issues] `try`-`except` within a loop incurs performance overhead -] +ignore = ["E501", "RET504", "RET505", "NPY002", "UP038", "W293", "PERF203"] [tool.ruff.lint.pydocstyle] convention = "numpy" @@ -141,10 +157,7 @@ convention = "numpy" version = {attr = "topobench.__version__"} [tool.setuptools.packages.find] -include = [ - "topobench", - "topobench.*" -] +include = ["topobench", "topobench.*"] [tool.mypy] warn_redundant_casts = true @@ -154,27 +167,13 @@ disable_error_code = ["import-untyped"] plugins = "numpy.typing.mypy_plugin" [[tool.mypy.overrides]] -module = [ - "torch_cluster.*","networkx.*","scipy.spatial","scipy.sparse","toponetx.classes.simplicial_complex" -] +module = ["torch_cluster.*","networkx.*","scipy.spatial","scipy.sparse","toponetx.classes.simplicial_complex"] ignore_missing_imports = true [tool.pytest.ini_options] addopts = "--capture=no" -pythonpath = [ - "." -] +pythonpath = ["."] [tool.numpydoc_validation] -checks = [ - "all", - "GL01", - "ES01", - "EX01", - "SA01" -] -exclude = [ - '\.undocumented_method$', - '\.__init__$', - '\.__repr__$', -] +checks = ["all", "GL01", "ES01", "EX01", "SA01"] +exclude = ['\.undocumented_method$', '\.__init__$', '\.__repr__$'] \ No newline at end of file diff --git a/topobench/optimizer/optimizer.py b/topobench/optimizer/optimizer.py index 9ca8efdcd..05d754824 100644 --- a/topobench/optimizer/optimizer.py +++ b/topobench/optimizer/optimizer.py @@ -29,6 +29,10 @@ def __init__(self, optimizer_id, parameters, scheduler=None) -> None: self.optimizer = functools.partial( TORCH_OPTIMIZERS[optimizer_id], **parameters ) + + # CHANGED: Store the scheduler config so we can access keys like 'monitor' later + self.scheduler_config = scheduler + if scheduler is not None: scheduler_id = scheduler.get("scheduler_id") scheduler_params = scheduler.get("scheduler_params") @@ -63,13 +67,17 @@ def configure_optimizer(self, model_parameters) -> dict[str:Any]: optimizer = self.optimizer(params=model_parameters) if self.scheduler is not None: scheduler = self.scheduler(optimizer=optimizer) + + # CHANGED: Use .get() to read from the config, falling back to defaults if missing return { "optimizer": optimizer, "lr_scheduler": { "scheduler": scheduler, - "monitor": "val/loss", - "interval": "epoch", - "frequency": 1, + "monitor": self.scheduler_config.get( + "monitor", "val/loss" + ), + "interval": self.scheduler_config.get("interval", "epoch"), + "frequency": self.scheduler_config.get("frequency", 1), }, } return {"optimizer": optimizer} diff --git a/topobench/run.py b/topobench/run.py index 142ea2cc4..eec0d61a0 100755 --- a/topobench/run.py +++ b/topobench/run.py @@ -301,7 +301,9 @@ def rerun_best_model_checkpoint( log.info("Re-testing best model checkpoint on validation set!") val_loader = datamodule.val_dataloader() - results = checkpoint_trainer.test( + # TODO: Fix the issue with the on_validation_epoch_start hook as it is strictly attached to the training procedure. + checkpoint_model.on_validation_epoch_start = lambda: None + results = checkpoint_trainer.validate( model=checkpoint_model, dataloaders=val_loader ) if results: diff --git a/uv_env_setup.sh b/uv_env_setup.sh new file mode 100644 index 000000000..4c15c571a --- /dev/null +++ b/uv_env_setup.sh @@ -0,0 +1,79 @@ +#!/bin/bash -l + +# ============================================================================== +# ๐Ÿ› ๏ธ TopoBench Environment Setup Script (Py3.11 + Dynamic CUDA) +# ============================================================================== +# usage: bash uv_env_setup.sh [cpu|cu118|cu121] +# ============================================================================== + +PLATFORM="${1:-cpu}" + +# Visual Header +echo "" +echo "=======================================================" +echo "๐Ÿš€ Initializing TopoBench Environment ($PLATFORM)" +echo "=======================================================" + +# ------------------------------------------------------------------------------ +# Configuration +# ------------------------------------------------------------------------------ +TORCH_VER="2.3.0" + +if [ "$PLATFORM" == "cpu" ]; then + TARGET_INDEX="pytorch-cpu" + PYG_URL="https://data.pyg.org/whl/torch-${TORCH_VER}+cpu.html" +elif [ "$PLATFORM" == "cu118" ]; then + TARGET_INDEX="pytorch-cu118" + PYG_URL="https://data.pyg.org/whl/torch-${TORCH_VER}+cu118.html" +elif [ "$PLATFORM" == "cu121" ]; then + TARGET_INDEX="pytorch-cu121" + PYG_URL="https://data.pyg.org/whl/torch-${TORCH_VER}+cu121.html" +else + echo "โŒ Error: Invalid platform '$PLATFORM'. Use: cpu, cu118, or cu121." + exit 1 +fi + +echo "โš™๏ธ Updating pyproject.toml..." + +# 1. Update the 'find-links' URL for PyG extensions +if [[ "$OSTYPE" == "darwin"* ]]; then + # MacOS sed + sed -i '' "s|find-links = \[\".*\"\]|find-links = [\"${PYG_URL}\"]|g" pyproject.toml + # Update Linux Source Marker + sed -i '' "s/index = \"pytorch-[a-z0-9]*\", marker = \"sys_platform == 'linux'/index = \"${TARGET_INDEX}\", marker = \"sys_platform == 'linux'/g" pyproject.toml +else + # Linux sed + sed -i "s|find-links = \[\".*\"\]|find-links = [\"${PYG_URL}\"]|g" pyproject.toml + # Update Linux Source Marker + sed -i "s/index = \"pytorch-[a-z0-9]*\", marker = \"sys_platform == 'linux'/index = \"${TARGET_INDEX}\", marker = \"sys_platform == 'linux'/g" pyproject.toml +fi + +echo "โœ… Set PyG Links to : ${PYG_URL}" +echo "โœ… Set Torch Index to: ${TARGET_INDEX}" + +# ------------------------------------------------------------------------------ +# Sync +# ------------------------------------------------------------------------------ +echo "" +echo "๐Ÿงน Cleaning old lockfile..." +rm -f uv.lock + +echo "๐Ÿ“ฆ Syncing Environment (Python 3.11)..." +# Force Python 3.11 creation +uv sync --python 3.11 --all-extras + +# ------------------------------------------------------------------------------ +# Finalize +# ------------------------------------------------------------------------------ +source .venv/bin/activate +echo "" +echo "๐Ÿ”ง Configuring Git Hooks..." +uv pip install pre-commit +pre-commit install + +echo "" +echo "=======================================================" +echo "๐ŸŽ‰ Setup Complete!" +echo "=======================================================" +python -c "import sys; import torch; print(f'โœ… Python Ver : {sys.version.split()[0]}'); print(f'โœ… Torch Version : {torch.__version__}'); print(f'โœ… CUDA Available: {torch.cuda.is_available()}'); print(f'โœ… CUDA Version : {torch.version.cuda}')" +echo "=======================================================" \ No newline at end of file