#![allow(unsafe_op_in_unsafe_fn)]
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
#[cfg(target_arch = "aarch64")]
use std::arch::aarch64::*;
#[allow(dead_code)]
pub mod encoding {
pub const GAP: u8 = 0x0;
pub const A: u8 = 0x1;
pub const C: u8 = 0x2;
pub const M: u8 = 0x3;
pub const T: u8 = 0x4;
pub const W: u8 = 0x5;
pub const Y: u8 = 0x6;
pub const H: u8 = 0x7;
pub const G: u8 = 0x8;
pub const R: u8 = 0x9;
pub const S: u8 = 0xA;
pub const V: u8 = 0xB;
pub const K: u8 = 0xC;
pub const D: u8 = 0xD;
pub const B: u8 = 0xE;
pub const N: u8 = 0xF;
}
#[repr(align(64))]
struct AlignedEncodeLUT([u8; 256]);
static ENCODE_LUT: AlignedEncodeLUT = {
let mut table = [0u8; 256];
table[b'A' as usize] = 0x1;
table[b'a' as usize] = 0x1;
table[b'C' as usize] = 0x2;
table[b'c' as usize] = 0x2;
table[b'T' as usize] = 0x4;
table[b't' as usize] = 0x4;
table[b'U' as usize] = 0x4;
table[b'u' as usize] = 0x4; table[b'G' as usize] = 0x8;
table[b'g' as usize] = 0x8;
table[b'M' as usize] = 0x3;
table[b'm' as usize] = 0x3; table[b'W' as usize] = 0x5;
table[b'w' as usize] = 0x5; table[b'Y' as usize] = 0x6;
table[b'y' as usize] = 0x6; table[b'R' as usize] = 0x9;
table[b'r' as usize] = 0x9; table[b'S' as usize] = 0xA;
table[b's' as usize] = 0xA; table[b'K' as usize] = 0xC;
table[b'k' as usize] = 0xC; table[b'H' as usize] = 0x7;
table[b'h' as usize] = 0x7; table[b'V' as usize] = 0xB;
table[b'v' as usize] = 0xB; table[b'D' as usize] = 0xD;
table[b'd' as usize] = 0xD; table[b'B' as usize] = 0xE;
table[b'b' as usize] = 0xE; table[b'N' as usize] = 0xF;
table[b'n' as usize] = 0xF;
table[b'-' as usize] = 0x0;
table[b'.' as usize] = 0x0;
AlignedEncodeLUT(table)
};
#[repr(align(16))]
struct AlignedDecodeLUT([u8; 16]);
static DECODE_LUT: AlignedDecodeLUT = AlignedDecodeLUT([
b'-', b'A', b'C', b'M', b'T', b'W', b'Y', b'H', b'G', b'R', b'S', b'V', b'K', b'D', b'B', b'N',
]);
#[inline(always)]
fn char_to_4bit_fast(c: u8) -> u8 {
ENCODE_LUT.0[c as usize]
}
#[inline(always)]
fn fourbit_to_char_fast(bits: u8) -> u8 {
DECODE_LUT.0[(bits & 0xF) as usize]
}
#[cfg(target_arch = "x86_64")]
#[inline(always)]
unsafe fn prefetch_read(ptr: *const u8) {
#[cfg(target_feature = "sse")]
{
use std::arch::x86_64::_mm_prefetch;
_mm_prefetch::<{ std::arch::x86_64::_MM_HINT_T0 }>(ptr as *const i8);
}
}
#[cfg(target_arch = "aarch64")]
#[inline(always)]
unsafe fn prefetch_read(ptr: *const u8) {
std::arch::asm!(
"prfm pldl1keep, [{ptr}]",
ptr = in(reg) ptr,
options(nostack, preserves_flags)
);
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
#[inline(always)]
fn prefetch_read(_ptr: *const u8) {
}
const PREFETCH_DISTANCE: usize = 128;
pub fn encode_dna_prefer_simd(sequence: &[u8]) -> Vec<u8> {
let len = sequence.len();
let padded_len = (len + 1) & !1;
let mut output = vec![0u8; padded_len / 2];
if len >= 32 {
let used_simd = encode_with_simd_if_available_direct(sequence, &mut output);
if used_simd {
return output;
}
}
encode_scalar_optimized(sequence, &mut output);
output
}
#[inline]
#[allow(dead_code)]
fn encode_with_simd_if_available(padded: &[u8], output: &mut [u8]) -> bool {
#[cfg(target_arch = "x86_64")]
{
if is_x86_feature_detected!("ssse3") {
unsafe { encode_x86_ssse3(padded, output) };
return true;
}
}
#[cfg(target_arch = "aarch64")]
{
unsafe { encode_arm_neon(padded, output) };
return true;
}
#[allow(unreachable_code)]
false
}
#[inline]
fn encode_with_simd_if_available_direct(sequence: &[u8], output: &mut [u8]) -> bool {
#[cfg(target_arch = "x86_64")]
{
if is_x86_feature_detected!("ssse3") {
unsafe { encode_x86_ssse3_direct(sequence, output) };
return true;
}
}
#[cfg(target_arch = "aarch64")]
{
unsafe { encode_arm_neon_direct(sequence, output) };
return true;
}
#[allow(unreachable_code)]
false
}
#[inline]
fn encode_scalar_optimized(sequence: &[u8], output: &mut [u8]) {
let len = sequence.len();
let mut i = 0;
let mut out_idx = 0;
while i + 4 <= len {
let b0 = char_to_4bit_fast(sequence[i]);
let b1 = char_to_4bit_fast(sequence[i + 1]);
let b2 = char_to_4bit_fast(sequence[i + 2]);
let b3 = char_to_4bit_fast(sequence[i + 3]);
output[out_idx] = (b0 << 4) | b1;
output[out_idx + 1] = (b2 << 4) | b3;
i += 4;
out_idx += 2;
}
if i + 2 <= len {
let high = char_to_4bit_fast(sequence[i]);
let low = char_to_4bit_fast(sequence[i + 1]);
output[out_idx] = (high << 4) | low;
i += 2;
out_idx += 1;
}
if i < len {
let high = char_to_4bit_fast(sequence[i]);
output[out_idx] = high << 4; }
}
pub fn decode_dna_prefer_simd(encoded: &[u8], length: usize) -> Vec<u8> {
let mut output = vec![0u8; length];
let used_simd = decode_with_simd_if_available(encoded, &mut output, length);
if !used_simd {
decode_scalar(encoded, &mut output, length);
}
output
}
#[inline]
fn decode_with_simd_if_available(encoded: &[u8], output: &mut [u8], length: usize) -> bool {
#[cfg(target_arch = "x86_64")]
{
if is_x86_feature_detected!("ssse3") {
unsafe { decode_x86_ssse3(encoded, output, length) };
return true;
}
}
#[cfg(target_arch = "aarch64")]
{
unsafe { decode_arm_neon(encoded, output, length) };
return true;
}
#[allow(unreachable_code)]
false
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "ssse3")]
unsafe fn encode_x86_ssse3(sequence: &[u8], output: &mut [u8]) {
let len = sequence.len();
let simd32_len = (len / 32) * 32;
let mut out_idx = 0;
let mut in_idx = 0;
while in_idx < simd32_len {
let chunk0 = _mm_loadu_si128(sequence.as_ptr().add(in_idx) as *const __m128i);
let chunk1 = _mm_loadu_si128(sequence.as_ptr().add(in_idx + 16) as *const __m128i);
let encoded0 = encode_16_bytes_simd_x86(chunk0);
let encoded1 = encode_16_bytes_simd_x86(chunk1);
let packed0 = pack_16_to_8_bytes_simd_x86(encoded0);
let packed1 = pack_16_to_8_bytes_simd_x86(encoded1);
_mm_storel_epi64(output.as_mut_ptr().add(out_idx) as *mut __m128i, packed0);
_mm_storel_epi64(
output.as_mut_ptr().add(out_idx + 8) as *mut __m128i,
packed1,
);
in_idx += 32;
out_idx += 16;
}
if in_idx + 16 <= len {
let chunk = _mm_loadu_si128(sequence.as_ptr().add(in_idx) as *const __m128i);
let encoded = encode_16_bytes_simd_x86(chunk);
let packed = pack_16_to_8_bytes_simd_x86(encoded);
_mm_storel_epi64(output.as_mut_ptr().add(out_idx) as *mut __m128i, packed);
in_idx += 16;
out_idx += 8;
}
if in_idx < len {
encode_scalar(&sequence[in_idx..], &mut output[out_idx..]);
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "ssse3")]
unsafe fn encode_x86_ssse3_direct(sequence: &[u8], output: &mut [u8]) {
let len = sequence.len();
let simd32_len = (len / 32) * 32;
let mut out_idx = 0;
let mut in_idx = 0;
while in_idx < simd32_len {
if in_idx + PREFETCH_DISTANCE < len {
prefetch_read(sequence.as_ptr().add(in_idx + PREFETCH_DISTANCE));
}
let chunk0 = _mm_loadu_si128(sequence.as_ptr().add(in_idx) as *const __m128i);
let chunk1 = _mm_loadu_si128(sequence.as_ptr().add(in_idx + 16) as *const __m128i);
let encoded0 = encode_16_bytes_simd_x86_fast(chunk0);
let encoded1 = encode_16_bytes_simd_x86_fast(chunk1);
let packed0 = pack_16_to_8_bytes_simd_x86(encoded0);
let packed1 = pack_16_to_8_bytes_simd_x86(encoded1);
_mm_storel_epi64(output.as_mut_ptr().add(out_idx) as *mut __m128i, packed0);
_mm_storel_epi64(
output.as_mut_ptr().add(out_idx + 8) as *mut __m128i,
packed1,
);
in_idx += 32;
out_idx += 16;
}
if in_idx + 16 <= len {
let chunk = _mm_loadu_si128(sequence.as_ptr().add(in_idx) as *const __m128i);
let encoded = encode_16_bytes_simd_x86_fast(chunk);
let packed = pack_16_to_8_bytes_simd_x86(encoded);
_mm_storel_epi64(output.as_mut_ptr().add(out_idx) as *mut __m128i, packed);
in_idx += 16;
out_idx += 8;
}
if in_idx < len {
encode_scalar_optimized(&sequence[in_idx..], &mut output[out_idx..]);
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "ssse3")]
#[inline]
unsafe fn encode_16_bytes_simd_x86_fast(input: __m128i) -> __m128i {
let mut bytes: [u8; 16] = std::mem::transmute(input);
for b in bytes.iter_mut() {
*b = char_to_4bit_fast(*b);
}
_mm_loadu_si128(bytes.as_ptr() as *const __m128i)
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "ssse3")]
#[inline]
unsafe fn encode_16_bytes_simd_x86(input: __m128i) -> __m128i {
let mut bytes: [u8; 16] = std::mem::transmute(input);
for b in bytes.iter_mut() {
*b = char_to_4bit(*b);
}
_mm_loadu_si128(bytes.as_ptr() as *const __m128i)
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "ssse3")]
#[inline]
unsafe fn pack_16_to_8_bytes_simd_x86(encoded: __m128i) -> __m128i {
let shuffle_evens = _mm_setr_epi8(0, 2, 4, 6, 8, 10, 12, 14, -1, -1, -1, -1, -1, -1, -1, -1);
let shuffle_odds = _mm_setr_epi8(1, 3, 5, 7, 9, 11, 13, 15, -1, -1, -1, -1, -1, -1, -1, -1);
let evens = _mm_shuffle_epi8(encoded, shuffle_evens);
let odds = _mm_shuffle_epi8(encoded, shuffle_odds);
let evens_shifted = _mm_slli_epi16(evens, 4);
let mask_upper = _mm_set1_epi8(0xF0_u8 as i8);
let mask_lower = _mm_set1_epi8(0x0F);
let evens_masked = _mm_and_si128(evens_shifted, mask_upper);
let odds_masked = _mm_and_si128(odds, mask_lower);
_mm_or_si128(evens_masked, odds_masked)
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "ssse3")]
unsafe fn decode_x86_ssse3(encoded: &[u8], output: &mut [u8], length: usize) {
let lookup = _mm_setr_epi8(
b'-' as i8, b'A' as i8, b'C' as i8, b'M' as i8, b'T' as i8, b'W' as i8, b'Y' as i8,
b'H' as i8, b'G' as i8, b'R' as i8, b'S' as i8, b'V' as i8, b'K' as i8, b'D' as i8,
b'B' as i8, b'N' as i8,
);
let mut out_idx = 0;
let mut enc_idx = 0;
for chunk in encoded.chunks_exact(8) {
if out_idx + 16 > length {
break;
}
let unpacked = unpack_8_to_16_bytes(chunk);
let values = _mm_loadu_si128(unpacked.as_ptr() as *const __m128i);
let decoded = _mm_shuffle_epi8(lookup, values);
_mm_storeu_si128(output[out_idx..].as_mut_ptr() as *mut __m128i, decoded);
out_idx += 16;
enc_idx += 8;
}
if out_idx < length {
decode_scalar(
&encoded[enc_idx..],
&mut output[out_idx..],
length - out_idx,
);
}
}
fn unpack_8_to_16_bytes(packed: &[u8]) -> [u8; 16] {
let mut result = [0u8; 16];
for (i, &byte) in packed.iter().enumerate().take(8) {
let base = i * 2;
result[base] = (byte >> 4) & 0xF;
result[base + 1] = byte & 0xF;
}
result
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
#[allow(dead_code)]
unsafe fn encode_arm_neon(sequence: &[u8], output: &mut [u8]) {
let len = sequence.len();
let simd32_len = (len / 32) * 32;
let mut out_idx = 0;
let mut in_idx = 0;
while in_idx < simd32_len {
let chunk0 = vld1q_u8(sequence.as_ptr().add(in_idx));
let chunk1 = vld1q_u8(sequence.as_ptr().add(in_idx + 16));
let encoded0 = encode_16_bytes_simd_neon(chunk0);
let encoded1 = encode_16_bytes_simd_neon(chunk1);
let packed0 = pack_16_to_8_bytes_simd_neon(encoded0);
let packed1 = pack_16_to_8_bytes_simd_neon(encoded1);
vst1_u8(output.as_mut_ptr().add(out_idx), packed0);
vst1_u8(output.as_mut_ptr().add(out_idx + 8), packed1);
in_idx += 32;
out_idx += 16;
}
if in_idx + 16 <= len {
let chunk = vld1q_u8(sequence.as_ptr().add(in_idx));
let encoded = encode_16_bytes_simd_neon(chunk);
let packed = pack_16_to_8_bytes_simd_neon(encoded);
vst1_u8(output.as_mut_ptr().add(out_idx), packed);
in_idx += 16;
out_idx += 8;
}
if in_idx < len {
encode_scalar(&sequence[in_idx..], &mut output[out_idx..]);
}
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
unsafe fn encode_arm_neon_direct(sequence: &[u8], output: &mut [u8]) {
let len = sequence.len();
let simd32_len = (len / 32) * 32;
let mut out_idx = 0;
let mut in_idx = 0;
while in_idx < simd32_len {
if in_idx + PREFETCH_DISTANCE < len {
prefetch_read(sequence.as_ptr().add(in_idx + PREFETCH_DISTANCE));
}
let chunk0 = vld1q_u8(sequence.as_ptr().add(in_idx));
let chunk1 = vld1q_u8(sequence.as_ptr().add(in_idx + 16));
let encoded0 = encode_16_bytes_simd_neon_fast(chunk0);
let encoded1 = encode_16_bytes_simd_neon_fast(chunk1);
let packed0 = pack_16_to_8_bytes_simd_neon(encoded0);
let packed1 = pack_16_to_8_bytes_simd_neon(encoded1);
vst1_u8(output.as_mut_ptr().add(out_idx), packed0);
vst1_u8(output.as_mut_ptr().add(out_idx + 8), packed1);
in_idx += 32;
out_idx += 16;
}
if in_idx + 16 <= len {
let chunk = vld1q_u8(sequence.as_ptr().add(in_idx));
let encoded = encode_16_bytes_simd_neon_fast(chunk);
let packed = pack_16_to_8_bytes_simd_neon(encoded);
vst1_u8(output.as_mut_ptr().add(out_idx), packed);
in_idx += 16;
out_idx += 8;
}
if in_idx < len {
encode_scalar_optimized(&sequence[in_idx..], &mut output[out_idx..]);
}
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
#[inline]
unsafe fn encode_16_bytes_simd_neon_fast(input: uint8x16_t) -> uint8x16_t {
let mut bytes = [0u8; 16];
vst1q_u8(bytes.as_mut_ptr(), input);
for b in bytes.iter_mut() {
*b = char_to_4bit_fast(*b);
}
vld1q_u8(bytes.as_ptr())
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
#[inline]
#[allow(dead_code)]
unsafe fn encode_16_bytes_simd_neon(input: uint8x16_t) -> uint8x16_t {
let mut bytes = [0u8; 16];
vst1q_u8(bytes.as_mut_ptr(), input);
for b in bytes.iter_mut() {
*b = char_to_4bit(*b);
}
vld1q_u8(bytes.as_ptr())
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
#[inline]
unsafe fn pack_16_to_8_bytes_simd_neon(encoded: uint8x16_t) -> uint8x8_t {
let shuffle_evens: [u8; 8] = [0, 2, 4, 6, 8, 10, 12, 14];
let shuffle_odds: [u8; 8] = [1, 3, 5, 7, 9, 11, 13, 15];
let evens_idx = vld1_u8(shuffle_evens.as_ptr());
let odds_idx = vld1_u8(shuffle_odds.as_ptr());
let evens = vqtbl1_u8(encoded, evens_idx);
let odds = vqtbl1_u8(encoded, odds_idx);
let evens_shifted = vshl_n_u8::<4>(evens);
vorr_u8(evens_shifted, odds)
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
unsafe fn decode_arm_neon(encoded: &[u8], output: &mut [u8], length: usize) {
let lookup_data: [u8; 16] = [
b'-', b'A', b'C', b'M', b'T', b'W', b'Y', b'H', b'G', b'R', b'S', b'V', b'K', b'D', b'B',
b'N',
];
let lookup = vld1q_u8(lookup_data.as_ptr());
let mut out_idx = 0;
let mut enc_idx = 0;
for chunk in encoded.chunks_exact(8) {
if out_idx + 16 > length {
break;
}
let unpacked = unpack_8_to_16_bytes(chunk);
let values = vld1q_u8(unpacked.as_ptr());
let decoded = vqtbl1q_u8(lookup, values);
vst1q_u8(output[out_idx..].as_mut_ptr(), decoded);
out_idx += 16;
enc_idx += 8;
}
if out_idx < length {
decode_scalar(
&encoded[enc_idx..],
&mut output[out_idx..],
length - out_idx,
);
}
}
#[allow(dead_code)]
pub fn encode_scalar(sequence: &[u8], output: &mut [u8]) {
for (i, chunk) in sequence.chunks_exact(2).enumerate() {
let packed = (char_to_4bit(chunk[0]) << 4) | char_to_4bit(chunk[1]);
output[i] = packed;
}
}
pub fn decode_scalar(encoded: &[u8], output: &mut [u8], length: usize) {
let mut out_idx = 0;
for &byte in encoded {
if out_idx >= length {
break;
}
output[out_idx] = fourbit_to_char_fast((byte >> 4) & 0xF);
out_idx += 1;
if out_idx >= length {
break;
}
output[out_idx] = fourbit_to_char_fast(byte & 0xF);
out_idx += 1;
}
}
#[inline]
#[allow(dead_code)]
pub fn decode_scalar_optimized(encoded: &[u8], output: &mut [u8], length: usize) {
let mut out_idx = 0;
let mut enc_idx = 0;
let enc_len = encoded.len();
while enc_idx + 2 <= enc_len && out_idx + 4 <= length {
let byte0 = encoded[enc_idx];
let byte1 = encoded[enc_idx + 1];
output[out_idx] = fourbit_to_char_fast((byte0 >> 4) & 0xF);
output[out_idx + 1] = fourbit_to_char_fast(byte0 & 0xF);
output[out_idx + 2] = fourbit_to_char_fast((byte1 >> 4) & 0xF);
output[out_idx + 3] = fourbit_to_char_fast(byte1 & 0xF);
enc_idx += 2;
out_idx += 4;
}
while enc_idx < enc_len && out_idx < length {
let byte = encoded[enc_idx];
output[out_idx] = fourbit_to_char_fast((byte >> 4) & 0xF);
out_idx += 1;
if out_idx < length {
output[out_idx] = fourbit_to_char_fast(byte & 0xF);
out_idx += 1;
}
enc_idx += 1;
}
}
#[inline]
#[allow(dead_code)]
pub fn char_to_4bit(c: u8) -> u8 {
match c {
b'A' | b'a' => encoding::A,
b'C' | b'c' => encoding::C,
b'G' | b'g' => encoding::G,
b'T' | b't' | b'U' | b'u' => encoding::T, b'R' | b'r' => encoding::R,
b'Y' | b'y' => encoding::Y,
b'S' | b's' => encoding::S,
b'W' | b'w' => encoding::W,
b'K' | b'k' => encoding::K,
b'M' | b'm' => encoding::M,
b'B' | b'b' => encoding::B,
b'D' | b'd' => encoding::D,
b'H' | b'h' => encoding::H,
b'V' | b'v' => encoding::V,
b'N' | b'n' => encoding::N,
b'-' | b'.' => encoding::GAP,
_ => encoding::GAP, }
}
#[inline]
#[allow(dead_code)]
pub fn fourbit_to_char(bits: u8) -> u8 {
const DECODE_TABLE: [u8; 16] = [
b'-', b'A', b'C', b'M', b'T', b'W', b'Y', b'H', b'G', b'R', b'S', b'V', b'K', b'D', b'B',
b'N',
];
DECODE_TABLE[(bits & 0xF) as usize]
}
#[inline]
pub fn complement_4bit(bits: u8) -> u8 {
((bits << 2) | (bits >> 2)) & 0xF
}
#[inline]
pub fn complement_packed_byte(packed: u8) -> u8 {
let high = packed >> 4;
let low = packed & 0xF;
(complement_4bit(high) << 4) | complement_4bit(low)
}
const SIMD_REVCOMP_THRESHOLD: usize = 32;
pub fn reverse_complement_encoded(encoded: &[u8], length: usize) -> Vec<u8> {
if encoded.is_empty() {
return Vec::new();
}
let num_bytes = encoded.len();
let is_odd_length = length % 2 == 1;
let mut output = vec![0u8; num_bytes];
if num_bytes < SIMD_REVCOMP_THRESHOLD {
reverse_complement_scalar(encoded, &mut output, length);
return output;
}
let used_simd = reverse_complement_with_simd_if_available(encoded, &mut output, length);
if !used_simd {
reverse_complement_scalar(encoded, &mut output, length);
return output;
}
if is_odd_length {
shift_nibbles_left(&mut output);
}
output
}
#[inline]
fn shift_nibbles_left(buffer: &mut [u8]) {
if buffer.is_empty() {
return;
}
let len = buffer.len();
for i in 0..len - 1 {
let current_low = buffer[i] & 0x0F;
let next_high = buffer[i + 1] >> 4;
buffer[i] = (current_low << 4) | next_high;
}
buffer[len - 1] = (buffer[len - 1] & 0x0F) << 4;
}
#[inline]
fn reverse_complement_with_simd_if_available(
encoded: &[u8],
output: &mut [u8],
length: usize,
) -> bool {
#[cfg(target_arch = "x86_64")]
{
if is_x86_feature_detected!("ssse3") {
unsafe { reverse_complement_x86_ssse3(encoded, output, length) };
return true;
}
}
#[cfg(target_arch = "aarch64")]
{
unsafe { reverse_complement_arm_neon(encoded, output, length) };
return true;
}
#[allow(unreachable_code)]
false
}
fn reverse_complement_scalar(encoded: &[u8], output: &mut [u8], length: usize) {
let num_bytes = encoded.len();
let is_odd_length = length % 2 == 1;
if !is_odd_length {
for (i, &byte) in encoded.iter().enumerate() {
let reverse_idx = num_bytes - 1 - i;
let high = byte >> 4;
let low = byte & 0xF;
let high_comp = complement_4bit(high);
let low_comp = complement_4bit(low);
output[reverse_idx] = (low_comp << 4) | high_comp;
}
} else {
for out_nuc_idx in 0..length {
let in_nuc_idx = length - 1 - out_nuc_idx;
let in_byte_idx = in_nuc_idx / 2;
let in_is_high = in_nuc_idx.is_multiple_of(2);
let nibble = if in_is_high {
encoded[in_byte_idx] >> 4
} else {
encoded[in_byte_idx] & 0xF
};
let comp_nibble = complement_4bit(nibble);
let out_byte_idx = out_nuc_idx / 2;
let out_is_high = out_nuc_idx.is_multiple_of(2);
if out_is_high {
output[out_byte_idx] = comp_nibble << 4;
} else {
output[out_byte_idx] |= comp_nibble;
}
}
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "ssse3")]
unsafe fn reverse_complement_x86_ssse3(encoded: &[u8], output: &mut [u8], _length: usize) {
let num_bytes = encoded.len();
if num_bytes < 16 {
for (i, &byte) in encoded.iter().enumerate() {
let reverse_idx = num_bytes - 1 - i;
let high = byte >> 4;
let low = byte & 0xF;
let high_comp = complement_4bit(high);
let low_comp = complement_4bit(low);
output[reverse_idx] = (low_comp << 4) | high_comp;
}
return;
}
let reverse_mask = _mm_setr_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
let mask_0f = _mm_set1_epi8(0x0F);
let full_chunks = num_bytes / 16;
let remainder = num_bytes % 16;
for chunk_idx in 0..full_chunks {
let in_offset = chunk_idx * 16;
let out_offset = num_bytes - (chunk_idx + 1) * 16;
let data = _mm_loadu_si128(encoded[in_offset..].as_ptr() as *const __m128i);
let reversed = _mm_shuffle_epi8(data, reverse_mask);
let low_nibbles = _mm_and_si128(reversed, mask_0f);
let high_nibbles = _mm_and_si128(_mm_srli_epi16(reversed, 4), mask_0f);
let swapped = _mm_or_si128(_mm_slli_epi16(low_nibbles, 4), high_nibbles);
let low = _mm_and_si128(swapped, mask_0f);
let high = _mm_and_si128(_mm_srli_epi16(swapped, 4), mask_0f);
let low_rot = _mm_and_si128(
_mm_or_si128(_mm_slli_epi16(low, 2), _mm_srli_epi16(low, 2)),
mask_0f,
);
let high_rot = _mm_and_si128(
_mm_or_si128(_mm_slli_epi16(high, 2), _mm_srli_epi16(high, 2)),
mask_0f,
);
let result = _mm_or_si128(_mm_slli_epi16(high_rot, 4), low_rot);
_mm_storeu_si128(output[out_offset..].as_mut_ptr() as *mut __m128i, result);
}
if remainder > 0 {
let in_start = full_chunks * 16;
let out_end = remainder;
for i in 0..remainder {
let byte = encoded[in_start + i];
let reverse_idx = out_end - 1 - i;
let high = byte >> 4;
let low = byte & 0xF;
let high_comp = complement_4bit(high);
let low_comp = complement_4bit(low);
output[reverse_idx] = (low_comp << 4) | high_comp;
}
}
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
unsafe fn reverse_complement_arm_neon(encoded: &[u8], output: &mut [u8], _length: usize) {
let num_bytes = encoded.len();
if num_bytes < 16 {
for (i, &byte) in encoded.iter().enumerate() {
let reverse_idx = num_bytes - 1 - i;
let high = byte >> 4;
let low = byte & 0xF;
let high_comp = complement_4bit(high);
let low_comp = complement_4bit(low);
output[reverse_idx] = (low_comp << 4) | high_comp;
}
return;
}
let reverse_indices: [u8; 16] = [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
let reverse_tbl = vld1q_u8(reverse_indices.as_ptr());
let mask_0f = vdupq_n_u8(0x0F);
let full_chunks = num_bytes / 16;
let remainder = num_bytes % 16;
for chunk_idx in 0..full_chunks {
let in_offset = chunk_idx * 16;
let out_offset = num_bytes - (chunk_idx + 1) * 16;
let data = vld1q_u8(encoded[in_offset..].as_ptr());
let reversed = vqtbl1q_u8(data, reverse_tbl);
let low_nibbles = vandq_u8(reversed, mask_0f);
let high_nibbles = vandq_u8(vshrq_n_u8(reversed, 4), mask_0f);
let swapped = vorrq_u8(vshlq_n_u8(low_nibbles, 4), high_nibbles);
let low = vandq_u8(swapped, mask_0f);
let high = vandq_u8(vshrq_n_u8(swapped, 4), mask_0f);
let low_rot = vandq_u8(vorrq_u8(vshlq_n_u8(low, 2), vshrq_n_u8(low, 2)), mask_0f);
let high_rot = vandq_u8(vorrq_u8(vshlq_n_u8(high, 2), vshrq_n_u8(high, 2)), mask_0f);
let result = vorrq_u8(vshlq_n_u8(high_rot, 4), low_rot);
vst1q_u8(output[out_offset..].as_mut_ptr(), result);
}
if remainder > 0 {
let in_start = full_chunks * 16;
let out_end = remainder;
for i in 0..remainder {
let byte = encoded[in_start + i];
let reverse_idx = out_end - 1 - i;
let high = byte >> 4;
let low = byte & 0xF;
let high_comp = complement_4bit(high);
let low_comp = complement_4bit(low);
output[reverse_idx] = (low_comp << 4) | high_comp;
}
}
}
pub fn reverse_complement(sequence: &[u8]) -> Vec<u8> {
if sequence.is_empty() {
return Vec::new();
}
let encoded = encode_dna_prefer_simd(sequence);
let rc_encoded = reverse_complement_encoded(&encoded, sequence.len());
decode_dna_prefer_simd(&rc_encoded, sequence.len())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_char_to_4bit_standard_bases() {
assert_eq!(char_to_4bit(b'A'), 0x1);
assert_eq!(char_to_4bit(b'C'), 0x2);
assert_eq!(char_to_4bit(b'G'), 0x8);
assert_eq!(char_to_4bit(b'T'), 0x4);
}
#[test]
fn test_char_to_4bit_lowercase() {
assert_eq!(char_to_4bit(b'a'), 0x1);
assert_eq!(char_to_4bit(b'c'), 0x2);
assert_eq!(char_to_4bit(b'g'), 0x8);
assert_eq!(char_to_4bit(b't'), 0x4);
}
#[test]
fn test_char_to_4bit_iupac() {
assert_eq!(char_to_4bit(b'R'), 0x9);
assert_eq!(char_to_4bit(b'Y'), 0x6);
assert_eq!(char_to_4bit(b'S'), 0xA);
assert_eq!(char_to_4bit(b'W'), 0x5);
assert_eq!(char_to_4bit(b'K'), 0xC);
assert_eq!(char_to_4bit(b'M'), 0x3);
assert_eq!(char_to_4bit(b'B'), 0xE);
assert_eq!(char_to_4bit(b'D'), 0xD);
assert_eq!(char_to_4bit(b'H'), 0x7);
assert_eq!(char_to_4bit(b'V'), 0xB);
assert_eq!(char_to_4bit(b'N'), 0xF);
}
#[test]
fn test_char_to_4bit_gaps() {
assert_eq!(char_to_4bit(b'-'), 0x0);
assert_eq!(char_to_4bit(b'.'), 0x0);
assert_eq!(char_to_4bit(b'X'), 0x0); assert_eq!(char_to_4bit(b' '), 0x0); }
#[test]
fn test_char_to_4bit_uracil() {
assert_eq!(char_to_4bit(b'U'), 0x4);
assert_eq!(char_to_4bit(b'u'), 0x4);
}
#[test]
fn test_fourbit_to_char() {
assert_eq!(fourbit_to_char(0x0), b'-');
assert_eq!(fourbit_to_char(0x1), b'A');
assert_eq!(fourbit_to_char(0x2), b'C');
assert_eq!(fourbit_to_char(0x3), b'M');
assert_eq!(fourbit_to_char(0x4), b'T');
assert_eq!(fourbit_to_char(0x5), b'W');
assert_eq!(fourbit_to_char(0x6), b'Y');
assert_eq!(fourbit_to_char(0x7), b'H');
assert_eq!(fourbit_to_char(0x8), b'G');
assert_eq!(fourbit_to_char(0x9), b'R');
assert_eq!(fourbit_to_char(0xA), b'S');
assert_eq!(fourbit_to_char(0xB), b'V');
assert_eq!(fourbit_to_char(0xC), b'K');
assert_eq!(fourbit_to_char(0xD), b'D');
assert_eq!(fourbit_to_char(0xE), b'B');
assert_eq!(fourbit_to_char(0xF), b'N');
}
#[test]
fn test_fourbit_to_char_masks_input() {
assert_eq!(fourbit_to_char(0x10), b'-'); assert_eq!(fourbit_to_char(0xFF), b'N'); }
#[test]
fn test_encode_decode_roundtrip_standard_bases() {
let sequence = b"ACGT";
let encoded = encode_dna_prefer_simd(sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, sequence);
}
#[test]
fn test_encode_decode_roundtrip_all_iupac() {
let sequence = b"ACGTRYSWKMBDHVN-";
let encoded = encode_dna_prefer_simd(sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, sequence);
}
#[test]
fn test_encode_decode_roundtrip_16_nucleotides() {
let sequence = b"ACGTACGTACGTACGT";
let encoded = encode_dna_prefer_simd(sequence);
assert_eq!(encoded.len(), 8); let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, sequence);
}
#[test]
fn test_encode_decode_roundtrip_32_nucleotides() {
let sequence = b"ACGTACGTACGTACGTACGTACGTACGTACGT";
let encoded = encode_dna_prefer_simd(sequence);
assert_eq!(encoded.len(), 16);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, sequence);
}
#[test]
fn test_encode_decode_roundtrip_non_aligned() {
for len in [1, 2, 3, 5, 7, 11, 15, 16, 17, 19, 23, 31, 32, 33, 48, 50] {
let sequence: Vec<u8> = (0..len).map(|i| b"ACGT"[i % 4]).collect();
let encoded = encode_dna_prefer_simd(&sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, sequence, "Failed for length {}", len);
}
}
#[test]
fn test_encode_compression_ratio() {
let sequence = b"ACGTACGTACGTACGT"; let encoded = encode_dna_prefer_simd(sequence);
assert_eq!(encoded.len(), 8); }
#[test]
fn test_encode_empty_sequence() {
let sequence = b"";
let encoded = encode_dna_prefer_simd(sequence);
assert!(encoded.is_empty());
}
#[test]
fn test_encode_single_nucleotide() {
for &base in b"ACGTRYSWKMBDHVN-".iter() {
let sequence = [base];
let encoded = encode_dna_prefer_simd(&sequence);
let decoded = decode_dna_prefer_simd(&encoded, 1);
assert_eq!(decoded[0], base, "Failed for base {}", base as char);
}
}
#[test]
fn test_encode_specific_values() {
let sequence = b"AC";
let encoded = encode_dna_prefer_simd(sequence);
assert_eq!(encoded.len(), 1);
assert_eq!(encoded[0], 0x12);
let sequence = b"GT";
let encoded = encode_dna_prefer_simd(sequence);
assert_eq!(encoded[0], 0x84);
}
#[test]
fn test_iupac_codes_preserved() {
let codes = b"ACGTRYSWKMBDHVN-";
let encoded = encode_dna_prefer_simd(codes);
let decoded = decode_dna_prefer_simd(&encoded, codes.len());
assert_eq!(decoded, codes);
}
#[test]
fn test_iupac_in_long_sequence() {
let sequence = b"ACGTNNNNACGTRYSWACGTKMBDHVNACGT";
let encoded = encode_dna_prefer_simd(sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, sequence);
}
#[test]
fn test_iupac_n_at_simd_boundary() {
let sequence = b"NNNNNNNNNNNNNNNN"; let encoded = encode_dna_prefer_simd(sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, sequence);
}
#[test]
fn test_iupac_lowercase() {
let sequence = b"acgtryswkmbdhvn";
let encoded = encode_dna_prefer_simd(sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, b"ACGTRYSWKMBDHVN");
}
#[test]
fn test_iupac_specific_encoding_values() {
let encoded = encode_dna_prefer_simd(b"RY");
assert_eq!(encoded[0], 0x96, "RY should encode to 0x96");
let encoded = encode_dna_prefer_simd(b"SW");
assert_eq!(encoded[0], 0xA5, "SW should encode to 0xA5");
let encoded = encode_dna_prefer_simd(b"KM");
assert_eq!(encoded[0], 0xC3, "KM should encode to 0xC3");
let encoded = encode_dna_prefer_simd(b"BD");
assert_eq!(encoded[0], 0xED, "BD should encode to 0xED");
let encoded = encode_dna_prefer_simd(b"HV");
assert_eq!(encoded[0], 0x7B, "HV should encode to 0x7B");
let encoded = encode_dna_prefer_simd(b"N-");
assert_eq!(encoded[0], 0xF0, "N- should encode to 0xF0");
}
#[test]
fn test_iupac_at_odd_positions() {
let codes: [(u8, u8); 12] = [
(b'R', 0x9),
(b'Y', 0x6),
(b'S', 0xA),
(b'W', 0x5),
(b'K', 0xC),
(b'M', 0x3),
(b'B', 0xE),
(b'D', 0xD),
(b'H', 0x7),
(b'V', 0xB),
(b'N', 0xF),
(b'-', 0x0),
];
for (code, expected_nibble) in codes {
let sequence = [b'A', code];
let encoded = encode_dna_prefer_simd(&sequence);
let expected_byte = (0x1 << 4) | expected_nibble; assert_eq!(
encoded[0], expected_byte,
"A{} should encode to 0x{:02X}",
code as char, expected_byte
);
}
}
#[test]
fn test_iupac_at_even_positions() {
let codes: [(u8, u8); 12] = [
(b'R', 0x9),
(b'Y', 0x6),
(b'S', 0xA),
(b'W', 0x5),
(b'K', 0xC),
(b'M', 0x3),
(b'B', 0xE),
(b'D', 0xD),
(b'H', 0x7),
(b'V', 0xB),
(b'N', 0xF),
(b'-', 0x0),
];
for (code, expected_nibble) in codes {
let sequence = [code, b'A'];
let encoded = encode_dna_prefer_simd(&sequence);
let expected_byte = (expected_nibble << 4) | 0x1; assert_eq!(
encoded[0], expected_byte,
"{}A should encode to 0x{:02X}",
code as char, expected_byte
);
}
}
#[test]
fn test_iupac_lowercase_conversion() {
let codes: [(u8, u8); 11] = [
(b'r', 0x9),
(b'y', 0x6),
(b's', 0xA),
(b'w', 0x5),
(b'k', 0xC),
(b'm', 0x3),
(b'b', 0xE),
(b'd', 0xD),
(b'h', 0x7),
(b'v', 0xB),
(b'n', 0xF),
];
for (code, expected_value) in codes {
assert_eq!(
char_to_4bit(code),
expected_value,
"Lowercase '{}' should encode to 0x{:X}",
code as char,
expected_value
);
}
}
#[test]
fn test_iupac_crossing_simd_boundary() {
let mut sequence = vec![b'A'; 32];
sequence[14] = b'N';
sequence[15] = b'R';
sequence[16] = b'Y';
sequence[17] = b'S';
let encoded = encode_dna_prefer_simd(&sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, sequence);
}
#[test]
fn test_iupac_multiple_simd_blocks() {
let pattern = b"ACGTRYSWKMBDHVN-";
let sequence: Vec<u8> = pattern.iter().cycle().take(64).copied().collect();
let encoded = encode_dna_prefer_simd(&sequence);
assert_eq!(encoded.len(), 32); let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, sequence);
}
#[test]
fn test_iupac_individual_roundtrip() {
let iupac_codes = b"RYSWKMBDHVN-";
for &code in iupac_codes {
let single = [code];
let encoded = encode_dna_prefer_simd(&single);
let decoded = decode_dna_prefer_simd(&encoded, 1);
assert_eq!(decoded[0], code, "Single {} failed roundtrip", code as char);
let paired = [code, code];
let encoded = encode_dna_prefer_simd(&paired);
let decoded = decode_dna_prefer_simd(&encoded, 2);
assert_eq!(decoded, paired, "Paired {} failed roundtrip", code as char);
let sixteen: Vec<u8> = vec![code; 16];
let encoded = encode_dna_prefer_simd(&sixteen);
let decoded = decode_dna_prefer_simd(&encoded, 16);
assert_eq!(decoded, sixteen, "16x {} failed roundtrip", code as char);
}
}
#[test]
fn test_iupac_interspersed_with_bases() {
let sequence = b"ANCTGNRTYCSWAGKT";
let encoded = encode_dna_prefer_simd(sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, sequence);
}
#[test]
fn test_iupac_dot_as_gap() {
let sequence = b"AC.GT";
let encoded = encode_dna_prefer_simd(sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, b"AC-GT");
}
#[test]
fn test_all_lowercase_encoding() {
let sequence = b"acgtacgtacgtacgt";
let encoded = encode_dna_prefer_simd(sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, b"ACGTACGTACGTACGT");
}
#[test]
fn test_mixed_case_encoding() {
let sequence = b"AcGtAcGtAcGtAcGt";
let encoded = encode_dna_prefer_simd(sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, b"ACGTACGTACGTACGT");
}
#[test]
fn test_case_insensitive_encoding() {
let upper = b"ACGTRYSWKMBDHVN";
let lower = b"acgtryswkmbdhvn";
let mixed = b"AcGtRySw";
let enc_upper = encode_dna_prefer_simd(upper);
let enc_lower = encode_dna_prefer_simd(lower);
assert_eq!(enc_upper, enc_lower);
let enc_mixed = encode_dna_prefer_simd(mixed);
let enc_upper_part = encode_dna_prefer_simd(b"ACGTRYSW");
assert_eq!(enc_mixed, enc_upper_part);
}
#[test]
fn test_invalid_characters_become_gaps() {
let sequence = b"ACXZGT";
let encoded = encode_dna_prefer_simd(sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, b"AC--GT");
}
#[test]
fn test_whitespace_becomes_gaps() {
let sequence = b"AC GT";
let encoded = encode_dna_prefer_simd(sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, b"AC-GT");
}
#[test]
fn test_numbers_become_gaps() {
let sequence = b"A1C2G3T4";
let encoded = encode_dna_prefer_simd(sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, b"A-C-G-T-");
}
#[test]
fn test_large_sequence() {
let sequence: Vec<u8> = (0..1000).map(|i| b"ACGTRYSWKMBDHVN-"[i % 16]).collect();
let encoded = encode_dna_prefer_simd(&sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, sequence);
}
#[test]
fn test_very_large_sequence_with_remainder() {
let len = 10_007; let sequence: Vec<u8> = (0..len).map(|i| b"ACGTN"[i % 5]).collect();
let encoded = encode_dna_prefer_simd(&sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, sequence);
}
#[test]
fn test_decode_partial_length() {
let sequence = b"ACGTACGT";
let encoded = encode_dna_prefer_simd(sequence);
let decoded = decode_dna_prefer_simd(&encoded, 5);
assert_eq!(decoded, b"ACGTA");
}
#[test]
fn test_lengths_1_to_100() {
for len in 1..=100 {
let sequence: Vec<u8> = (0..len).map(|i| b"ACGTN"[i % 5]).collect();
let encoded = encode_dna_prefer_simd(&sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, sequence, "Failed for length {}", len);
}
}
#[test]
fn test_encode_scalar_directly() {
let sequence = b"ACGTACGT";
let mut output = vec![0u8; 4];
encode_scalar(sequence, &mut output);
assert_eq!(output[0], 0x12); assert_eq!(output[1], 0x84); assert_eq!(output[2], 0x12); assert_eq!(output[3], 0x84); }
#[test]
fn test_decode_scalar_directly() {
let encoded = [0x12u8, 0x84]; let mut output = vec![0u8; 4];
decode_scalar(&encoded, &mut output, 4);
assert_eq!(output, b"ACGT");
}
#[test]
fn test_decode_specific_4bit_values() {
let decoded = decode_dna_prefer_simd(&[0x12, 0x84], 4);
assert_eq!(decoded, b"ACGT");
let decoded = decode_dna_prefer_simd(&[0x96, 0xA5], 4);
assert_eq!(decoded, b"RYSW");
let decoded = decode_dna_prefer_simd(&[0xC3, 0xED], 4);
assert_eq!(decoded, b"KMBD");
let decoded = decode_dna_prefer_simd(&[0x7B, 0xF0], 4);
assert_eq!(decoded, b"HVN-");
}
#[test]
fn test_decode_all_4bit_values() {
let expected: [u8; 16] = [
b'-', b'A', b'C', b'M', b'T', b'W', b'Y', b'H', b'G', b'R', b'S', b'V', b'K', b'D',
b'B', b'N',
];
for (value, &expected_char) in expected.iter().enumerate() {
let encoded_high = [(value as u8) << 4];
let decoded = decode_dna_prefer_simd(&encoded_high, 1);
assert_eq!(
decoded[0], expected_char,
"Value 0x{:X} in high nibble should decode to '{}'",
value, expected_char as char
);
let encoded_low = [value as u8];
let decoded = decode_dna_prefer_simd(&encoded_low, 2);
assert_eq!(
decoded[1], expected_char,
"Value 0x{:X} in low nibble should decode to '{}'",
value, expected_char as char
);
}
}
#[test]
fn test_decode_truncated_length() {
let sequence = b"ACGTRYSWKMBDHVN-";
let encoded = encode_dna_prefer_simd(sequence);
let decoded = decode_dna_prefer_simd(&encoded, 8);
assert_eq!(decoded, b"ACGTRYSW");
let decoded = decode_dna_prefer_simd(&encoded, 1);
assert_eq!(decoded, b"A");
let decoded = decode_dna_prefer_simd(&encoded, 3);
assert_eq!(decoded, b"ACG");
}
#[test]
fn test_decode_empty() {
let decoded = decode_dna_prefer_simd(&[], 0);
assert!(decoded.is_empty());
}
#[test]
fn test_decode_zero_length() {
let encoded = [0x01, 0x23];
let decoded = decode_dna_prefer_simd(&encoded, 0);
assert!(decoded.is_empty());
}
#[test]
fn test_decode_simd_full_block() {
let encoded: Vec<u8> = (0..16)
.map(|i| {
let high = (i * 2) % 16;
let low = (i * 2 + 1) % 16;
((high << 4) | low) as u8
})
.collect();
let decoded = decode_dna_prefer_simd(&encoded, 32);
assert_eq!(decoded.len(), 32);
for (i, &c) in decoded.iter().enumerate() {
let expected_value = i % 16;
let expected_char = fourbit_to_char(expected_value as u8);
assert_eq!(
c, expected_char,
"Position {} should be '{}'",
i, expected_char as char
);
}
}
#[test]
fn test_decode_exactly_16_nucleotides() {
let sequence = b"ACGTRYSWKMBDHVN-";
let encoded = encode_dna_prefer_simd(sequence);
assert_eq!(encoded.len(), 8);
let decoded = decode_dna_prefer_simd(&encoded, 16);
assert_eq!(decoded, b"ACGTRYSWKMBDHVN-");
}
#[test]
fn test_decode_with_remainder() {
let sequence = b"ACGTRYSWKMBDHVN-ACGT";
let encoded = encode_dna_prefer_simd(sequence);
assert_eq!(encoded.len(), 10);
let decoded = decode_dna_prefer_simd(&encoded, 20);
assert_eq!(decoded, sequence);
}
#[test]
fn test_decode_multiple_simd_blocks() {
let pattern = b"ACGTRYSWKMBDHVN-";
let sequence: Vec<u8> = pattern.iter().cycle().take(64).copied().collect();
let encoded = encode_dna_prefer_simd(&sequence);
assert_eq!(encoded.len(), 32);
let decoded = decode_dna_prefer_simd(&encoded, 64);
assert_eq!(decoded, sequence);
}
#[test]
fn test_decode_iupac_codes() {
let iupac_only = [
0x96u8, 0xA5, 0xC3, 0xED, 0x7B, 0xFF, ];
let decoded = decode_dna_prefer_simd(&iupac_only, 12);
assert_eq!(decoded, b"RYSWKMBDHVNN");
}
#[test]
fn test_decode_gaps() {
let all_gaps = [0x00u8, 0x00, 0x00, 0x00];
let decoded = decode_dna_prefer_simd(&all_gaps, 8);
assert_eq!(decoded, b"--------");
}
#[test]
fn test_decode_scalar_small_inputs() {
for size in [2, 4, 6, 8, 10, 12, 14] {
let sequence: Vec<u8> = (0..size).map(|i| b"ACGTN"[i % 5]).collect();
let encoded = encode_dna_prefer_simd(&sequence);
let decoded = decode_dna_prefer_simd(&encoded, size);
assert_eq!(decoded, sequence, "Failed for size {}", size);
}
}
#[test]
fn test_decode_always_uppercase() {
let sequence = b"acgtryswkmbdhvn";
let encoded = encode_dna_prefer_simd(sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
for &c in &decoded {
assert!(
c.is_ascii_uppercase() || c == b'-',
"Decoded character '{}' should be uppercase",
c as char
);
}
assert_eq!(decoded, b"ACGTRYSWKMBDHVN");
}
#[test]
fn test_simd_boundary_15_nucleotides() {
let sequence: Vec<u8> = (0..15).map(|i| b"ACGTN"[i % 5]).collect();
let encoded = encode_dna_prefer_simd(&sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, sequence);
}
#[test]
fn test_simd_boundary_17_nucleotides() {
let sequence: Vec<u8> = (0..17).map(|i| b"ACGTN"[i % 5]).collect();
let encoded = encode_dna_prefer_simd(&sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, sequence);
}
#[test]
fn test_simd_boundary_exact_multiples() {
for len in [16, 32, 48, 64, 128, 256] {
let sequence: Vec<u8> = (0..len).map(|i| b"ACGTN"[i % 5]).collect();
let encoded = encode_dna_prefer_simd(&sequence);
let decoded = decode_dna_prefer_simd(&encoded, sequence.len());
assert_eq!(decoded, sequence, "Failed for length {}", len);
}
}
#[test]
fn test_concurrent_encoding() {
use std::thread;
let sequences: Vec<Vec<u8>> = (0..100)
.map(|i| {
let len = 100 + i * 10;
(0..len).map(|j| b"ACGTN"[j % 5]).collect()
})
.collect();
let handles: Vec<_> = sequences
.into_iter()
.map(|seq| {
thread::spawn(move || {
let encoded = encode_dna_prefer_simd(&seq);
let decoded = decode_dna_prefer_simd(&encoded, seq.len());
assert_eq!(decoded, seq);
})
})
.collect();
for handle in handles {
handle.join().expect("Thread panicked");
}
}
#[test]
fn test_concurrent_decoding() {
use std::thread;
let test_data: Vec<(Vec<u8>, usize)> = (0..100)
.map(|i| {
let len = 100 + i * 10;
let seq: Vec<u8> = (0..len).map(|j| b"ACGTN"[j % 5]).collect();
let encoded = encode_dna_prefer_simd(&seq);
(encoded, len)
})
.collect();
let handles: Vec<_> = test_data
.into_iter()
.map(|(encoded, len)| {
thread::spawn(move || {
let decoded = decode_dna_prefer_simd(&encoded, len);
for &nucleotide in &decoded {
assert!(
b"ACGTN".contains(&nucleotide),
"Invalid nucleotide: {}",
nucleotide as char
);
}
})
})
.collect();
for handle in handles {
handle.join().expect("Thread panicked");
}
}
#[test]
fn test_concurrent_encode_decode_same_data() {
use std::sync::Arc;
use std::thread;
let sequence: Arc<Vec<u8>> =
Arc::new((0..10000).map(|i| b"ACGTRYSWKMBDHVN-"[i % 16]).collect());
let handles: Vec<_> = (0..50)
.map(|_| {
let seq = Arc::clone(&sequence);
thread::spawn(move || {
let encoded = encode_dna_prefer_simd(&seq);
let decoded = decode_dna_prefer_simd(&encoded, seq.len());
assert_eq!(decoded.as_slice(), seq.as_slice());
})
})
.collect();
for handle in handles {
handle.join().expect("Thread panicked");
}
}
#[test]
fn test_thread_safety_with_varying_lengths() {
use std::thread;
let lengths: Vec<usize> = vec![1, 2, 8, 15, 16, 17, 31, 32, 33, 63, 64, 65, 100, 256, 1000];
let handles: Vec<_> = lengths
.into_iter()
.flat_map(|len| {
(0..10).map(move |_| {
thread::spawn(move || {
let seq: Vec<u8> = (0..len).map(|j| b"ACGTN"[j % 5]).collect();
let encoded = encode_dna_prefer_simd(&seq);
let decoded = decode_dna_prefer_simd(&encoded, seq.len());
assert_eq!(decoded, seq, "Failed for length {}", len);
})
})
})
.collect();
for handle in handles {
handle.join().expect("Thread panicked");
}
}
#[test]
fn test_complement_4bit_standard_bases() {
assert_eq!(complement_4bit(encoding::A), encoding::T);
assert_eq!(complement_4bit(encoding::T), encoding::A);
assert_eq!(complement_4bit(encoding::C), encoding::G);
assert_eq!(complement_4bit(encoding::G), encoding::C);
}
#[test]
fn test_complement_4bit_ambiguity_codes() {
assert_eq!(complement_4bit(encoding::R), encoding::Y);
assert_eq!(complement_4bit(encoding::Y), encoding::R);
assert_eq!(complement_4bit(encoding::K), encoding::M);
assert_eq!(complement_4bit(encoding::M), encoding::K);
assert_eq!(complement_4bit(encoding::D), encoding::H);
assert_eq!(complement_4bit(encoding::H), encoding::D);
assert_eq!(complement_4bit(encoding::B), encoding::V);
assert_eq!(complement_4bit(encoding::V), encoding::B);
}
#[test]
fn test_complement_4bit_self_complementary() {
assert_eq!(complement_4bit(encoding::S), encoding::S);
assert_eq!(complement_4bit(encoding::W), encoding::W);
assert_eq!(complement_4bit(encoding::N), encoding::N);
assert_eq!(complement_4bit(encoding::GAP), encoding::GAP);
}
#[test]
fn test_complement_4bit_involution() {
for i in 0..16u8 {
let comp = complement_4bit(i);
let double_comp = complement_4bit(comp);
assert_eq!(
double_comp, i,
"Double complement of 0x{:X} should be itself",
i
);
}
}
#[test]
fn test_reverse_complement_palindrome() {
let rc = reverse_complement(b"ACGT");
assert_eq!(rc, b"ACGT");
let rc = reverse_complement(b"ATAT");
assert_eq!(rc, b"ATAT");
}
#[test]
fn test_reverse_complement_standard() {
let rc = reverse_complement(b"AACG");
assert_eq!(rc, b"CGTT");
let rc = reverse_complement(b"A");
assert_eq!(rc, b"T");
let rc = reverse_complement(b"AC");
assert_eq!(rc, b"GT");
let rc = reverse_complement(b"AAA");
assert_eq!(rc, b"TTT");
let rc = reverse_complement(b"GATTACA");
assert_eq!(rc, b"TGTAATC");
}
#[test]
fn test_reverse_complement_iupac() {
let rc = reverse_complement(b"R");
assert_eq!(rc, b"Y");
let rc = reverse_complement(b"S");
assert_eq!(rc, b"S");
let rc = reverse_complement(b"W");
assert_eq!(rc, b"W");
let rc = reverse_complement(b"N");
assert_eq!(rc, b"N");
let rc = reverse_complement(b"ACRYGT");
assert_eq!(rc, b"ACRYGT");
}
#[test]
fn test_reverse_complement_empty() {
let rc = reverse_complement(b"");
assert!(rc.is_empty());
}
#[test]
fn test_reverse_complement_long_sequence() {
let sequence: Vec<u8> = b"ACGT".iter().cycle().take(64).copied().collect();
let rc = reverse_complement(&sequence);
assert_eq!(rc.len(), 64);
assert_eq!(rc, sequence);
let sequence: Vec<u8> = b"AACG".iter().cycle().take(64).copied().collect();
let rc = reverse_complement(&sequence);
let rc_rc = reverse_complement(&rc);
assert_eq!(rc_rc, sequence);
}
#[test]
fn test_reverse_complement_encoded() {
let sequence = b"GATTACA";
let encoded = encode_dna_prefer_simd(sequence);
let rc_encoded = reverse_complement_encoded(&encoded, sequence.len());
let rc_decoded = decode_dna_prefer_simd(&rc_encoded, sequence.len());
assert_eq!(rc_decoded, b"TGTAATC");
}
#[test]
fn test_complement_packed_byte() {
let packed = 0x12u8; let comp = complement_packed_byte(packed);
assert_eq!(comp, 0x48); }
#[test]
fn test_reverse_complement_thread_safety() {
use std::sync::Arc;
use std::thread;
let sequence = Arc::new(b"GATTACA".to_vec());
let handles: Vec<_> = (0..50)
.map(|_| {
let seq = Arc::clone(&sequence);
thread::spawn(move || {
let rc = reverse_complement(&seq);
assert_eq!(rc, b"TGTAATC");
})
})
.collect();
for handle in handles {
handle.join().expect("Thread panicked");
}
}
#[test]
fn test_reverse_complement_simd_threshold_boundary() {
let seq_31: Vec<u8> = b"ACGTACGTACGTACGTACGTACGTACGTACG".to_vec();
assert_eq!(seq_31.len(), 31);
let rc_31 = reverse_complement(&seq_31);
let rc_rc_31 = reverse_complement(&rc_31);
assert_eq!(rc_rc_31, seq_31, "31-base involution failed");
let seq_32: Vec<u8> = b"ACGTACGTACGTACGTACGTACGTACGTACGT".to_vec();
assert_eq!(seq_32.len(), 32);
let rc_32 = reverse_complement(&seq_32);
let rc_rc_32 = reverse_complement(&rc_32);
assert_eq!(rc_rc_32, seq_32, "32-base involution failed");
let seq_33: Vec<u8> = b"ACGTACGTACGTACGTACGTACGTACGTACGTA".to_vec();
assert_eq!(seq_33.len(), 33);
let rc_33 = reverse_complement(&seq_33);
let rc_rc_33 = reverse_complement(&rc_33);
assert_eq!(rc_rc_33, seq_33, "33-base involution failed");
}
#[test]
fn test_reverse_complement_odd_length_simd_path() {
for len in [33, 35, 47, 49, 63, 65, 99, 127, 255, 511, 1023] {
let sequence: Vec<u8> = (0..len).map(|i| b"ACGT"[i % 4]).collect();
assert_eq!(sequence.len(), len);
let rc = reverse_complement(&sequence);
assert_eq!(rc.len(), len, "Length mismatch for {}-base sequence", len);
let rc_rc = reverse_complement(&rc);
assert_eq!(
rc_rc, sequence,
"Involution failed for {}-base sequence",
len
);
let expected_first = complement_char(sequence[len - 1]);
let expected_last = complement_char(sequence[0]);
assert_eq!(rc[0], expected_first, "First base wrong for len={}", len);
assert_eq!(
rc[len - 1],
expected_last,
"Last base wrong for len={}",
len
);
}
}
#[test]
fn test_reverse_complement_even_length_simd_path() {
for len in [32, 34, 48, 64, 100, 128, 256, 512, 1024] {
let sequence: Vec<u8> = (0..len).map(|i| b"ACGT"[i % 4]).collect();
assert_eq!(sequence.len(), len);
let rc = reverse_complement(&sequence);
assert_eq!(rc.len(), len, "Length mismatch for {}-base sequence", len);
let rc_rc = reverse_complement(&rc);
assert_eq!(
rc_rc, sequence,
"Involution failed for {}-base sequence",
len
);
}
}
#[test]
fn test_reverse_complement_simd_scalar_equivalence() {
for len in [1, 2, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129] {
let sequence: Vec<u8> = (0..len).map(|i| b"GATTACA"[i % 7]).collect();
let rc_api = reverse_complement(&sequence);
let rc_manual: Vec<u8> = sequence.iter().rev().map(|&c| complement_char(c)).collect();
assert_eq!(rc_api, rc_manual, "SIMD/scalar mismatch at length {}", len);
}
}
#[test]
fn test_reverse_complement_odd_length_non_palindromic() {
let test_cases = [
(b"AAACCCGGGTTTT".to_vec(), 13), (b"AAACCCGGGTTTTAAACCCGGGTTTTAAAAAAA".to_vec(), 33), (b"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG".to_vec(), 33), (b"TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT".to_vec(), 33), ];
for (sequence, expected_len) in test_cases {
assert_eq!(sequence.len(), expected_len);
let rc = reverse_complement(&sequence);
assert_eq!(rc.len(), expected_len);
let rc_rc = reverse_complement(&rc);
assert_eq!(rc_rc, sequence, "Involution failed for {:?}", sequence);
if sequence != b"AAACCCGGGTTTT".to_vec() {
}
}
}
#[test]
fn test_reverse_complement_encoded_api_consistency() {
for len in [
1, 15, 16, 17, 31, 32, 33, 63, 64, 65, 100, 127, 128, 129, 255, 256, 257,
] {
let sequence: Vec<u8> = (0..len).map(|i| b"ACGTRYSWKMBDHVN"[i % 15]).collect();
let rc_high = reverse_complement(&sequence);
let encoded = encode_dna_prefer_simd(&sequence);
let rc_encoded = reverse_complement_encoded(&encoded, len);
let rc_low = decode_dna_prefer_simd(&rc_encoded, len);
assert_eq!(
rc_high,
rc_low,
"API mismatch at length {} (odd={})",
len,
len % 2 == 1
);
}
}
#[test]
fn test_reverse_complement_iupac_odd_length_simd() {
let all_iupac = b"ACGTRYSWKMBDHVN-";
let sequence: Vec<u8> = all_iupac.iter().cycle().take(65).copied().collect();
assert_eq!(sequence.len(), 65);
let rc = reverse_complement(&sequence);
assert_eq!(rc.len(), 65);
for i in 0..65 {
let original_idx = 65 - 1 - i;
let original = sequence[original_idx];
let expected = complement_char(original);
assert_eq!(
rc[i], expected,
"Position {}: expected complement of {} to be {}, got {}",
i, original as char, expected as char, rc[i] as char
);
}
}
#[test]
fn test_nibble_shift_via_encoded_odd_sequences() {
for len in [33, 35, 65, 99, 127, 255] {
let sequence: Vec<u8> = (0..len).map(|i| b"ACGT"[i % 4]).collect();
let encoded = encode_dna_prefer_simd(&sequence);
let rc_encoded = reverse_complement_encoded(&encoded, len);
let decoded = decode_dna_prefer_simd(&rc_encoded, len);
let expected = reverse_complement(&sequence);
assert_eq!(decoded, expected, "Nibble shift issue at length {}", len);
}
}
fn complement_char(c: u8) -> u8 {
match c {
b'A' | b'a' => b'T',
b'T' | b't' => b'A',
b'C' | b'c' => b'G',
b'G' | b'g' => b'C',
b'U' | b'u' => b'A',
b'R' | b'r' => b'Y',
b'Y' | b'y' => b'R',
b'S' | b's' => b'S',
b'W' | b'w' => b'W',
b'K' | b'k' => b'M',
b'M' | b'm' => b'K',
b'B' | b'b' => b'V',
b'V' | b'v' => b'B',
b'D' | b'd' => b'H',
b'H' | b'h' => b'D',
b'N' | b'n' => b'N',
b'-' => b'-',
b'.' => b'-',
_ => b'-',
}
}
}