diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c4a6ec83..1c47ec7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,10 +17,10 @@ jobs: name: Lint & type-check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v8.2.0 with: enable-cache: true @@ -41,10 +41,10 @@ jobs: name: Build docs runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v8.2.0 with: enable-cache: true @@ -62,10 +62,10 @@ jobs: matrix: python-version: ["3.13"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v8.2.0 with: enable-cache: true @@ -86,7 +86,7 @@ jobs: - name: Upload coverage if: matrix.python-version == '3.12' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: coverage-xml path: coverage.xml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 71ee8389..72e104fd 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,30 +1,35 @@ -name: Deploy docs +name: Docs on: push: - branches: [master] + branches: [main] workflow_dispatch: +# Deploy via the GitHub Pages "GitHub Actions" source (set it in repo +# Settings -> Pages). Zensical has no `gh-deploy`; we build and upload the +# site as a Pages artifact instead. permissions: - contents: write - -concurrency: - group: docs-${{ github.ref }} - cancel-in-progress: true + contents: read + pages: write + id-token: write jobs: deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - - name: Install uv - uses: astral-sh/setup-uv@v5 + - uses: actions/configure-pages@v6 + - uses: actions/checkout@v7 + - uses: astral-sh/setup-uv@v8.2.0 with: + python-version: "3.13" enable-cache: true - - - name: Sync (docs group) - run: uv sync --group docs - - - name: Deploy to GitHub Pages - run: uv run mkdocs gh-deploy --force + - run: uv sync --dev + - run: uv run zensical build + - uses: actions/upload-pages-artifact@v6 + with: + path: site + - uses: actions/deploy-pages@v6 + id: deployment diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 981fa4c7..d78b950f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,10 +19,10 @@ jobs: name: Build distributions runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v8.2.0 - name: Build sdist + wheel run: uv build @@ -31,7 +31,7 @@ jobs: run: uvx twine check dist/* - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: dist path: dist/* @@ -44,7 +44,7 @@ jobs: permissions: id-token: write # OIDC for Trusted Publishing steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: dist path: dist diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a1c7a3da..5017bf04 100755 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.15.15 + rev: v0.15.16 hooks: # Run the linter. - id: ruff diff --git a/CITATION.cff b/CITATION.cff index 073a62b0..14093b48 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -4,7 +4,8 @@ message: >- fork and also cite the original TopOMetry work listed under references. type: software title: topometry-nosc -version: 0.1.0 +version: 0.3.0 +date-released: 2026-06-10 authors: - family-names: Madsen given-names: Jakob @@ -34,6 +35,6 @@ references: journal: eLife year: 2024 volume: 13 - article-number: RP100361 + number: RP100361 doi: 10.7554/eLife.100361.2 url: "https://doi.org/10.7554/eLife.100361.2" diff --git a/README.md b/README.md index 79182a1c..380f476d 100755 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![PyPI](https://img.shields.io/pypi/v/topometry-nosc)](https://pypi.org/project/topometry-nosc/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/HauserGroup/topometryNoSC/blob/master/notebooks/example.ipynb) [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv) @@ -161,9 +162,14 @@ Example `TopOGraph` fit: ## Changelog -**0.2.0** — Core-only release -- Removed single-cell / scanpy / AnnData wrappers (now a standalone geometry toolkit) -- Core API unchanged: `TopOGraph`, spectral scaffolds, graph operators, layouts, metrics, plotting +**0.3.0** — First PyPI release +- Published to PyPI as [`topometry-nosc`](https://pypi.org/project/topometry-nosc/) +- Simplified internals delegate to scikit-learn/SciPy/umap-learn where possible; + slimmer runtime dependencies (ANN backends and extra layouts are opt-in extras) +- Standalone building blocks (`kNN`, `Kernel`, `cknn_graph`, `EigenDecomposition`, + `LE`, `Projector`) usable and tested outside the `TopOGraph` orchestrator + +See [CHANGELOG.md](CHANGELOG.md) for the full history. #### Citation diff --git a/docs/changelog.md b/docs/changelog.md index 3bf68e65..786b75d5 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,13 +1 @@ -# Changelog - -## Unreleased - -- Documentation migrated to MkDocs Material. -- Added API reference through mkdocstrings. -- Added provenance and citation documentation. - -## v2.0.0 — Core-only release - -- Removed single-cell / scanpy / AnnData wrappers (now a standalone geometry toolkit). -- Core API unchanged: `TopOGraph`, spectral scaffolds, graph operators, layouts, - metrics, plotting. +--8<-- "CHANGELOG.md" diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index 6d1495dd..00000000 --- a/mkdocs.yml +++ /dev/null @@ -1,99 +0,0 @@ -site_name: topometry-nosc -site_description: "An independently maintained, heavily modified fork of TopOMetry." -site_url: https://HauserGroup.github.io/topometryNoSC/ -repo_url: https://github.com/HauserGroup/topometryNoSC -repo_name: HauserGroup/topometryNoSC - -watch: - - docs - - src/topo - -theme: - name: material - logo: assets/topometry_logo.png - favicon: assets/topometry_logo.png - icon: - repo: fontawesome/brands/github - font: - text: Nunito - code: JetBrains Mono - palette: - - media: "(prefers-color-scheme: light)" - scheme: default - primary: pink - accent: teal - toggle: - icon: material/weather-night - name: Switch to dark mode - - media: "(prefers-color-scheme: dark)" - scheme: slate - primary: pink - accent: teal - toggle: - icon: material/weather-sunny - name: Switch to light mode - features: - - navigation.tabs - - navigation.sections - - navigation.top - - navigation.indexes - - navigation.footer - - content.code.copy - - content.code.annotate - - search.suggest - - search.highlight - -extra_css: - - stylesheets/extra.css - -extra_javascript: - - javascripts/mathjax.js - - https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js - -plugins: - - search - - social - - mkdocstrings: - handlers: - python: - paths: [src] - options: - docstring_style: numpy - show_source: false - separate_signature: true - show_signature_annotations: true - members_order: source - merge_init_into_class: true - -nav: - - Home: index.md - - Installation: installation.md - - Quickstart: quickstart.md - - 'Practical FAQ: Manifold Learning & Dimensionality Reduction': faq.md - - Tutorials: - - 'Step by step: Manifold Learning Pipeline': tutorials/step-by-step.md - - Plotting: tutorials/plotting.md - - Data provenance: tutorials/data-provenance.md - - 'Concepts: Graphs & Spectral Scaffolds': concepts.md - - 'Math Details: LBO, Diffusion Maps, & CkNN': math_details.md - - 'Background: CkNN, Diffusion Maps, & Manifold Learning': background.md - - API Reference: - - Overview: api/index.md - - 'Main workflow: TopOGraph': api/topograph.md - - Advanced estimators: api/advanced.md - - Evaluation metrics: api/metrics.md - - Plotting: api/plotting.md - - Developer reference: api/internal.md - - Citation: citation.md - - Changelog: changelog.md - -markdown_extensions: - - admonition - - toc: - permalink: true - - tables - - pymdownx.superfences - - pymdownx.highlight - - pymdownx.inlinehilite - - pymdownx.arithmatex: - generic: true diff --git a/src/topo/layouts/map.py b/src/topo/layouts/map.py index b11909d7..67a0fd76 100755 --- a/src/topo/layouts/map.py +++ b/src/topo/layouts/map.py @@ -141,11 +141,12 @@ def fuzzy_embedding( euclidean_output : bool, default=True Whether to use the faster code specialised for euclidean output metrics save_every : int or None, optional - If provided and >0, store the embedding every `save_every` epochs into + If provided and >0, store the final embedding into `aux_data["checkpoints"]` as a list of dicts: [{"epoch": e, "embedding": Y_e}, ...] - WARNING: storing many snapshots can be memory intensive. Consider - passing `save_callback` to stream snapshots to disk. + Per-epoch checkpointing is not implemented since layout optimization + is delegated to `umap-learn`; at most the initial (epoch 0) and final + snapshots are captured. save_limit : int or None, optional Maximum number of snapshots to keep in-memory in `aux_data`. If exceeded, the earliest snapshots are discarded (FIFO). diff --git a/src/topo/layouts/projector.py b/src/topo/layouts/projector.py index 05767c11..ffa793e9 100755 --- a/src/topo/layouts/projector.py +++ b/src/topo/layouts/projector.py @@ -23,7 +23,7 @@ from topo.base.ann import kNN from topo.layouts.isomap import Isomap from topo.layouts.map import fuzzy_embedding -from topo.spectral.eigen import EigenDecomposition +from topo.spectral import LE from topo.tpgraph.kernels import Kernel from topo.utils._utils import get_landmark_indices @@ -73,10 +73,11 @@ class Projector(BaseEstimator, TransformerMixin): projection_method : str, default='Isomap' Which projection method to use. `UMAP` delegates to `umap-learn`; `MAP` - is TopoMetry's local checkpoint-aware graph-layout optimizer. Current options are: + is TopoMetry's graph-layout optimizer (optional initial/final snapshot + capture). Current options are: * 'Isomap' - one of the first manifold learning methods * ['t-SNE'](https://github.com/DmitryUlyanov/Multicore-TSNE) - a classic manifold learning method - * 'MAP' - local checkpoint-aware graph-layout optimization + * 'MAP' - graph-layout optimization with initial/final snapshot capture * ['UMAP'](https://umap-learn.readthedocs.io/en/latest/index.html) - upstream `umap-learn` estimator * ['PaCMAP'](http://jmlr.org/papers/v22/20-1061.html) (Pairwise-controlled Manifold Approximation and Projection) - for balanced visualizations * ['TriMAP'](https://github.com/eamid/trimap) - dimensionality reduction using triplets @@ -125,11 +126,11 @@ def __init__( keep_estimator=False, random_state=None, verbose=False, - # ---- NEW: checkpointing passthrough to MAP ---- - save_every=None, # int or None: store Y every `save_every` epochs + # ---- checkpointing passthrough to MAP ---- + save_every=None, # int or None: store final MAP embedding in aux checkpoints save_limit=None, # cap snapshots kept in memory save_callback=None, # callable(epoch:int, Y:np.ndarray) -> None - include_init_snapshot=True, # store epoch=0 (post-init) snapshot + include_init_snapshot=True, # store epoch=0 snapshot before SGD ): self.n_components = n_components self.metric = metric @@ -317,9 +318,14 @@ def fit(self, X: np.ndarray | csr_matrix | Kernel, **kwargs: Any) -> "Projector" if self.init == "spectral": try: self.init_Y_ = np.asarray( - EigenDecomposition( - n_components=self.n_components - ).fit_transform(K) + LE( + K, + n_eigs=int(self.n_components), + laplacian_type="normalized", + drop_first=True, + return_evals=False, + ), + dtype=np.float32, ) except Exception: warnings.warn( @@ -327,7 +333,7 @@ def fit(self, X: np.ndarray | csr_matrix | Kernel, **kwargs: Any) -> "Projector" ) self.init_Y_ = self.random_state.randn( _n_rows(K, "projection graph"), self.n_components - ) + ).astype(np.float32) else: self.init_Y_ = self.random_state.randn( _n_rows(K, "projection graph"), self.n_components diff --git a/src/topo/spectral/eigen.py b/src/topo/spectral/eigen.py index a5c5d0c3..0354a88d 100755 --- a/src/topo/spectral/eigen.py +++ b/src/topo/spectral/eigen.py @@ -164,7 +164,7 @@ def eigendecompose( G_csr, k=k, which="LM" if largest else "SM", - tol=float(eigen_tol), # type: ignore + tol=_eigsh_tol(eigen_tol), ) except ArpackError as exc: raise RuntimeError(