From 81d3170073d5d34a5b876fbe92aeac9c7eb928cd Mon Sep 17 00:00:00 2001 From: Ori Kronfeld Date: Mon, 9 Dec 2024 15:19:48 +0200 Subject: [PATCH 01/10] adding cuda test --- .github/workflows/build.yml | 8 ++- .github/workflows/test_linux_cuda.yml | 72 +++++++++++++++++++++++++++ .readthedocs.yaml | 6 ++- tests/conftest.py | 28 +++++++++++ 4 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/test_linux_cuda.yml create mode 100644 tests/conftest.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 28e97ee..5267274 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,9 +2,13 @@ name: Check Build on: push: - branches: [main] + branches: [main, "[0-9]+.[0-9]+.x"] pull_request: - branches: [main] + branches: [main, "[0-9]+.[0-9]+.x"] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: package: diff --git a/.github/workflows/test_linux_cuda.yml b/.github/workflows/test_linux_cuda.yml new file mode 100644 index 0000000..50c2c56 --- /dev/null +++ b/.github/workflows/test_linux_cuda.yml @@ -0,0 +1,72 @@ +name: PopV (cuda) + +on: + push: + branches: [main, "[0-9]+.[0-9]+.x"] #this is new + pull_request: + branches: [main, "[0-9]+.[0-9]+.x"] + types: [labeled, synchronize, opened] + schedule: + - cron: "0 10 * * *" # runs at 10:00 UTC (03:00 PST) every day + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + # if PR has label "cuda tests" or "all tests" or if scheduled or manually triggered + if: >- + ( + contains(github.event.pull_request.labels.*.name, 'cuda tests') || + contains(github.event.pull_request.labels.*.name, 'all tests') || + contains(github.event_name, 'schedule') || + contains(github.event_name, 'workflow_dispatch') + ) + + runs-on: [self-hosted, Linux, X64, CUDA] + + defaults: + run: + shell: bash -e {0} # -e to fail on error + + container: + image: ghcr.io/yoseflab/popv:py3.10-cu12-base + options: --user root --gpus all --pull always + + name: integration + + env: + OS: ${{ matrix.os }} + PYTHON: ${{ matrix.python }} + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + cache: "pip" + cache-dependency-path: "**/pyproject.toml" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip wheel uv + python -m uv pip install --system "PopV[tests] @ ." + python -m pip install jax[cuda] + python -m pip install nvidia-nccl-cu12 + + - name: Run pytest + env: + MPLBACKEND: agg + PLATFORM: ${{ matrix.os }} + DISPLAY: :42 + COLUMNS: 120 + run: | + coverage run -m pytest -v --color=yes --accelerator cuda --devices auto + coverage report + + - uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 69897c3..58f1018 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -13,4 +13,8 @@ python: - method: pip path: . extra_requirements: - - doc + - docsbuild +submodules: + include: + - "docs/notebooks" + recursive: true diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..b197c34 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,28 @@ +import shutil +import pytest + +def pytest_addoption(parser): + """Docstring for pytest_addoption.""" + parser.addoption( + "--accelerator", + action="store", + default="cpu", + help="Option to specify which accelerator to use for tests.", + ) + parser.addoption( + "--devices", + action="store", + default="auto", + help="Option to specify which devices to use for tests.", + ) + +@pytest.fixture(scope="session") +def accelerator(request): + """Docstring for accelerator.""" + return request.config.getoption("--accelerator") + + +@pytest.fixture(scope="session") +def devices(request): + """Docstring for devices.""" + return request.config.getoption("--devices") From 1e8e0d204e46559fa0f98da9efe9f79496dbb741 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:20:54 +0000 Subject: [PATCH 02/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/notebooks/tabula_sapiens_tutorial.ipynb | 2 +- tabula_sapiens_tutorial.ipynb | 2 +- tests/conftest.py | 3 +- tests/core/test_models.py | 30 +++++++++----------- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/docs/notebooks/tabula_sapiens_tutorial.ipynb b/docs/notebooks/tabula_sapiens_tutorial.ipynb index 03dd337..f380c47 100644 --- a/docs/notebooks/tabula_sapiens_tutorial.ipynb +++ b/docs/notebooks/tabula_sapiens_tutorial.ipynb @@ -1254,4 +1254,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} diff --git a/tabula_sapiens_tutorial.ipynb b/tabula_sapiens_tutorial.ipynb index 2722a92..fb07b36 100644 --- a/tabula_sapiens_tutorial.ipynb +++ b/tabula_sapiens_tutorial.ipynb @@ -1582,4 +1582,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} diff --git a/tests/conftest.py b/tests/conftest.py index b197c34..519a49f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,6 @@ -import shutil import pytest + def pytest_addoption(parser): """Docstring for pytest_addoption.""" parser.addoption( @@ -16,6 +16,7 @@ def pytest_addoption(parser): help="Option to specify which devices to use for tests.", ) + @pytest.fixture(scope="session") def accelerator(request): """Docstring for accelerator.""" diff --git a/tests/core/test_models.py b/tests/core/test_models.py index dad6c1d..0a0b3f3 100644 --- a/tests/core/test_models.py +++ b/tests/core/test_models.py @@ -10,7 +10,7 @@ from popv.reproducibility import _accuracy -def _get_test_anndata(cl_obo_folder="resources/ontology/", mode='retrain'): +def _get_test_anndata(cl_obo_folder="resources/ontology/", mode="retrain"): print("UUU", os.getcwd()) save_folder = "tests/tmp_testing/popv_test_results/" fn = save_folder + "annotated_query.h5ad" @@ -180,41 +180,39 @@ def test_celltypist(): def test_annotation(): """Test Annotation and Plotting pipeline.""" adata = _get_test_anndata().adata - popv.annotation.annotate_data( - adata, methods=["svm", "rf"], - save_path="tests/tmp_testing/popv_test_results/") + popv.annotation.annotate_data(adata, methods=["svm", "rf"], save_path="tests/tmp_testing/popv_test_results/") popv.visualization.agreement_score_bar_plot(adata) popv.visualization.prediction_score_bar_plot(adata) popv.visualization.make_agreement_plots(adata, prediction_keys=adata.uns["prediction_keys"], show=False) popv.visualization.celltype_ratio_bar_plot(adata) obo_fn = "resources/ontology/cl.obo" - _accuracy._ontology_accuracy(adata[adata.obs['_dataset']=='ref'], obofile=obo_fn, gt_key='cell_ontology_class', pred_key='popv_prediction') - _accuracy._fine_ontology_sibling_accuracy(adata[adata.obs['_dataset']=='ref'], obofile=obo_fn, gt_key='cell_ontology_class', pred_key='popv_prediction') + _accuracy._ontology_accuracy( + adata[adata.obs["_dataset"] == "ref"], obofile=obo_fn, gt_key="cell_ontology_class", pred_key="popv_prediction" + ) + _accuracy._fine_ontology_sibling_accuracy( + adata[adata.obs["_dataset"] == "ref"], obofile=obo_fn, gt_key="cell_ontology_class", pred_key="popv_prediction" + ) assert "popv_majority_vote_prediction" in adata.obs.columns assert not adata.obs["popv_majority_vote_prediction"].isnull().any() - adata = _get_test_anndata(mode='inference').adata - popv.annotation.annotate_data( - adata, save_path="tests/tmp_testing/popv_test_results/") + adata = _get_test_anndata(mode="inference").adata + popv.annotation.annotate_data(adata, save_path="tests/tmp_testing/popv_test_results/") - adata = _get_test_anndata(mode='fast').adata - popv.annotation.annotate_data( - adata, save_path="tests/tmp_testing/popv_test_results/") + adata = _get_test_anndata(mode="fast").adata + popv.annotation.annotate_data(adata, save_path="tests/tmp_testing/popv_test_results/") def test_annotation_no_ontology(): """Test Annotation and Plotting pipeline without ontology.""" adata = _get_test_anndata(cl_obo_folder=False).adata - popv.annotation.annotate_data( - adata, methods=["svm", "rf"], - save_path="tests/tmp_testing/popv_test_results/") + popv.annotation.annotate_data(adata, methods=["svm", "rf"], save_path="tests/tmp_testing/popv_test_results/") popv.visualization.agreement_score_bar_plot(adata) popv.visualization.prediction_score_bar_plot(adata) popv.visualization.make_agreement_plots(adata, prediction_keys=adata.uns["prediction_keys"]) popv.visualization.celltype_ratio_bar_plot(adata, save_folder="tests/tmp_testing/popv_test_results/") popv.visualization.celltype_ratio_bar_plot(adata, normalize=False) - adata.obs['empty_columns'] = 'a' + adata.obs["empty_columns"] = "a" input_data = adata.obs[["empty_columns", "popv_rf_prediction"]].values.tolist() popv.reproducibility._alluvial.plot(input_data) From 0f10026a2f1176d1fb4a9a38ace9e66c356f3b9e Mon Sep 17 00:00:00 2001 From: Ori Kronfeld Date: Thu, 12 Dec 2024 15:22:32 +0200 Subject: [PATCH 03/10] update RTD yaml --- .readthedocs.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 58f1018..cdc6534 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,13 +1,10 @@ -# https://docs.readthedocs.io/en/stable/config-file/v2.html version: 2 build: os: ubuntu-20.04 tools: - python: "3.10" + python: "3.11" sphinx: configuration: docs/conf.py - # disable this for more lenient docs builds - fail_on_warning: true python: install: - method: pip From 8633c5eccc965b290393ce28852647529bb3e2b2 Mon Sep 17 00:00:00 2001 From: Ori Kronfeld Date: Thu, 12 Dec 2024 15:48:55 +0200 Subject: [PATCH 04/10] update conf file --- docs/conf.py | 15 +++++++++------ pyproject.toml | 20 ++++++++++---------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 7274633..1eb8008 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -48,17 +48,20 @@ # They can be extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ "myst_nb", - "sphinx_copybutton", "sphinx.ext.autodoc", "sphinx.ext.intersphinx", - "sphinx.ext.autosummary", + "sphinx.ext.linkcode", + "sphinx.ext.mathjax", "sphinx.ext.napoleon", + "sphinx_autodoc_typehints", # needs to be after napoleon + "sphinx.ext.extlinks", + "sphinx.ext.autosummary", "sphinxcontrib.bibtex", - "sphinx_autodoc_typehints", - "sphinx.ext.mathjax", - "IPython.sphinxext.ipython_console_highlighting", - "sphinxext.opengraph", *[p.stem for p in (HERE / "extensions").glob("*.py")], + "sphinx_copybutton", + "sphinx_design", + "sphinxext.opengraph", + "hoverxref.extension", ] autosummary_generate = True diff --git a/pyproject.toml b/pyproject.toml index 77f3bd4..51ce43a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,18 +51,18 @@ dev = [ "twine>=4.0.2" ] doc = [ - "docutils>=0.8,!=0.18.*,!=0.19.*", - "sphinx>=4", - "sphinx-book-theme>=1.0.0", - "myst-nb", + "docutils>=0.8,!=0.18.*,!=0.19.*", # see https://github.com/scverse/cookiecutter-scverse/pull/205 + "sphinx>=4.1", + "ipython", + "sphinx-book-theme>=1.0.1", + "sphinx_copybutton", + "sphinx-design", + "sphinxext-opengraph", + "sphinx-hoverxref", "sphinxcontrib-bibtex>=1.0.0", + "myst-parser", + "myst-nb", "sphinx-autodoc-typehints", - "sphinxext-opengraph", - # For notebooks - "ipykernel", - "ipython", - "sphinx-copybutton", - "pandas", ] test = [ "pytest", From f9f1e41e597ebd833dd89964527874250ab4d538 Mon Sep 17 00:00:00 2001 From: Ori Kronfeld Date: Thu, 12 Dec 2024 16:09:04 +0200 Subject: [PATCH 05/10] update pyproj file --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 51ce43a..8c20cb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ dev = [ "pre-commit", "twine>=4.0.2" ] -doc = [ +docs = [ "docutils>=0.8,!=0.18.*,!=0.19.*", # see https://github.com/scverse/cookiecutter-scverse/pull/205 "sphinx>=4.1", "ipython", @@ -64,6 +64,7 @@ doc = [ "myst-nb", "sphinx-autodoc-typehints", ] +docsbuild = ["popv[docs]"] test = [ "pytest", "coverage", From a27e74865a11ef8139b80c1f6f2f9c828a7712c6 Mon Sep 17 00:00:00 2001 From: Ori Kronfeld Date: Thu, 12 Dec 2024 16:17:29 +0200 Subject: [PATCH 06/10] update conf file again --- docs/conf.py | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 1eb8008..4a9e236 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,6 +9,15 @@ from datetime import datetime from importlib.metadata import metadata from pathlib import Path +import importlib.util +import inspect +import os +import re +import subprocess +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any HERE = Path(__file__).parent sys.path.insert(0, str(HERE / "extensions")) @@ -130,3 +139,79 @@ # you can add an exception to this list. # ("py:class", "igraph.Graph"), ] + + + +# -- Config for linkcode ------------------------------------------- + + +def git(*args): + """Run git command and return output as string.""" + return subprocess.check_output(["git", *args]).strip().decode() + + +# https://github.com/DisnakeDev/disnake/blob/7853da70b13fcd2978c39c0b7efa59b34d298186/docs/conf.py#L192 +# Current git reference. Uses branch/tag name if found, otherwise uses commit hash +git_ref = None +try: + git_ref = git("name-rev", "--name-only", "--no-undefined", "HEAD") + git_ref = re.sub(r"^(remotes/[^/]+|tags)/", "", git_ref) +except Exception: + pass + +# (if no name found or relative ref, use commit hash instead) +if not git_ref or re.search(r"[\^~]", git_ref): + try: + git_ref = git("rev-parse", "HEAD") + except Exception: + git_ref = "main" + +# https://github.com/DisnakeDev/disnake/blob/7853da70b13fcd2978c39c0b7efa59b34d298186/docs/conf.py#L192 +_scvi_tools_module_path = os.path.dirname(importlib.util.find_spec("scvi").origin) # type: ignore + + +def linkcode_resolve(domain, info): + """Determine the URL corresponding to Python object.""" + if domain != "py": + return None + + try: + obj: Any = sys.modules[info["module"]] + for part in info["fullname"].split("."): + obj = getattr(obj, part) + obj = inspect.unwrap(obj) + + if isinstance(obj, property): + obj = inspect.unwrap(obj.fget) # type: ignore + + path = os.path.relpath(inspect.getsourcefile(obj), start=_scvi_tools_module_path) # type: ignore + src, lineno = inspect.getsourcelines(obj) + except Exception: + return None + + path = f"{path}#L{lineno}-L{lineno + len(src) - 1}" + return f"{repository_url}/blob/{git_ref}/src/scvi/{path}" + + +# -- Config for hoverxref ------------------------------------------- + +hoverx_default_type = "tooltip" +hoverxref_domains = ["py"] +hoverxref_role_types = dict.fromkeys( + ["ref", "class", "func", "meth", "attr", "exc", "data", "mod"], + "tooltip", +) +hoverxref_intersphinx = [ + "python", + "numpy", + "scanpy", + "anndata", + "pytorch_lightning", + "scipy", + "pandas", + "ml_collections", + "ray", +] +# use proxied API endpoint on rtd to avoid CORS issues +if os.environ.get("READTHEDOCS"): + hoverxref_api_host = "/_" From d52e365386fd6e0ed8dba8c50898e25f5ec0118b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 14:18:06 +0000 Subject: [PATCH 07/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/conf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 4a9e236..99df092 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -141,7 +141,6 @@ ] - # -- Config for linkcode ------------------------------------------- From bf8cab4ac4dfe555285218dc062eaf937a4a7c90 Mon Sep 17 00:00:00 2001 From: Ori Kronfeld Date: Thu, 12 Dec 2024 16:25:24 +0200 Subject: [PATCH 08/10] update pre-commit --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ef5789e..d2e817c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,8 +2,8 @@ fail_fast: false default_language_version: python: python3 default_stages: - - commit - - push + - pre-commit + - pre-push minimum_pre_commit_version: 2.16.0 repos: - repo: https://github.com/psf/black From 38422f11212beebd88e76b74fad6dfb158df9c98 Mon Sep 17 00:00:00 2001 From: Ori Kronfeld Date: Thu, 12 Dec 2024 16:27:43 +0200 Subject: [PATCH 09/10] remove cuda daily test --- .github/workflows/test_linux_cuda.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/test_linux_cuda.yml b/.github/workflows/test_linux_cuda.yml index 50c2c56..6926ab2 100644 --- a/.github/workflows/test_linux_cuda.yml +++ b/.github/workflows/test_linux_cuda.yml @@ -6,8 +6,6 @@ on: pull_request: branches: [main, "[0-9]+.[0-9]+.x"] types: [labeled, synchronize, opened] - schedule: - - cron: "0 10 * * *" # runs at 10:00 UTC (03:00 PST) every day workflow_dispatch: concurrency: From cb4ea856c1aa3b2b4c07f2e9ae33e73892ae2a42 Mon Sep 17 00:00:00 2001 From: Can Ergen Date: Thu, 12 Dec 2024 23:28:32 -0800 Subject: [PATCH 10/10] Pre-commit for documentation. --- docs/conf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 99df092..8ba3576 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -155,14 +155,14 @@ def git(*args): try: git_ref = git("name-rev", "--name-only", "--no-undefined", "HEAD") git_ref = re.sub(r"^(remotes/[^/]+|tags)/", "", git_ref) -except Exception: +except Exception: # noqa: BLE001 pass # (if no name found or relative ref, use commit hash instead) if not git_ref or re.search(r"[\^~]", git_ref): try: git_ref = git("rev-parse", "HEAD") - except Exception: + except Exception: # noqa: BLE001 git_ref = "main" # https://github.com/DisnakeDev/disnake/blob/7853da70b13fcd2978c39c0b7efa59b34d298186/docs/conf.py#L192 @@ -185,7 +185,7 @@ def linkcode_resolve(domain, info): path = os.path.relpath(inspect.getsourcefile(obj), start=_scvi_tools_module_path) # type: ignore src, lineno = inspect.getsourcelines(obj) - except Exception: + except Exception: # noqa: BLE001 return None path = f"{path}#L{lineno}-L{lineno + len(src) - 1}"