use crate::{
DEFAULT_SIMD_LANES,
data::types::amino_acids::AminoAcidsReadable,
distance::{
DistanceError::{self, NoData, NotComparable},
hamming_simd,
},
private::Sealed,
};
pub trait AminoAcidsDistance: AminoAcidsReadable + Sealed {
#[inline]
#[must_use]
fn distance_hamming<T: AminoAcidsReadable>(&self, other_sequence: &T) -> usize {
hamming_simd::<{ DEFAULT_SIMD_LANES }>(self.amino_acids_bytes(), other_sequence.amino_acids_bytes())
}
#[inline]
fn distance_physiochemical<T: AminoAcidsReadable>(&self, other_sequence: &T) -> Result<f32, DistanceError> {
physiochemical(self.amino_acids_bytes(), other_sequence.amino_acids_bytes())
}
}
impl<T: AminoAcidsReadable + Sealed> AminoAcidsDistance for T {}
#[allow(clippy::cast_precision_loss)]
pub fn physiochemical(seq1: &[u8], seq2: &[u8]) -> Result<f32, DistanceError> {
use crate::data::matrices::PHYSIOCHEMICAL_FACTORS as dm;
if seq1.is_empty() || seq2.is_empty() {
return Err(NoData);
}
if seq1 == seq2 {
return if seq1.iter().any(|&aa| dm[b'A' as usize][aa as usize].is_some()) {
Ok(0.0)
} else {
Err(NotComparable)
};
}
let (number_valid, mut pcd_distance) = seq1
.iter()
.zip(seq2)
.filter_map(|(a1, a2)| dm[*a1 as usize][*a2 as usize])
.map(|val| (1, val))
.fold((0u32, 0.0), |(a_valid, a_value), (i_valid, i_value)| {
(a_valid + i_valid, a_value + i_value)
});
if number_valid > 0 {
pcd_distance /= number_valid as f32;
Ok(pcd_distance)
} else {
Err(NotComparable)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{assert_fp_eq, distance::DistanceError::NotComparable};
#[test]
fn pcd_identical_sequences() {
assert!(matches!(physiochemical(b"???", b"???"), Err(NotComparable)));
assert_fp_eq!(physiochemical(b"MANATEE", b"MANATEE").unwrap(), 0.0);
assert_fp_eq!(physiochemical(b"MA?A", b"MA?A").unwrap(), 0.0);
}
}