diff --git a/lightmotif/src/dense.rs b/lightmotif/src/dense.rs index c3ab0bf78a3c92f95b99a43516fe7db7e8dff5ec..4fe699c9b0490ba0a7e98c6aab4e9d7d64af70a8 100644 --- a/lightmotif/src/dense.rs +++ b/lightmotif/src/dense.rs @@ -216,6 +216,7 @@ impl<T: Default + Copy, C: Unsigned, A: Unsigned + PowerOfTwo> Index<usize> fn index(&self, index: usize) -> &Self::Output { let c = self.stride(); let row = self.offset + c * index; + debug_assert!(row + C::USIZE <= self.data.len()); &self.data[row..row + C::USIZE] } } @@ -227,6 +228,7 @@ impl<T: Default + Copy, C: Unsigned, A: Unsigned + PowerOfTwo> IndexMut<usize> fn index_mut(&mut self, index: usize) -> &mut Self::Output { let c = self.stride(); let row = self.offset + c * index; + debug_assert!(row + C::USIZE <= self.data.len()); &mut self.data[row..row + C::USIZE] } } @@ -239,6 +241,7 @@ impl<T: Default + Copy, C: Unsigned, A: Unsigned + PowerOfTwo> Index<MatrixCoord fn index(&self, index: MatrixCoordinates) -> &Self::Output { let c = self.stride(); let i = self.offset + c * index.row + index.col; + debug_assert!(i < self.data.len()); &self.data[i] } } diff --git a/lightmotif/src/pli/mod.rs b/lightmotif/src/pli/mod.rs index 40685b299dd4730d99d080dd4484079c772c0cfd..297c60a6d940d67d827f3c3f8f2ccaebe3f6121d 100644 --- a/lightmotif/src/pli/mod.rs +++ b/lightmotif/src/pli/mod.rs @@ -81,13 +81,13 @@ pub trait Score<A: Alphabet, C: StrictlyPositive> { let seq = seq.as_ref(); let pssm = pssm.as_ref(); - if seq.len() < pssm.len() { + if seq.len() < pssm.len() || rows.len() == 0 { scores.resize(0, 0); return; } // FIXME? - scores.resize(rows.len(), seq.len() - pssm.len() + 1); + scores.resize(rows.len(), (seq.len() + 1).saturating_sub(pssm.len())); let result = scores.matrix_mut(); let matrix = pssm.matrix(); @@ -471,3 +471,38 @@ where C: StrictlyPositive + MultipleOf<U16>, { } + +// -- Tests -------------------------------------------------------------------- + +#[cfg(test)] +mod test { + use std::str::FromStr; + use typenum::consts::U4; + + use super::*; + + use crate::abc::Dna; + use crate::pwm::CountMatrix; + + #[test] + fn test_score_rows_into_empty() { + let pli = Pipeline::generic(); + + let seq = EncodedSequence::<Dna>::from_str("ATGCA").unwrap(); + let mut striped = <Pipeline<_, _> as Stripe<Dna, U4>>::stripe(&pli, seq); + + let cm = CountMatrix::<Dna>::from_sequences( + ["ATTA", "ATTC"] + .iter() + .map(|x| EncodedSequence::encode(x).unwrap()), + ) + .unwrap(); + let pbm = cm.to_freq(0.1); + let pwm = pbm.to_weight(None); + let pssm = pwm.to_scoring(); + + striped.configure(&pssm); + let mut scores = StripedScores::empty(); + pli.score_rows_into(pssm, striped, 1..1, &mut scores); + } +} diff --git a/lightmotif/src/pli/platform/avx2.rs b/lightmotif/src/pli/platform/avx2.rs index b8f262cc3056ca5883e310f96c6e312b07c5df16..367c50209273283ef76bf19096fa75db53ce74f1 100644 --- a/lightmotif/src/pli/platform/avx2.rs +++ b/lightmotif/src/pli/platform/avx2.rs @@ -105,6 +105,8 @@ unsafe fn score_avx2_permute<A>( <<A as Alphabet>::K as IsLessOrEqual<U5>>::Output: NonZero, { let data = scores.matrix_mut(); + debug_assert!(data.rows() > 0); + let mut rowptr = data[0].as_mut_ptr(); // constant vector for comparing unknown bases let n = _mm256_set1_epi32(<A as Alphabet>::K::I32 - 1); @@ -406,7 +408,7 @@ unsafe fn stripe_avx2<A>( let mut matrix = std::mem::take(striped).into_matrix(); matrix.resize(src_stride); - // Early exit if sequence is too empty (no allocated matrix). + // Early exit if sequence is empty (no allocated matrix). if length == 0 { return; } @@ -637,12 +639,12 @@ impl Avx2 { ); } - if seq.len() < pssm.len() { + if seq.len() < pssm.len() || rows.len() == 0 { scores.resize(0, 0); return; } - scores.resize(rows.len(), seq.len() - pssm.len() + 1); + scores.resize(rows.len(), (seq.len() + 1).saturating_sub(pssm.len())); #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] unsafe { score_avx2_permute(pssm, seq, rows, scores) @@ -672,12 +674,12 @@ impl Avx2 { ); } - if seq.len() < pssm.len() { + if seq.len() < pssm.len() || rows.len() == 0 { scores.resize(0, 0); return; } - scores.resize(rows.len(), seq.len() - pssm.len() + 1); + scores.resize(rows.len(), (seq.len() + 1).saturating_sub(pssm.len())); #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] unsafe { score_avx2_gather(pssm, seq, rows, scores) diff --git a/lightmotif/src/pli/platform/neon.rs b/lightmotif/src/pli/platform/neon.rs index c7751a152aa6acf96777e2a30641d0756f390314..bc1eb3aba8ac32e225db9b886c364bc8f943e4a9 100644 --- a/lightmotif/src/pli/platform/neon.rs +++ b/lightmotif/src/pli/platform/neon.rs @@ -215,12 +215,12 @@ impl Neon { ); } - if seq.len() < pssm.len() { + if seq.len() < pssm.len() || rows.len() == 0 { scores.resize(0, 0); return; } - scores.resize(rows.len(), seq.len() - pssm.len() + 1); + scores.resize(rows.len(), (seq.len() + 1).saturating_sub(pssm.len())); #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] unsafe { score_neon(pssm, seq, rows, scores); diff --git a/lightmotif/src/pli/platform/sse2.rs b/lightmotif/src/pli/platform/sse2.rs index aca5c3eac26bd0073198be9a7eb497d36bd8890f..92339d50ead2bd4dfea5d38f44f17b57fafdeaac 100644 --- a/lightmotif/src/pli/platform/sse2.rs +++ b/lightmotif/src/pli/platform/sse2.rs @@ -205,12 +205,12 @@ impl Sse2 { ); } - if seq.len() < pssm.len() { + if seq.len() < pssm.len() || rows.len() == 0 { scores.resize(0, 0); return; } - scores.resize(rows.len(), seq.len() - pssm.len() + 1); + scores.resize(rows.len(), (seq.len() + 1).saturating_sub(pssm.len())); #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] unsafe { score_sse2(pssm, seq, rows, scores); diff --git a/lightmotif/src/scan.rs b/lightmotif/src/scan.rs index adb2fe3cd904a33b70ba350c1c20a4be226b97fa..ce4444ea2e45210efa595162d72d307d00286454 100644 --- a/lightmotif/src/scan.rs +++ b/lightmotif/src/scan.rs @@ -4,6 +4,8 @@ use super::pli::dispatch::Dispatch; use super::pli::platform::Backend; use super::pwm::ScoringMatrix; use super::seq::StripedSequence; +use crate::dense::DenseMatrix; +use crate::num::Unsigned; use crate::pli::Maximum; use crate::pli::Pipeline; use crate::pli::Score; @@ -141,7 +143,8 @@ where fn next(&mut self) -> Option<Self::Item> { while self.hits.is_empty() && self.row < self.seq.matrix().rows() { let pli = Pipeline::dispatch(); - let end = (self.row + self.block_size).min(self.seq.matrix().rows() - self.seq.wrap()); + let end = (self.row + self.block_size) + .min(self.seq.matrix().rows().saturating_sub(self.seq.wrap())); pli.score_rows_into(&self.pssm, &self.seq, self.row..end, &mut self.scores); let matrix = self.scores.matrix(); for c in pli.threshold(&self.scores, self.threshold) { diff --git a/lightmotif/src/seq.rs b/lightmotif/src/seq.rs index 023a1d12b3d1b5392e3f2e6749122b4088ea4c7f..1880de4ba5cd9e6fd9fbefdebe4b4e1de11be495 100644 --- a/lightmotif/src/seq.rs +++ b/lightmotif/src/seq.rs @@ -337,6 +337,8 @@ impl<A: Alphabet, C: StrictlyPositive> SymbolCount<A> for &StripedSequence<A, C> } } +// -- Tests -------------------------------------------------------------------- + #[cfg(test)] mod test { use typenum::consts::U2;