haystackfm 0.3.0

GPU-accelerated FM-index construction for DNA sequences via WebGPU
Documentation
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Before 1.0, a breaking change bumps the **minor** version.

## [Unreleased]

## [0.3.0] - 2026-07-24

Breaking release: the index now retains the indexed text and can serve the bases of any
sequence, so callers no longer need to keep their own copy of every reference.

### Added
- `FmIndex::sequence(SeqId)` / `BidirFmIndex::sequence(SeqId)` — the bases of one indexed
  sequence as a borrowed slice, in O(1), with the trailing sentinel excluded. Returns
  alphabet codes (`A = 1`, `C = 2`, …), not ASCII; use `decode_char` to render them.
- `FmIndex::sequence_by_header(&str)` / `BidirFmIndex::sequence_by_header(&str)` — the same
  by header, equivalent to `seq_id(h).and_then(|id| self.sequence(id))`.
- `encode_byte`, `encode_char` and `decode_char` re-exported at the crate root, so callers
  can move queries into (and results out of) the alphabet's code space without reaching
  into the `alphabet` module.

### Changed
- **Breaking.** The serialized layout gains a `text` field, so indexes written by 0.2.0 and
  earlier are rejected by `from_bytes` and must be rebuilt.
- The concatenated text is no longer dropped after BWT construction. This costs ~n bytes of
  resident and serialized size and forgoes a build-time peak-memory reduction, in exchange
  for random-access substrings. An FM-index can otherwise only recover text by LF-walking
  backwards one symbol at a time — far too slow for a caller rescoring a read against a
  candidate diagonal, which is the case this exists to serve.
- Only the forward half of a `BidirFmIndex` retains text. The reverse half's copy is a
  redundant reversal that is never served, so it is released at build time, keeping the
  overhead at ~n rather than ~2n.

## [0.2.0] - 2026-07-24

Breaking release: match locations are now reported by integer sequence id instead of by
FASTA header string.

### Changed
- **Breaking.** Queries report match locations as `(SeqId, offset)` rather than
  `(String, offset)`, so no header string is allocated per occurrence — the cost was per
  occurrence rather than per seed, and scaled with seed multiplicity. Affects
  `FmIndex::locate`, `FmIndex::locate_gpu`, `FmIndex::map_position`,
  `BidirFmIndex::locate_interval`, `Mem::positions` and `MemHit::positions`, and the WASM
  `locate`. Callers that need labels resolve ids through `seq_header()`, or build an
  `id -> label` table once from `seq_headers()` and index it by `SeqId::index()`.
- **Breaking.** Sequence headers must now be unique; a collision fails the build with
  `FmIndexError::DuplicateHeader`. This is what makes `seq_id` an exact inverse of
  `seq_header`. Sequences supplied without a header are still auto-named `seq_{i}`, so this
  only fires on genuinely repeated names.

### Added
- `SeqId`, a stable 0-based identifier for an indexed reference. Assigned in build order and
  preserved across `to_bytes` / `from_bytes`.
- Sequence-id accessors on `FmIndex` and `BidirFmIndex``seq_headers()`, `seq_header(id)`
  and `seq_id(header)`. Both directions are O(1), backed by a header map built at
  construction and rebuilt on deserialization (it is derived, so it is not serialized).
- `FmIndexError::DuplicateHeader`.
- WASM bindings for the accessors: `seq_header`, `seq_id`, `seq_headers`.

The on-disk index format is unchanged — a `SeqId` is the position in the already-serialized
header list, and the header map is rebuilt on load rather than stored — so indexes written by
0.1.0 deserialize under 0.2.0, with one exception: an index built by 0.1.0 from *duplicate*
headers is now rejected by `from_bytes` with `DuplicateHeader`, since 0.1.0 permitted
collisions that 0.2.0 does not. Rebuild those indexes with unique headers.

## [0.1.0] - 2026-07-16

First release under the `haystackfm` name. The project was previously published as
`webgpu-fmidx` (versions 0.1.0–0.5.1); version numbering restarts at 0.1.0 under the new
name, and its earlier history is not carried over here.

### Added
- GPU-accelerated FM-index construction (suffix array, BWT, Occ table) via WebGPU compute
  shaders, alongside CPU construction.
- `count` / `locate` queries; bidirectional index with MEM / SMEM finding (CPU and GPU paths).
- Full 16-symbol IUPAC ambiguity alphabet with a pluggable `Alphabet` trait
  (`IupacDna` default, `ExactDna` for exact ACGT matching).
- WASM bindings for in-browser WebGPU use; index serialization (`to_bytes` / `from_bytes`).
- Community health files, CI (fmt / clippy / build / test on `--all-features`), and Dependabot.

### Changed
- Licensed under Apache-2.0.

[Unreleased]: https://github.com/sriram98v/haystackfm/commits/main
[0.1.0]: https://crates.io/crates/haystackfm/0.1.0