Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Count & Locate

The two core exact-match queries. Both operate on IUPAC-encoded byte slices (DnaSequence::as_slice()).

count

let n = index.count(query.as_slice()); // u32 — number of occurrences

Runs a backward search and returns the final SA-interval size — O(|P|), independent of how many times the pattern occurs.

locate

let hits = index.locate(query.as_slice()); // Vec<(SeqId, u32)>

Returns every occurrence as (seq_id, offset_within_seq), where seq_id is a SeqId — the reference’s 0-based position in build order, not its FASTA name — and offset is a u32 within that reference. Cost is O(|P| + occ·k), where occ is the number of hits and k is sa_sample_rate — each hit LF-walks at most k steps to the nearest SA sample.

No header string is allocated per hit. Since locate cost scales with occurrence count, that matters most exactly where it hurts: a seed conserved across many references.

Sequence ids and headers

SeqId and the FASTA header are separate things; convert between them explicitly. Both directions are O(1).

index.seq_header(id)?;         // Option<&str>   — id -> header
index.seq_id("chr1");          // Option<SeqId>  — header -> id
index.seq_headers();           // &[String]      — all headers, indexed by SeqId::index()

To label results, resolve per hit:

for (id, offset) in index.locate(query.as_slice()) {
    println!("{}:{offset}", index.seq_header(id).unwrap());
}

Or, in a hot loop, build your own table once and index it by SeqId::index():

// Done once at load time, not per occurrence.
let labels: Vec<MyLabel> = index.seq_headers().iter().map(MyLabel::parse).collect();

for (id, offset) in index.locate(query.as_slice()) {
    let label = &labels[id.index()];   // plain Vec indexing — no hashing
}

Headers must be unique: a duplicate fails the build with FmIndexError::DuplicateHeader, which is what makes seq_id an exact inverse of seq_header. Sequences built without an explicit header are named seq_{i} in build order.

Ids survive serialization — see Serialization.

Index metadata

index.text_len();        // u32 — total length of the concatenated text
index.num_sequences();   // u32 — number of indexed sequences

GPU batch locate

When you have many patterns, resolve them together on the GPU. It is IUPAC-aware and returns results per query:

use haystackfm::gpu::locate::locate_batch_gpu;

let ctx = haystackfm::gpu::GpuContext::new().await?;
let queries: Vec<&[u8]> = vec![q1.as_slice(), q2.as_slice()];
let hits = locate_batch_gpu(&ctx, &index, &queries).await?;
// hits[i] = Vec<(seq_id, offset_within_seq)> for queries[i]

Requires the gpu feature. GPU results carry the same occurrences as the CPU locate output (order is not guaranteed), reported with the same (SeqId, offset) shape — so the same label-resolution code works for both. FmIndex::locate_gpu wraps this for a whole batch. For when the GPU path is worthwhile, see GPU Acceleration.