From 6acafa7f2c5e6fd5a33a0cc4b1dc2f36979f44c9 Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Thu, 13 Aug 2026 10:01:29 +0200 Subject: [PATCH] docs: add obikmer user guide and MkDocs build configuration Introduces a comprehensive documentation set covering theoretical foundations, CLI usage, installation, and system architecture. Adds MkDocs configuration and Makefile targets to generate, serve with live reload, and clean the documentation site. Includes citation styles and bibliography files for academic references. --- Makefile | 21 +- UserDocMD/architecture.md | 33 +++ UserDocMD/ecology-letters.csl | 230 +++++++++++++++++++ UserDocMD/formats/index_layout.md | 56 +++++ UserDocMD/index.md | 68 ++++++ UserDocMD/installation.md | 84 +++++++ UserDocMD/references.bib | 261 ++++++++++++++++++++++ UserDocMD/theory/encoding.md | 28 +++ UserDocMD/theory/entropy_filter.md | 36 +++ UserDocMD/theory/indexing_architecture.md | 32 +++ UserDocMD/theory/kmers_and_superkmers.md | 24 ++ UserDocMD/theory/minimizer_selection.md | 42 ++++ UserDocMD/usage/annotate.md | 25 +++ UserDocMD/usage/distance.md | 97 ++++++++ UserDocMD/usage/dump.md | 25 +++ UserDocMD/usage/estimate.md | 18 ++ UserDocMD/usage/filter.md | 47 ++++ UserDocMD/usage/index_command.md | 50 +++++ UserDocMD/usage/merge.md | 29 +++ UserDocMD/usage/pack.md | 15 ++ UserDocMD/usage/predicates.md | 46 ++++ UserDocMD/usage/query.md | 38 ++++ UserDocMD/usage/reindex.md | 25 +++ UserDocMD/usage/select.md | 35 +++ UserDocMD/usage/superkmer.md | 27 +++ UserDocMD/usage/unitig.md | 19 ++ UserDocMD/usage/utils.md | 26 +++ mkdocs-user.yml | 61 +++++ 28 files changed, 1497 insertions(+), 1 deletion(-) create mode 100644 UserDocMD/architecture.md create mode 100644 UserDocMD/ecology-letters.csl create mode 100644 UserDocMD/formats/index_layout.md create mode 100644 UserDocMD/index.md create mode 100644 UserDocMD/installation.md create mode 100644 UserDocMD/references.bib create mode 100644 UserDocMD/theory/encoding.md create mode 100644 UserDocMD/theory/entropy_filter.md create mode 100644 UserDocMD/theory/indexing_architecture.md create mode 100644 UserDocMD/theory/kmers_and_superkmers.md create mode 100644 UserDocMD/theory/minimizer_selection.md create mode 100644 UserDocMD/usage/annotate.md create mode 100644 UserDocMD/usage/distance.md create mode 100644 UserDocMD/usage/dump.md create mode 100644 UserDocMD/usage/estimate.md create mode 100644 UserDocMD/usage/filter.md create mode 100644 UserDocMD/usage/index_command.md create mode 100644 UserDocMD/usage/merge.md create mode 100644 UserDocMD/usage/pack.md create mode 100644 UserDocMD/usage/predicates.md create mode 100644 UserDocMD/usage/query.md create mode 100644 UserDocMD/usage/reindex.md create mode 100644 UserDocMD/usage/select.md create mode 100644 UserDocMD/usage/superkmer.md create mode 100644 UserDocMD/usage/unitig.md create mode 100644 UserDocMD/usage/utils.md create mode 100644 mkdocs-user.yml diff --git a/Makefile b/Makefile index dedc3bed..5889db68 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,11 @@ DOC_FILE := mkdocs.yml DOC_SITE := doc DOC_PORT := 8001 +DOC_USER_DIR := UserDocMD +DOC_USER_FILE := mkdocs-user.yml +DOC_USER_SITE := doc-user +DOC_USER_PORT := 8002 + # ── virtualenv ──────────────────────────────────────────────────────────────── $(VENV)/bin/activate: @@ -60,8 +65,22 @@ doc-serve: $(MKDOCS) clean-doc: rm -rf $(DOC_SITE)/ +.PHONY: doc-user +doc-user: $(MKDOCS) + $(MKDOCS) build -f $(DOC_USER_FILE) + +.PHONY: doc-user-serve +doc-user-serve: $(MKDOCS) + $(MKDOCS) serve -f $(DOC_USER_FILE) \ + --dev-addr=127.0.0.1:$(DOC_USER_PORT) \ + --livereload + +.PHONY: clean-doc-user +clean-doc-user: + rm -rf $(DOC_USER_SITE)/ + .PHONY: clean -clean: clean-doc +clean: clean-doc clean-doc-user rm -rf $(VENV) # ── release ─────────────────────────────────────────────────────────────────── diff --git a/UserDocMD/architecture.md b/UserDocMD/architecture.md new file mode 100644 index 00000000..87bf9b91 --- /dev/null +++ b/UserDocMD/architecture.md @@ -0,0 +1,33 @@ +# Architecture notes for advanced use + +This page describes execution-level behavior relevant to sizing and running `obikmer` on large datasets or multi-socket machines. It complements the [index format](formats/index_layout.md) and [theory](theory/indexing_architecture.md) pages. + +## Sequence invariant + +Every input sequence is treated purely as a compact representation of a set of overlapping kmers: + +- Only the `A`/`C`/`G`/`T` alphabet (case-insensitive) is recognized; a sequence is cut at any other character (including IUPAC ambiguity codes), so runs containing them are not represented in the index. +- Sequences are internally processed in chunks of at most 256 nucleotides; a chunk shorter than k is dropped. This is invisible to the user beyond the ACGT-only, minimum-length-k constraints above. +- Kmers are always handled in canonical form (see [DNA encoding](theory/encoding.md)), so the tool is strand-agnostic throughout: a kmer and its reverse complement are always the same entry. + +## Index dimensioning + +An index directory is organized as `KmerIndex → partitions → layers`, with a canonical kmer belonging to exactly one (partition, layer) pair. This is what makes set operations (merge, filter, distance) parallel and coordination-free across partitions. + +- **Partition count** (`-p`/`--partitions`, rounded up to a power of 2) is the main dimensioning knob: more partitions means more independent parallel units and a smaller working set per partition, at the cost of more open files during construction. +- **Layers** accumulate as an index grows through successive merges; per-partition query cost grows with the number of layers (worst case linear, expected constant since most kmer lookups resolve in the first layer they could plausibly be in). +- Genome columns (count or presence data) are kept at a consistent width across every layer and partition after a merge, which is what allows whole-index aggregate distances (Jaccard, Bray-Curtis, Euclidean, Hellinger, …) to be computed as a two-pass cascade (local partial sums per partition, then a global combination) with no double counting. + +## Parallel execution and NUMA awareness + +Partition-level work (index construction, `merge`, `filter`, `reindex`, `select`, `distance`'s sibling-annex/Sankoff computations) is dispatched by a partition runner that adapts to the machine's memory topology, detected automatically at startup via hwloc: + +- On a multi-socket / multi-NUMA-node machine, one thread pool is pinned per NUMA node, and each partition is processed entirely by threads pinned to one node — keeping the memory a partition touches local to that node's DRAM. This matters because touching kmer data across NUMA nodes without pinning can degrade throughput by an order of magnitude or more on large multi-socket machines. +- On a single-socket machine, Apple Silicon, or if hwloc cannot report NUMA topology, all cores are treated as one node with no pinning and negligible overhead — this is the default behavior on macOS. +- Within a node, the number of active worker threads ramps up progressively rather than being fixed up front: it starts conservatively and grows in steps, but only as long as measured CPU efficiency or disk I/O throughput keeps improving. If neither improves after a growth step, the runner stops adding workers — avoiding oversubscription on stages that are memory-bandwidth-bound rather than CPU- or I/O-bound. Ramp speed scales with the number of cores per node, so a single-node machine ramps just as fast as a large multi-node one. + +No CLI flag controls this directly; it is fully automatic at runtime. NUMA-aware pinning can be compiled out (Cargo feature `numa`, on by default), in which case a plain global thread pool is used instead. + +## Kmer filtering (`filter`) + +[`filter`](usage/filter.md) evaluates predicates against the genome metadata matrix directly whenever every active filter can be expressed as a column-level test (e.g. "any outgroup column non-zero"), producing a per-slot keep/drop decision without touching kmer sequence data at all. If any active filter cannot be expressed this way, evaluation falls back to a per-kmer, row-level check. Either way, the result is always written as a single, freshly compacted layer (`unitigs.bin` and the MPHF are rebuilt from the surviving kmers), never as an additional layer on top of the source index. diff --git a/UserDocMD/ecology-letters.csl b/UserDocMD/ecology-letters.csl new file mode 100644 index 00000000..6a96c856 --- /dev/null +++ b/UserDocMD/ecology-letters.csl @@ -0,0 +1,230 @@ + + diff --git a/UserDocMD/formats/index_layout.md b/UserDocMD/formats/index_layout.md new file mode 100644 index 00000000..09633b09 --- /dev/null +++ b/UserDocMD/formats/index_layout.md @@ -0,0 +1,56 @@ +# Index construction and on-disk layout + +## Construction pipeline + +Building an index ([`index`](../usage/index_command.md)) proceeds through a fixed sequence of phases, each operating independently per partition (see [Partitioning and indexing architecture](../theory/indexing_architecture.md)): + +1. **Scatter.** A single streaming pass over the input. Each sequence fragment is cut at non-ACGT bases, passed through the low-complexity entropy filter (see [Low-complexity kmer filter](../theory/entropy_filter.md)), and any resulting segment shorter than k is dropped. Surviving segments are decomposed into super-kmers, canonicalized, and routed by `hash(minimizer) mod n_partitions` into one file per partition. +2. **Dereplication.** Within each partition, identical super-kmer sequences are merged and their occurrence counts summed. This count is per super-kmer, not per kmer — a kmer's true abundance is the sum of the counts of every super-kmer containing it. +3. **Exact counting.** Every kmer in every dereplicated super-kmer is enumerated and its exact total count computed. A per-genome kmer frequency spectrum is produced at this stage. +4. **Quorum filtering.** Kmers outside the `--min-abundance`/`--max-abundance` range are dropped, and super-kmers are recompacted around the surviving kmer set. +5. **Local assembly.** The surviving kmers of each partition are assembled into unitigs — maximal non-branching runs of a local de Bruijn graph — such that every kmer appears exactly once, at one (unitig, offset) location. +6. **MPHF and evidence construction.** A minimal perfect hash function is built over the canonical kmers of each partition, together with the evidence structure needed to verify that a queried kmer was genuinely indexed (see below). Per-genome counts or presence bits are recorded alongside if requested. + +Phases 1–5 are independent per partition and run in parallel; phase 6 finalizes each partition once its kmer set is fixed. + +## Minimal perfect hash function (MPHF) + +Each partition's surviving kmers are mapped to a dense range of integer slots by a minimal perfect hash function: no collisions, near-optimal space (a few bits per key), O(1) lookup. Because an MPHF maps *any* input to some slot — including kmers that were never indexed — a lookup alone cannot distinguish a genuinely indexed kmer from an arbitrary one; every lookup is followed by an evidence check. + +## Evidence: exact vs. approximate + +Two verification modes are available, selected at build time (`index --approx`) and convertible afterwards ([`reindex`](../usage/reindex.md)): + +- **Exact** (default): the hashed slot stores a pointer back into the partition's unitig data. At query time the kmer is reconstructed from that location and compared directly to the query. Zero false positives, at the cost of one extra random read per lookup. +- **Approximate** (`--approx`): the slot stores a short fingerprint (`--evidence-bits` bits) instead of a pointer; verification is a single fingerprint comparison. This trades a small, bounded false-positive rate ($1/2^b$ per kmer, reduced further to about $1/2^{b \cdot z}$ for a read requiring $z$ consecutive matching kmers via the `-z`/`--findere-z` parameter) for lower memory and disk usage, since no reconstruction index is needed. See [`estimate`](../usage/estimate.md) to explore this trade-off before building. + +## On-disk layout + +``` +/ + index.meta global configuration (k, minimizer size, partition count, + evidence mode, whether counts are stored) and genome list/metadata + scatter.done / count.done / index.done build-progress sentinels + spectrums/