use crate::{
Result, StrobeError,
constants::DEFAULT_PRIME_NUMBER,
hashes::{KmerHasher, NtHash64, check_hash_count},
util::{last_start_index, roundup64},
};
#[derive(Debug, Clone)]
pub struct RandStrobes {
n: u8, _k: usize, w_min: usize, w_max: usize,
hashes: Vec<u64>,
idx: usize, end_idx: Option<usize>,
end_hash: usize,
idx2: usize, idx3: usize,
prime: u64, shrink: bool,
h1: u64, h2: u64, h3: u64, }
impl RandStrobes {
pub fn new(seq: &[u8], n: u8, k: usize, w_min: usize, w_max: usize) -> Result<Self> {
Self::with_hasher(seq, n, k, w_min, w_max, &NtHash64)
}
pub fn with_hasher<H>(
seq: &[u8],
n: u8,
k: usize,
w_min: usize,
w_max: usize,
hasher: &H,
) -> Result<Self>
where
H: KmerHasher,
{
validate_params!(seq, n, k, w_min, w_max);
let hashes = hasher.hash_all(seq, k)?;
check_hash_count(&hashes, seq.len(), k)?;
let end_hash = seq.len() - k; let end_idx = seq.len().checked_sub(k + (n as usize - 1) * k);
Ok(Self {
n,
_k: k,
w_min,
w_max,
hashes,
idx: 0,
end_idx,
end_hash,
idx2: 0,
idx3: 0,
prime: DEFAULT_PRIME_NUMBER,
shrink: true,
h1: 0,
h2: 0,
h3: 0,
})
}
pub fn set_prime(&mut self, q: u64) -> Result<()> {
if q < 256 {
return Err(StrobeError::PrimeNumberTooSmall);
}
self.prime = roundup64(q).wrapping_sub(1);
Ok(())
}
pub fn set_window_shrink(&mut self, s: bool) {
self.shrink = s;
}
pub fn index(&self) -> Option<usize> {
self.idx.checked_sub(1)
}
pub fn indexes(&self) -> [usize; 3] {
[self.index().unwrap_or(0), self.idx2, self.idx3]
}
#[inline(always)]
fn choose_min(&self, base: u64, start: usize, end: usize) -> Option<(usize, u64)> {
if start > end {
return None;
}
let window = &self.hashes[start..=end];
let prime = self.prime;
if prime <= u32::MAX as u64 && window.len() <= u32::MAX as usize {
let best = packed_argmin(window, base, prime);
return Some((start + (best & 0xFFFF_FFFF) as usize, best >> 32));
}
let mut best_off = 0usize;
let mut best_val = u64::MAX;
for (off, &h) in window.iter().enumerate() {
let cand = base.wrapping_add(h) & prime;
if cand < best_val {
best_val = cand;
best_off = off;
}
}
Some((start + best_off, best_val))
}
#[inline]
fn remaining(&self) -> usize {
let (Some(end_idx), Some(limit)) = (
self.end_idx,
last_start_index(self.n, self.shrink, self.w_min, self.w_max, self.end_hash),
) else {
return 0;
};
(limit.min(end_idx) + 1).saturating_sub(self.idx)
}
fn next_order2(&mut self) -> Option<u64> {
if self.idx > self.end_idx? {
return None;
}
let w_start = self.idx + self.w_min;
let mut w_end = self.idx + self.w_max;
if w_end > self.end_hash {
if !self.shrink {
return None;
}
w_end = self.end_hash;
}
self.h1 = self.hashes[self.idx];
let (pos2, _) = self.choose_min(self.h1, w_start, w_end)?;
self.idx2 = pos2;
self.h2 = (self.h1 >> 1) + self.hashes[pos2] / 3;
self.idx += 1;
Some(self.h2)
}
fn next_order3(&mut self) -> Option<u64> {
if self.idx > self.end_idx? {
return None;
}
let w1_start = self.idx + self.w_min;
let w1_end = self.idx + self.w_max;
let w2_start = self.idx + self.w_max + self.w_min;
let mut w2_end = self.idx + (self.w_max << 1);
if w2_start > self.end_hash {
return None;
}
if w2_end > self.end_hash {
if !self.shrink {
return None;
}
w2_end = self.end_hash;
}
self.h1 = self.hashes[self.idx];
let (pos2, _) = self.choose_min(self.h1, w1_start, w1_end)?;
self.idx2 = pos2;
self.h2 = self.h1 / 3 + (self.hashes[pos2] >> 2);
let (pos3, _) = self.choose_min(self.h2, w2_start, w2_end)?;
self.idx3 = pos3;
self.h3 = self.h2 + self.hashes[pos3] / 5;
self.idx += 1;
Some(self.h3)
}
}
#[cfg(target_feature = "avx512f")]
#[inline(always)]
fn packed_argmin(window: &[u64], base: u64, prime: u64) -> u64 {
let mut best = u64::MAX;
for (off, &h) in window.iter().enumerate() {
best = best.min(((base.wrapping_add(h) & prime) << 32) | off as u64);
}
best
}
#[cfg(not(target_feature = "avx512f"))]
#[inline(always)]
fn packed_argmin(window: &[u64], base: u64, prime: u64) -> u64 {
#[allow(clippy::chunks_exact_to_as_chunks)]
let mut chunks = window.chunks_exact(4);
let mut acc = [u64::MAX; 4];
let mut off = 0u64;
for c in &mut chunks {
for (j, &h) in c.iter().enumerate() {
acc[j] = acc[j].min(((base.wrapping_add(h) & prime) << 32) | (off + j as u64));
}
off += 4;
}
for (j, &h) in chunks.remainder().iter().enumerate() {
acc[0] = acc[0].min(((base.wrapping_add(h) & prime) << 32) | (off + j as u64));
}
acc[0].min(acc[1]).min(acc[2]).min(acc[3])
}
impl Iterator for RandStrobes {
type Item = u64;
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let n = self.remaining();
(n, Some(n))
}
fn next(&mut self) -> Option<Self::Item> {
match self.n {
2 => self.next_order2(),
3 => self.next_order3(),
_ => None, }
}
}
impl ExactSizeIterator for RandStrobes {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn order2_basic() {
let mut rs = RandStrobes::new("ACGTACGTACGT".as_bytes(), 2, 3, 1, 4).unwrap();
assert!(rs.next().is_some());
}
#[test]
fn order3_basic() {
let seq = "ACGTACGTACGTACGTACGTACGT";
let rs = RandStrobes::new(seq.as_bytes(), 3, 3, 1, 4).unwrap();
assert_eq!(rs.take(10).count(), 10);
}
}