Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 7 additions & 6 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ name: Docs

on:
push:
branches: [dev] # dev is the default branch; the github-pages environment
paths: [docs/**, python/**, src/**, "**/*.py", .github/workflows/docs.yml]
branches: [master, dev] # master is the default/release branch.
paths: [docs/**, python/**, src/**, "**/*.py", pyproject.toml, CMakeLists.txt, .github/workflows/docs.yml]
pull_request:
branches: [master, dev]
workflow_dispatch:

permissions:
Expand All @@ -12,7 +14,7 @@ permissions:
id-token: write

concurrency:
group: pages
group: pages-${{ github.ref }}
cancel-in-progress: true

jobs:
Expand All @@ -25,9 +27,7 @@ jobs:
python-version: "3.12"
- uses: astral-sh/setup-uv@v6
- name: Install package + docs deps
run: |
uv pip install --system -e .
uv pip install --system -r docs/requirements.txt
run: uv pip install --system -e ".[docs]"
- name: Build HTML
run: sphinx-build -b html -W --keep-going docs docs/_build/html
- uses: actions/configure-pages@v5
Expand All @@ -36,6 +36,7 @@ jobs:
path: docs/_build/html

deploy:
if: github.ref == 'refs/heads/master'
needs: build
runs-on: ubuntu-latest
environment:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ tmp/
!/skills/seqtree/
/add_bindings.py
/add_native.py

# Private retired branches, notes and scratch; never package or publish.
/attic/
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
All notable changes to `seqtree`. Dates are release dates. From 1.0.0 the project follows semantic
versioning: breaking changes need a **major** bump.

## Unreleased

## 1.3.3

- Cap automatic native CPU budgets at 16 allocated CPUs; preserve larger explicit
requests within the allocation and serial inner kernels in sample workers.

- Reject unknown setup flags before creating an environment or installing packages.
- Keep native bindings in the reviewed nanobind 2.x range.
- Build documentation from declared extras on master/dev and pull requests;
deploy master only, without development builds cancelling release documentation.

## [1.3.1] — 2026-10-19

### Changed
Expand Down
4 changes: 1 addition & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
if(NOT MSVC)
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
endif() # MSVC Release already uses /O2; -O3 is not a valid MSVC flag
# Keep CMake/toolchain Release flags, including compiler-specific optimization.

option(SEQTREE_TESTS "Build C++ tests" OFF)
option(SEQTREE_BENCH "Build C++ benchmarks" OFF)
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Python](https://img.shields.io/pypi/pyversions/seqtree.svg)](https://pypi.org/project/seqtree/)
[![License](https://img.shields.io/badge/license-GPLv3-green)](LICENSE)
[![CI](https://github.com/antigenomics/seqtree/actions/workflows/ci.yml/badge.svg)](https://github.com/antigenomics/seqtree/actions/workflows/ci.yml)
[![Docs](https://github.com/antigenomics/seqtree/actions/workflows/docs.yml/badge.svg?branch=dev)](https://antigenomics.github.io/seqtree/)
[![Docs](https://github.com/antigenomics/seqtree/actions/workflows/docs.yml/badge.svg?branch=master)](https://antigenomics.github.io/seqtree/)

Fast fuzzy search over biological sequences (amino-acid or nucleotide), as a C++
core with a minimal Python binding. Build an immutable index once, then search
Expand Down Expand Up @@ -32,7 +32,7 @@ for hit in idx.search("CASSLAPGATNEKLFF", p):
print(hit.ref_id, hit.score, hit.n_subs)

# parallel batch (releases the GIL)
results = idx.search_batch(queries, p, threads=0) # 0 = all cores
results = idx.search_batch(queries, p, threads=0) # 0 = automatic, up to 16 allocated CPUs

# matrix-weighted budget. Name seqtrie -- engine="auto" always means seqtm.
# gap_open must follow the matrix: 2 * blosum62.scale() == 28, not the default 1.
Expand Down Expand Up @@ -273,3 +273,7 @@ and tie, count, exclusion and unsupported-value semantics.

The public [seqtree skill](skills/seqtree/SKILL.md) guides use of the C++20 library
and Python bindings for sequence search, alignment, distances, and batch operations.

CPU budgets use `threads=0` for automatic parallel work, capped at 16 allocated CPUs.
Use `1` for serial work or a positive
count to override the automatic cap within the scheduler/container allocation.
2 changes: 0 additions & 2 deletions docs/requirements.txt

This file was deleted.

4 changes: 2 additions & 2 deletions include/seqtree/parallel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ inline unsigned available_cpus() {
}

// Runs body(i, local) for every i in [0, n), across nt workers pulling contiguous chunks off
// one atomic cursor. `threads <= 0` means the detected available CPU budget.
// one atomic cursor. `threads <= 0` selects up to 16 available CPUs; positive counts may exceed that cap.
//
// `make_local()` is called once per worker and its result handed to every body() that worker
// runs -- that is how a Searcher or a scratch buffer gets allocated once per thread rather
Expand All @@ -85,7 +85,7 @@ template <class MakeLocal, class Body>
void parallel_for(size_t n, int threads, MakeLocal make_local, Body body,
size_t chunk_min = 1, size_t chunk_max = 1024) {
const unsigned available = available_cpus();
unsigned nt = threads > 0 ? unsigned(threads) : available;
unsigned nt = threads > 0 ? unsigned(threads) : std::min(available, 16u);
if (nt > available || nt >= 1000)
throw std::invalid_argument("worker count exceeds the available CPU budget; request fewer threads");
if (n == 0) return;
Expand Down
7 changes: 4 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[build-system]
requires = ["scikit-build-core>=0.12", "nanobind>=2.5"]
requires = ["scikit-build-core>=0.12", "nanobind>=2.5,<3"]
build-backend = "scikit_build_core.build"

[project]
name = "seqtree"
version = "1.3.2"
version = "1.3.3"
description = "Fast fuzzy search over biological sequences (C++ core, Python bindings)"
readme = "README.md"
requires-python = ">=3.10"
Expand All @@ -26,7 +26,7 @@ dependencies = []
# seqtree itself has no runtime dependencies and must never import it.
test = ["pytest>=7.4", "pytest-cov>=4.1", "biopython>=1.81"]
bench = ["huggingface_hub"]
docs = ["sphinx", "pydata-sphinx-theme"]
docs = ["sphinx>=7", "pydata-sphinx-theme>=0.15"]
control = ["huggingface_hub"] # only needed to download controls larger than the bundled subset
pmhc = ["huggingface_hub"] # only needed to fetch the isalgo/pmhc_data epitope tables

Expand All @@ -40,6 +40,7 @@ wheel.packages = ["python/seqtree"]
cmake.build-type = "Release"
# Keep the sdist lean: ship only what builds the package. Anchor with a leading "/".
sdist.exclude = [
"/attic/",
"/bench", "/tests", "/docs", "/.github", "/setup.sh", "/build", "/appendix", "/examples",
# Local agent config: machine-specific absolute paths and unrelated repo names.
"/.claude", "/CLAUDE.md", "/AGENTS.md", "/ROADMAP.md", "/SOURCES.md", "/skills",
Expand Down
19 changes: 10 additions & 9 deletions setup.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
#!/bin/sh
# Repo-local venv + editable install, via uv. POSIX sh -- runs under bash and zsh. Flags:
# --tests also install pytest extra
# --bench also install benchmark extras (huggingface_hub, psutil)
# --bench also install benchmark extras (huggingface_hub)
#
# Needs uv (https://docs.astral.sh/uv/); `brew install uv` or the standalone installer.
set -eu
cd "$(dirname "$0")" || exit 1

extras=""
for arg in "$@"; do
case "$arg" in
--tests) extras="${extras}test," ;;
--bench) extras="${extras}bench," ;;
*) echo "Unknown flag: $arg" >&2; exit 2 ;;
esac
done

if ! command -v uv >/dev/null 2>&1; then
echo "setup.sh needs uv -- install it with 'brew install uv' or see https://docs.astral.sh/uv/"
exit 1
Expand All @@ -19,14 +28,6 @@ if [ ! -d .venv ]; then
fi
. .venv/bin/activate

extras=""
for arg in "$@"; do
case "$arg" in
--tests) extras="${extras}test," ;;
--bench) extras="${extras}bench," ;;
esac
done

if [ -n "$extras" ]; then extras="[${extras%,}]"; fi
uv pip install -e ".$extras"

Expand Down
3 changes: 3 additions & 0 deletions skills/seqtree/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,6 @@ Keep exact hits for ordinary annotation. Exclusion filters hits without rebuildi
indices, refitting models, or changing population sizes. The legacy `evalues`
helper excludes zero-score hits, which can include nonidentical sequences under
masked penalties; other reducers use encoded identity. Check the specific API.

Automatic native batches use at most 16 available CPUs; positive thread counts
override this cap within the allocation.
11 changes: 11 additions & 0 deletions tests/cpp/test_parallel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,14 @@ TEST_CASE("parallel batches reject an excessive live worker budget") {
[] { return 0; }, [](size_t, int&) {}),
std::invalid_argument);
}


TEST_CASE("automatic batches cap workers without capping the allocation") {
for (int requested : {0, int(seqtree::available_cpus())}) {
std::atomic<unsigned> factories{0};
seqtree::parallel_for(2048, requested, [&] { ++factories; return 0; },
[](size_t, int&) {});
CHECK(factories == (requested ? unsigned(requested) :
std::min(seqtree::available_cpus(), 16u)));
}
}
4 changes: 4 additions & 0 deletions tests/python/test_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ def test_setup_extras_and_install_failure(tmp_path):
root = tmp_path / "project"
root.mkdir()
(root / "setup.sh").write_text((Path(__file__).resolve().parents[2] / "setup.sh").read_text())
unknown = subprocess.run(["sh", "setup.sh", "--unknown"], cwd=root,
env=env, capture_output=True, text=True, timeout=10)
assert unknown.returncode == 2 and "Unknown flag: --unknown" in unknown.stderr
assert not (root / ".venv").exists()
activation = root / ".venv" / "bin" / "activate"
activation.parent.mkdir(parents=True)
activation.write_text("")
Expand Down
Loading