You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
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.
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).
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.