onehot_dna
Small fixed-length one-hot DNA encoding for fast barcode and primer matching in Rust.
This crate is meant for short sequences where all candidates have the same length, for example single-cell barcode blocks, sample tags, primer fragments, or other small DNA tokens that need fast exact or fuzzy matching.
The central type is:
It stores N DNA bases in a single u128, using four bits per base. Therefore N <= 32.
Encoding
Each DNA base is encoded as a four-bit one-hot mask:
| Base | Bits |
|---|---|
A |
0001 |
C |
0010 |
G |
0100 |
T |
1000 |
anything else, including N |
0000 |
Unknown bases are encoded as zero. They match nothing and therefore count as mismatches. This is intentional for barcode correction: an ambiguous base should not accidentally rescue a hit.
Install
Add the crate directly from GitHub:
[]
= { = "https://github.com/stela2502/onehot_dna" }
Or, for a fixed revision:
[]
= { = "https://github.com/stela2502/onehot_dna", = "<commit>" }
Then import the types you need:
use ;
Basic usage
Encode a fixed-length DNA sequence:
use OneHot;
let barcode = from_bytes?;
assert_eq!;
println!;
# Ok::
The length is part of the type. A OneHot<9> and a OneHot<16> are different types, which helps avoid mixing incompatible barcode classes by accident.
use OneHot;
let cell_block = from_bytes?;
let umi_like = from_bytes?;
// cell_block and umi_like cannot be compared directly because their lengths differ.
# Ok::
Extracting a window from a read
Use from_window when the barcode/primer segment is embedded in a larger read:
use OneHot;
let read = b"XXACGTACGTNYY";
let block = from_window?;
assert_eq!;
# Ok::
Exact and fuzzy matching
mismatches returns the barcode-style Hamming distance between two encoded sequences of the same length:
use OneHot;
let a = from_bytes?;
let b = from_bytes?;
let n = from_bytes?;
assert_eq!;
assert_eq!; // N/unknown matches nothing
assert!;
assert!;
# Ok::
Exact matching is simple bit equality:
use OneHot;
let a = from_bytes?;
let b = from_bytes?;
assert!;
# Ok::
Candidate tables with OneHotSet
OneHotSet<N> is a small candidate table for fixed-length barcodes or primers.
use ;
let candidates = from_sequences?;
let query = from_bytes?;
let hit = candidates.best_match;
assert_eq!;
# Ok::
best_match returns:
where the tuple is (candidate_index, mismatch_distance).
It returns None when:
- no candidate is within
max_mismatches - the best hit is tied between multiple candidates
This is useful for barcode correction where a non-unique correction should be rejected rather than guessed.
Example tie rejection:
use ;
let candidates = from_sequences?;
let query = from_bytes?;
// The query is one mismatch away from both candidates, so the match is ambiguous.
assert_eq!;
# Ok::
Estimating a safe correction radius
OneHotSet can report the minimum pairwise distance between all candidates:
use OneHotSet;
let candidates = from_sequences?;
let min_distance = candidates.min_pairwise_mismatches;
let radius = candidates.correction_radius;
assert_eq!;
assert_eq!;
# Ok::
The correction radius is computed as:
(min_pairwise_distance - 1) / 2
That is the conservative Hamming-style radius where two valid candidates should not claim the same observed sequence.
For real barcode whitelists you may still want to set your own max_mismatches, because empirical sequencing error and whitelist design often matter more than the theoretical global radius.
BD Rhapsody 9 bp blocks
The crate includes a convenience alias:
pub type OneHot9 = ;
This is useful for BD Rhapsody-style C1/C2/C3 barcode blocks:
use ;
let c1_table = from_sequences?;
let observed_c1 = from_bytes?;
if let Some = c1_table.best_match
# Ok::
Raw bits
If you need compact storage or precomputed constants, use bits and from_bits:
use OneHot;
let x = from_bytes?;
let packed: u128 = x.bits;
let restored = from_bits;
assert_eq!;
# Ok::
from_bits does not validate that every four-bit nibble contains exactly one legal base bit. It is intended for trusted packed values.
Error handling
Construction can fail when the sequence length does not match N, or when N > 32:
use ;
let err = from_bytes.unwrap_err;
assert_eq!;
Design notes
This crate is intentionally small:
- no allocation for individual
OneHot<N>values - compact
u128storage - const-generic fixed length
- simple mismatch counting
- unknown bases treated as non-matching
- deterministic unique-best-hit correction via
OneHotSet::best_match
It is best suited for short fixed-length DNA tokens, not for general sequence alignment.
Run tests
License
Add your license information here.