use crate::{
alignment::{
Alignment, MaybeAligned, ProfileError, ScoreAndRanges, ScoreEnds, ScoreStarts, SeqSrc,
sw::{
sw_align_3pass, sw_scalar_align, sw_scalar_score, sw_simd_align, sw_simd_score, sw_simd_score_ends,
sw_simd_score_ends_reverse, sw_simd_score_ranges,
},
},
data::{mappings::ByteIndexMap, matrices::WeightMatrix},
math::{AlignableIntWidth, AnyInt, FromSameSignedness},
simd::SimdAnyInt,
};
use std::{
borrow::Cow,
convert::Into,
ops::Range,
simd::{SimdElement, prelude::*},
vec,
};
#[inline]
pub(crate) fn validate_profile_args(seq: &[u8], gap_open: i8, gap_extend: i8) -> Result<(), ProfileError> {
if seq.as_ref().is_empty() {
Err(ProfileError::EmptySequence)
} else if !(-127..=0).contains(&gap_open) {
Err(ProfileError::GapOpenOutOfRange { gap_open })
} else if !(-127..=0).contains(&gap_extend) {
Err(ProfileError::GapExtendOutOfRange { gap_extend })
} else if gap_extend < gap_open {
Err(ProfileError::BadGapWeights { gap_open, gap_extend })
} else {
Ok(())
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct ScalarProfile<'a, const S: usize> {
pub(crate) seq: Cow<'a, [u8]>,
pub(crate) matrix: &'a WeightMatrix<'a, i8, S>,
pub(crate) gap_open: i32,
pub(crate) gap_extend: i32,
}
impl<'a, const S: usize> ScalarProfile<'a, S> {
#[inline]
pub fn new(
seq: impl Into<Cow<'a, [u8]>>, matrix: &'a WeightMatrix<'a, i8, S>, gap_open: i8, gap_extend: i8,
) -> Result<Self, ProfileError> {
let seq = seq.into();
validate_profile_args(&seq, gap_open, gap_extend)?;
Ok(Self::new_unchecked(seq, matrix, gap_open, gap_extend))
}
#[inline]
#[must_use]
pub fn new_unchecked(
seq: impl Into<Cow<'a, [u8]>>, matrix: &'a WeightMatrix<'a, i8, S>, gap_open: i8, gap_extend: i8,
) -> Self {
ScalarProfile {
seq: seq.into(),
matrix,
gap_open: i32::from(gap_open),
gap_extend: i32::from(gap_extend),
}
}
#[inline]
#[must_use]
pub fn sw_score<Q>(&self, seq: &Q) -> MaybeAligned<u32>
where
Q: AsRef<[u8]> + ?Sized, {
sw_scalar_score(seq.as_ref(), self)
}
#[inline]
#[must_use]
pub fn sw_align<Q>(&self, seq: SeqSrc<&Q>) -> MaybeAligned<Alignment<u32>>
where
Q: AsRef<[u8]> + ?Sized, {
seq.make_alignment(|reference| sw_scalar_align(reference.as_ref(), self))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct StripedProfile<'a, T, const N: usize, const S: usize>
where
T: SimdElement, {
pub(crate) profile: Vec<Simd<T, N>>,
pub(crate) gap_open: T,
pub(crate) gap_extend: T,
pub(crate) bias: T,
pub(crate) mapping: &'a ByteIndexMap<S>,
pub(crate) seq_len: usize,
}
impl<'a, T, const N: usize, const S: usize> StripedProfile<'a, T, N, S>
where
T: AnyInt + SimdElement,
{
pub fn new<U>(seq: &[u8], matrix: &WeightMatrix<'a, U, S>, gap_open: i8, gap_extend: i8) -> Result<Self, ProfileError>
where
T: FromSameSignedness<U> + AlignableIntWidth,
U: AnyInt, {
validate_profile_args(seq, gap_open, gap_extend)?;
Ok(Self::new_unchecked(seq, matrix, gap_open, gap_extend))
}
pub fn new_unchecked<U>(seq: &[u8], matrix: &WeightMatrix<'a, U, S>, gap_open: i8, gap_extend: i8) -> Self
where
T: From<U>,
U: AnyInt, {
let number_vectors = seq.len().div_ceil(N);
let total_lanes = N * number_vectors;
let bias = matrix.bias.into();
let biases = Simd::splat(bias);
let mut profile = vec![biases; S * number_vectors];
for v in 0..number_vectors {
for ref_index in 0..matrix.mapping.len() {
let mut vector = biases;
for (i, q) in (v..total_lanes).step_by(number_vectors).enumerate() {
if q < seq.len() {
let query_index = matrix.mapping.to_index(seq[q]);
vector[i] = matrix.weights[ref_index][query_index].into();
}
}
profile[ref_index * number_vectors + v] = vector;
}
}
StripedProfile {
profile,
gap_open: T::from_literal(-gap_open),
gap_extend: T::from_literal(-gap_extend),
bias,
mapping: matrix.mapping,
seq_len: seq.len(),
}
}
#[must_use]
pub(crate) fn reverse_from_forward(&self, seq_end: usize) -> Option<Self> {
if seq_end == 0 || seq_end > self.seq_len {
return None;
}
let number_vectors = seq_end.div_ceil(N);
let number_vecs_old = self.number_vectors();
let total_lanes = N * number_vectors;
let biases = Simd::splat(self.bias);
let mut profile = vec![biases; S * number_vectors];
for v in 0..number_vectors {
for ref_index in 0..self.mapping.len() {
let mut vector = biases;
for (i, q) in (v..total_lanes).step_by(number_vectors).enumerate() {
if q < seq_end {
let q_old = seq_end - 1 - q;
let v_old = q_old % number_vecs_old;
let lane_old = q_old / number_vecs_old;
vector[i] = self.profile[ref_index * number_vecs_old + v_old][lane_old];
}
}
profile[ref_index * number_vectors + v] = vector;
}
}
Some(StripedProfile {
profile,
gap_open: self.gap_open,
gap_extend: self.gap_extend,
bias: self.bias,
mapping: self.mapping,
seq_len: seq_end,
})
}
#[must_use]
#[allow(dead_code)]
pub(crate) fn new_with_range(&self, range: Range<usize>) -> Option<Self> {
if range.is_empty() || range.end > self.seq_len {
return None;
}
let new_len = range.end - range.start;
let number_vectors = new_len.div_ceil(N);
let number_vecs_old = self.number_vectors();
let total_lanes = N * number_vectors;
let biases = Simd::splat(self.bias);
let mut profile = vec![biases; S * number_vectors];
for v in 0..number_vectors {
for ref_index in 0..self.mapping.len() {
let mut vector = biases;
for (i, q) in (v..total_lanes).step_by(number_vectors).enumerate() {
if q < new_len {
let q_old = range.start + q;
let v_old = q_old % number_vecs_old;
let lane_old = q_old / number_vecs_old;
vector[i] = self.profile[ref_index * number_vecs_old + v_old][lane_old];
}
}
profile[ref_index * number_vectors + v] = vector;
}
}
Some(StripedProfile {
profile,
gap_open: self.gap_open,
gap_extend: self.gap_extend,
bias: self.bias,
mapping: self.mapping,
seq_len: new_len,
})
}
#[inline]
#[must_use]
pub fn number_vectors(&self) -> usize {
self.profile.len() / S
}
}
impl<T, const N: usize, const S: usize> StripedProfile<'_, T, N, S>
where
T: AlignableIntWidth,
Simd<T, N>: SimdAnyInt<T, N>,
{
#[inline]
#[must_use]
pub fn sw_score<Q>(&self, seq: &Q) -> MaybeAligned<u32>
where
Q: AsRef<[u8]> + ?Sized, {
sw_simd_score::<T, N, S>(seq.as_ref(), self)
}
#[inline]
#[must_use]
pub fn sw_score_ends<Q>(&self, seq: SeqSrc<&Q>) -> MaybeAligned<ScoreEnds<u32>>
where
Q: AsRef<[u8]> + ?Sized, {
seq.make_alignment(|reference| sw_simd_score_ends::<T, N, S>(reference.as_ref(), self))
}
#[inline]
#[must_use]
#[allow(dead_code)]
pub(crate) fn sw_score_ends_reverse<Q>(&self, seq: SeqSrc<&Q>) -> MaybeAligned<ScoreStarts<u32>>
where
Q: AsRef<[u8]> + ?Sized, {
seq.make_alignment(|reference| sw_simd_score_ends_reverse::<T, N, S>(reference.as_ref(), self))
}
#[inline]
#[must_use]
pub fn sw_align<Q>(&self, seq: SeqSrc<&Q>) -> MaybeAligned<Alignment<u32>>
where
Q: AsRef<[u8]> + ?Sized, {
seq.make_alignment(|reference| sw_simd_align::<T, N, S>(reference.as_ref(), self))
}
#[inline]
#[must_use]
pub fn sw_score_ranges<Q>(&self, seq: SeqSrc<&Q>) -> MaybeAligned<ScoreAndRanges<u32>>
where
Q: AsRef<[u8]> + ?Sized, {
seq.make_alignment(|reference| sw_simd_score_ranges::<T, N, S>(reference.as_ref(), self))
}
#[inline]
#[must_use]
pub(crate) fn sw_align_3pass<Q>(
&self, seq: SeqSrc<&Q>, profile_seq: &[u8], matrix: &WeightMatrix<i8, S>, gap_open: i8, gap_extend: i8,
) -> MaybeAligned<Alignment<u32>>
where
Q: AsRef<[u8]> + ?Sized, {
seq.make_alignment(|reference| sw_align_3pass(reference.as_ref(), self, profile_seq, matrix, gap_open, gap_extend))
}
}
#[cfg(test)]
mod bench {
use test::Bencher;
extern crate test;
use super::*;
use crate::alignment::sw::test_data::{GAP_EXTEND, GAP_OPEN};
use crate::data::{constants::mappings::DNA_PROFILE_MAP, matrices::WeightMatrix};
pub(crate) static DATA: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/data/KJ907631.1.txt")); pub(crate) static MATRIX: WeightMatrix<u8, 5> =
WeightMatrix::new(&DNA_PROFILE_MAP, 2, -5, Some(b'N')).to_biased_matrix();
#[bench]
fn build_profile_u8(b: &mut Bencher) {
b.iter(|| std::hint::black_box(StripedProfile::<u8, 32, 5>::new(DATA, &MATRIX, GAP_OPEN, GAP_EXTEND)));
}
#[bench]
fn build_profile_u8_rev_half_recreate(b: &mut Bencher) {
b.iter(|| {
let data_rev: Vec<u8> = DATA[..DATA.len() / 2].iter().copied().rev().collect();
std::hint::black_box(StripedProfile::<u8, 32, 5>::new(&data_rev, &MATRIX, GAP_OPEN, GAP_EXTEND))
});
}
#[bench]
fn build_profile_u8_rev_half_mapping(b: &mut Bencher) {
let prof = StripedProfile::<u8, 32, 5>::new(DATA, &MATRIX, GAP_OPEN, GAP_EXTEND).unwrap();
b.iter(|| std::hint::black_box(prof.reverse_from_forward(DATA.len() / 2)));
}
}