Status: Spec for review — 2026-05-27. The first sub-stage of Phase B4 (wire the consumers onto the persisted multi-contig index), under the contract thesis in docs/OPEN_PROBLEMS.md. Follows B3a/B3b/B3c (persisted zero-copy index + rosalind index/locate, all merged — PRs #15/#16/#17).
Read the reference sequence directly from a persisted index — no separate
--referenceFASTA, no full-reference allocation — decoding only the small windows a consumer asks for.
B3b stored the 2-bit forward reference into the index (the Reference2bit section) precisely so consumers wouldn't need a separate FASTA — but gave it no read path. B4a adds that read path: a borrowed, zero-copy ReferenceView<'a> that decodes bases on demand from the memory-mapped section. This is the shared foundation the rest of B4 needs — B4b (the aligner's banded-DP refinement window) and B4c (variants' ref_base / pileup) both read reference bases, and after B4a they read them from the .idx alone.
In:
- A borrowed
ReferenceView<'a>over theReference2bitsection:len(),base_at(global) -> u8(on-demand ASCII decode),decode_window(start, end, &mut Vec<u8>)(bounded buffer fill). ReferenceIndex::reference_view() -> Result<ReferenceView<'_>, IndexIoError>— parses + validates the section (extent, 8-alignment, little-endian host) and yields the borrowed view.- The equivalence gate: decoded bases (
base_atover the whole reference,decode_windowover ranges) are byte-identical to the originalindex.reference(), including anN-bearing multi-contig reference.
Out (deferred, by design):
- Wiring
ReferenceViewinto the aligner's DP window → B4b. - Wiring
ReferenceViewintovariants/pileupref_base→ B4c. - Any contig-coordinate API (
ReferenceViewis global-coordinate, matching the concatenated 2-bit reference;(contig, pos)resolution stays withContigSet, used by the consumers in B4b/B4c).
The B3b serializer (genomics/index/io.rs) writes, into the 8-aligned Reference2bit section:
len:u64, data_words:u64, amb_words:u64, data:[u64; data_words], amb:[u64; amb_words]
data is the 2-bit packing of CompressedDNA (32 bases per u64, A/C/G/T = 0/1/2/3); amb is the ambiguity bitmap (1 bit per base; a set bit marks N). This is exactly CompressedDNA::compress(index.reference())'s words() + ambiguity().bits().
pub struct ReferenceView<'a> {
len: usize,
data: &'a [u64], // 2-bit packed, 32 bases/word
amb: &'a [u64], // ambiguity bits, 1/base (set = N)
}- Construction (
pub(crate) fn new(bytes, sections), called byReferenceIndex::reference_view): reject big-endian hosts; locate theReference2bitsection (reusesection_bytes); readlen/data_words/amb_words(reuseread_u64); slicedata/ambvia the checkedas_u64_slice(emptyalign_toprefix/suffix — the same zero-copy discipline asFmIndexView); validate the slices coverlenbases (data.len() * 32 >= lenandamb.len() * 64 >= len) sobase_atcannot index out of bounds. Errors (not panics) on any mismatch. base_at(global) -> u8decodes one base, mirroringCompressedDNA::base_at: ifamb[global / 64] >> (global % 64) & 1 == 1→b'N'; else((data[global / 32] >> ((global % 32) * 2)) & 0b11)→A/C/G/T.debug_assert!(global < len).decode_window(start, end, out: &mut Vec<u8>)clearsoutand pushesbase_at(i)fori in start..end.min(len)(endis clamped tolen, so an over-range request never panics;start <= endis the caller's contract). Bounded by the window size (the caller decodes only what it needs — e.g. the aligner's band + read length — never the whole genome).- The 2-bit decode is a 4-line replication of
CompressedDNA::base_at, annotated as such; the equivalence gate (decoded == original) is the guarantee against drift. (Extracting a sharedpub(crate)decode helper acrossCompressedDNA/FmIndexView/ReferenceViewis deferred — out of B4a's scope; the gate makes replication safe.)
Reconstructing the full reference into a Vec<u8> at open would be simpler but O(reference) RAM — defeating the persisted index's bounded-query guarantee (the whole point of B3b). ReferenceView instead holds only two borrowed slices + a length and decodes on demand, so reference access stays bounded (a consumer pays only for the windows it reads). This matches the contract thesis and the FmIndexView precedent.
- Equivalence: for a built
GenomeIndex(multi-contig,N-bearing),reference_view().base_at(i)equalsindex.reference()[i]for everyi, anddecode_window(s, e, …)equals&index.reference()[s..e]over several ranges (including ranges spanning contig boundaries in the concatenated coordinate and an all-Nstretch). - No rebuild / self-contained:
reference_view()reads only the mmap (neversais_u32/BlockedFMIndex::build); decoding works after the source FASTA is gone (the bases come from the.idx). - Bounded:
ReferenceViewis a small borrow (slices + scalar), not an owned copy;size_of::<ReferenceView>()is independent of genome size. - Integrity: a
Reference2bitsection too small forlenbases is rejected atreference_view()(Err, not a later panic).
- A unit test in
genomics/index/view.rs: build → serialize → open →reference_view(); assertbase_atover the whole reference == the original,decode_windowover ranges == the slices, including anN-bearing multi-contig fixture (reuse the B3b/B3c fixture style). Assertsize_of::<ReferenceView>()is small. - Round-trip robustness is already covered by B3b's determinism/round-trip; B4a adds the decode direction.
- Decode divergence from
CompressedDNA. Mitigation: the decode is a verbatim mirror ofCompressedDNA::base_at; the equivalence gate (over anN-bearing reference) catches any drift loudly. - Out-of-bounds on a corrupt/short section. Mitigation:
newvalidatesdata/ambcoverlenbases and returnsErr;base_atis only reachable after that validation.decode_windowclampsendtolen, so an over-range request never panics. amb_wordsover-allocation quirk. B3b storesamb_words = ceil(len/32)(theCompressedDNAallocation), larger than theceil(len/64)strictly needed;new'samb.len() * 64 >= lencheck accommodates this, andbase_atindexesamb[i/64]which is in-bounds fori < len.
- On-demand zero-copy decode (§5) — bounded reference access is the whole point; full reconstruction is rejected.
- Global-coordinate view (§2) —
ReferenceViewmatches the concatenated 2-bit reference;(contig, pos)mapping stays inContigSet(the consumers compose them in B4b/B4c). - Replicate the 2-bit decode, gate against drift (§4) — a shared decode helper across
CompressedDNA/FmIndexView/ReferenceViewis deferred (not B4a's job); the equivalence gate makes the 4-line replication safe. - Lives in
genomics/index/view.rsalongsideFmIndexView/GenomeIndexView, reusing the existingsection_bytes/as_u64_slice/read_u64helpers; re-exported viagenomics::index→genomics.