Skip to content

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 26 Sep 02:24
32e3feb

Added

  • AlphabetFns::new / from_compatible_fn / compatible / core / tag: an alphabet's
    matching semantics as data — one SymbolSet of compatible reference codes per query
    code plus the core symbol set and a tag. CArray::present_symbols.
  • LookupHit and LookupTable::MAX_VARIANTS_PER_ENTRY: a k-mer entry now exposes its
    exact interval and the intervals of its wildcard variants, and whether it is complete.
  • examples/gen_fixtures.rs writes the frozen-format fixtures under tests/fixtures/;
    version-2 fixtures (v2_*.hfm) added, including an ExactDna one.
  • The WGSL COMPAT / COMPAT_LEN tables in locate_search.wgsl and mem_find.wgsl are
    parsed out of the shader sources by a plain unit test and checked against IupacDna,
    instead of against a hand-copied Rust array.
  • serde_raw::u32_pairs blob codec for Vec<(u32, u32)>.
  • OccTable::rank_with_below(c, i) / rank_with_below_pair(c, lo, hi): rank(c, ·) and
    the class rank of every symbol below c from one block-record load per border — the
    two numbers a bidirectional extension step needs. OccTable::rank_all_pair(lo, hi):
    rank_all on both borders with both records prefetched first.
  • BidirInterval::contract_right / BidirFmIndex::contract_right: the inverse of
    extend_right (Pc → P), the mirror of contract_left run on the reverse half (select,
    LCP widening and class rank on the reverse index, forward interval recovered from the
    offset). Same cost and error behaviour as contract_left.
  • BidirInterval::contract_left / BidirFmIndex::contract_left: the inverse of
    extend_left (cP → P) without re-walking. One OccTable::select on the forward half
    gives ψ of the interval's first row, the forward LCP array widens that sub-range to the
    full interval of P (O(1) when every occurrence of P is preceded by c, O(log n) otherwise
    via block minima + a sparse table), and the existing class rank recovers the reverse
    interval. Works across sentinel-preceded occurrences and IUPAC reference codes.
    has_lcp() reports whether contraction is available.
  • LcpArray (src/lcp.rs): Kasai LCP over the SA rows stored capped at u16::MAX, with
    psv_below / nsv_below. Built during CPU construction while the full suffix array is
    resident; opt-in via FmIndexConfig::build_lcp (default false). A BidirFmIndex
    builds one per half (the forward one backs contract_left / parent_fwd, the reverse
    one contract_right), so opting in grows its resident and serialized size by ~2.7
    B/base per half. Default builds carry none: has_lcp() is false and the contraction
    / parent operations return LcpNotBuilt. BidirFmIndex::has_lcp() is true only when
    both halves carry one; use fwd().has_lcp() / rev().has_lcp() for a single direction.
  • OccTable::select(c, r): position of the r-th occurrence of c in the BWT, from the
    existing superblock/block/lane-mask layout plus a small hint array (rebuilt on load,
    not serialized).
  • Serialized indexes now start with a 4-byte format marker ("HFM" plus a version byte,
    currently 2; see Changed). Legacy blobs without it still load, with
    has_lcp() == false.
  • benches/serialize.rs and examples/serde_profile.rs measure to_bytes /
    from_bytes, the single-threaded startup cost a consumer pays before any query.
  • FmIndexError::LcpNotBuilt, PatternTooLong, InvalidContraction.
  • FwdInterval (BidirInterval::fwd()): a forward-only row range with extend_left
    (an LF step, valid on any sub-range of an interval), parent (the nearest suffix-tree
    ancestor P[..d'], d' = max(LCP[lo], LCP[hi]), widened over the LCP array) and
    locate. BidirFmIndex::extend_left_fwd / parent_fwd / locate_fwd and
    fwd() / rev() accessors; FmIndex::locate_rows(lo, hi). Together with
    contract_left these support sliding-window MEM sweeps whose ancestor MEMs are read off
    a parent walk instead of re-extended.
  • BidirFmIndex::lookup_interval(kmer) / lookup_depth(): seed a cursor for a
    lookup_depth-mer from the k-mer tables of both halves, skipping that many extensions.
  • SymbolSet: a 16-bit set of alphabet codes with EMPTY, ALL, BASES, WILDCARDS
    (codes 5..=15), NON_SENTINEL, single, below, from_codes, set algebra and
    iteration. AlphabetFns::compatible_set(q) returns the codes q matches under that
    alphabet as a SymbolSet.
  • OccTable::rank_set, rank_set_pair and rank_all: class ranks over the occurrence
    table. Every block record already holds all lanes, so counting "any symbol in this set"
    costs one block touch regardless of the set's size, and rank_all returns every symbol's
    rank at once.
  • Wildcard-aware bidirectional cursor operations, on both BidirInterval (taking the
    relevant &FmIndex half) and BidirFmIndex:
    • count_wild_right / count_wild_left: occurrences followed / preceded in the reference
      by an ambiguity code (N or a degenerate IUPAC symbol). Two occ-block touches, no text
      access, independent of how many wildcard codes exist — a cursor walk can ask at every
      step and fan out only when the answer is non-zero.
    • count_right_in / count_left_in: the same for an arbitrary SymbolSet, e.g.
      compatible_set(base).intersection(SymbolSet::WILDCARDS).
    • children_right / children_left: every child interval for all 16 codes from two
      rank_all calls, bit-identical to the per-code extend_* results; slot 0 is the
      sentinel child (occurrences at a reference end / start) and child sizes sum to the
      parent's size.
    • extend_right_compatible / extend_left_compatible: the compatible-symbol fan-out
      find_smems / find_mems use internally, now public, driven by the index's own
      alphabet; BidirFmIndex::compatible_set exposes that alphabet's match set.

Changed

  • Breaking (on-disk format). Serialized indexes are format version 3 ("HFM\x03"):
    the alphabet's matching tables are stored in full (35 bytes) instead of a tag, so an
    index built with a custom Alphabet loads back with the semantics it was built with,
    and the lookup table is stored in CSR form with wildcard variants (see Fixed). A
    built-in tag (0, 1) must match its tables and tags 2..=127 are reserved; both are
    rejected with DeserializeError. Version-2, version-1 and legacy blobs still load;
    their lookup table is converted in place when it is provably complete (pure-ACGT
    reference, or ExactDna) and rebuilt at load otherwise. FORMAT_MAGIC now reads
    "HFM\x03", FORMAT_MAGIC_V2 names the previous marker. Blobs written by this version
    cannot be read by older builds.
  • Breaking. AlphabetFns fields are private; construct with AlphabetFns::new or
    AlphabetFns::from_compatible_fn and read with compatible(q) / core() / tag().
    compatible_set remains as an alias. The fan-out order of extend_*_compatible,
    backward search and MEM extension is ascending code order (unchanged for the built-in
    alphabets, whose tables were already ascending).
  • Breaking. LookupTable::get returns Option<LookupHit> (exact slot plus variant
    intervals and a completeness flag) instead of Option<(u32, u32)>, and returns None
    for a k-mer of the wrong length rather than debug-asserting. Its build takes the
    AlphabetFns rather than a core-symbol slice. Memory on a pure-ACGT reference is about
    12 × 4^depth bytes (was 8) plus 8 bytes per stored wildcard variant.
  • Backward search and the locate resolve table intersect the query code's compatibility
    mask with the text's present symbols once per step, instead of walking a slice and
    checking each symbol's count: one loop iteration per present compatible code.
  • BidirInterval::extend_right / extend_left touch two occ block records per step
    instead of four: the LF rank and the below(c) class rank at each border now come from
    one fused load (rank_with_below_pair), with the below(c) lane mask precomputed per
    symbol at build/load time. children_right / children_left prefetch both borders
    before ranking. Results are bit-identical.
  • Breaking (on-disk format). Serialized indexes are format version 2 ("HFM\x02"):
    the occ table's checkpoints and block records, the SA samples, the LCP arrays and the
    text are written as length-prefixed little-endian byte blobs that from_bytes copies
    in bulk instead of decoding one element at a time through serde. Loading a 117 MB
    bidirectional index (10 Mbp, sa_sample_rate: 1, OneHot, no LCP) drops from ~630 ms
    to ~15 ms under the crate's release profile (~68 ms to ~10 ms at opt-level 3).
    Version-1 ("HFM\x01") and legacy blobs still load unchanged; any other "HFM" version
    is rejected with a clear DeserializeError. FORMAT_MAGIC now reads "HFM\x02" and
    FORMAT_MAGIC_V1 names the previous marker. Blobs written by this version cannot be
    read by older builds.
  • BidirFmIndex::to_bytes returns SerializeError instead of silently truncating the
    length prefix when the forward half exceeds 4 GiB.
  • Breaking. BidirInterval gains a len: u32 field (the matched pattern length,
    maintained by every extension and contraction); struct literals must supply it.
  • BidirFmIndex::build_cpu* now builds the reverse half with the configured
    lookup_depth (previously always 0) so lookup_interval can seed both halves; lookup
    memory doubles when the table is enabled.
  • Breaking. FmIndexConfig gains build_lcp: bool (default false); exhaustive
    struct literals must supply it. Set it to true only when the cursor contraction /
    parent operations are needed; CPU-built indexes then grow by ~2.7 bytes per base per
    half. GPU construction never builds the LCP.
  • BidirInterval::extend_right / extend_left compute the paired-interval offset with a
    single class rank per border instead of one scalar rank per smaller symbol. Results are
    unchanged; extending by a high IUPAC code no longer costs up to 30 extra rank calls.

Fixed

  • With IupacDna and lookup_depth > 0, count / locate / locate_positions* missed
    reference occurrences that match the query only through an ambiguity code inside the
    last lookup_depth query positions (reference ANG, query ACG, depth 3 → 0 instead
    of 1): the depth-k table was built by exact A/C/G/T steps and seeded the search as if
    those steps had run. Each entry now also carries the intervals of the reference
    stretches that match its k-mer through ambiguity codes (at most
    MAX_VARIANTS_PER_ENTRY; an entry past the cap is marked incomplete and searched
    fully). BidirFmIndex::lookup_interval keeps its literal semantics: the exact interval
    is always slot 0. Default lookup_depth is 0, so indexes built without the table were
    unaffected.
  • tests/bidir_correctness.rs no longer zips a filtered fan-out with the compatible set
    (labels would shift after an empty child).
  • Stale comments: occ block-record layout, reverse-text code range, suffix-array sentinel
    requirement, SMEM query code range.
  • benches/query.rs compiles again (its FmIndexConfig literals predated the
    lookup_depth / build_threads / occ_encoding fields) and gains a wild_counts group
    comparing count_wild_right against the 11-code extend_right fan-out.