From 28b186f0a481978c37032a04e1850bb5b8515771 Mon Sep 17 00:00:00 2001
From: Martin Larralde <martin.larralde@embl.de>
Date: Thu, 20 Jun 2024 13:14:12 +0200
Subject: [PATCH] Make `Score` pipeline trait generic over the score type

---
 lightmotif-bench/dna.rs                |  2 +-
 lightmotif/benches/score.rs            |  4 +-
 lightmotif/benches/threshold.rs        |  2 +-
 lightmotif/src/dense.rs                |  8 ++++
 lightmotif/src/pli/dispatch.rs         | 13 +++---
 lightmotif/src/pli/mod.rs              | 47 +++++++++++-----------
 lightmotif/src/pli/platform/avx2.rs    | 55 ++++++++++++++------------
 lightmotif/src/pli/platform/generic.rs |  9 +++--
 lightmotif/src/pli/platform/sse2.rs    | 19 ++++-----
 lightmotif/src/pwm.rs                  |  2 +-
 lightmotif/src/scan.rs                 |  4 +-
 lightmotif/tests/dna.rs                |  8 ++--
 12 files changed, 95 insertions(+), 78 deletions(-)

diff --git a/lightmotif-bench/dna.rs b/lightmotif-bench/dna.rs
index 8a2ef8c..7a1612d 100644
--- a/lightmotif-bench/dna.rs
+++ b/lightmotif-bench/dna.rs
@@ -66,7 +66,7 @@ fn bench_scanner_best(bencher: &mut test::Bencher) {
     bencher.bytes = seq.len() as u64;
 }
 
-fn bench_lightmotif<C: StrictlyPositive, P: Score<Dna, C> + Maximum<f32, C>>(
+fn bench_lightmotif<C: StrictlyPositive, P: Score<f32, Dna, C> + Maximum<f32, C>>(
     bencher: &mut test::Bencher,
     pli: &P,
 ) {
diff --git a/lightmotif/benches/score.rs b/lightmotif/benches/score.rs
index 7bdc543..54c16ea 100644
--- a/lightmotif/benches/score.rs
+++ b/lightmotif/benches/score.rs
@@ -20,7 +20,7 @@ mod dna {
 
     const SEQUENCE: &str = include_str!("ecoli.txt");
 
-    fn bench<C: StrictlyPositive, P: Score<Dna, C>>(bencher: &mut test::Bencher, pli: &P) {
+    fn bench<C: StrictlyPositive, P: Score<f32, Dna, C>>(bencher: &mut test::Bencher, pli: &P) {
         let encoded = EncodedSequence::<Dna>::encode(SEQUENCE).unwrap();
         let mut striped = Pipeline::generic().stripe(encoded);
 
@@ -89,7 +89,7 @@ mod protein {
 
     const SEQUENCE: &str = include_str!("abyB1.txt");
 
-    fn bench<C: StrictlyPositive, P: Score<Protein, C>>(bencher: &mut test::Bencher, pli: &P) {
+    fn bench<C: StrictlyPositive, P: Score<f32, Protein, C>>(bencher: &mut test::Bencher, pli: &P) {
         let encoded = EncodedSequence::<Protein>::encode(SEQUENCE).unwrap();
         let mut striped = Pipeline::generic().stripe(encoded);
 
diff --git a/lightmotif/benches/threshold.rs b/lightmotif/benches/threshold.rs
index 0124978..f377422 100644
--- a/lightmotif/benches/threshold.rs
+++ b/lightmotif/benches/threshold.rs
@@ -17,7 +17,7 @@ use lightmotif::seq::EncodedSequence;
 
 const SEQUENCE: &str = include_str!("ecoli.txt");
 
-fn bench<C: StrictlyPositive, P: Score<Dna, C> + Threshold<f32, C>>(
+fn bench<C: StrictlyPositive, P: Score<f32, Dna, C> + Threshold<f32, C>>(
     bencher: &mut test::Bencher,
     pli: &P,
 ) {
diff --git a/lightmotif/src/dense.rs b/lightmotif/src/dense.rs
index c72316c..6b3b6d8 100644
--- a/lightmotif/src/dense.rs
+++ b/lightmotif/src/dense.rs
@@ -209,6 +209,14 @@ impl<T: MatrixElement, C: Unsigned, A: Unsigned + PowerOfTwo> DenseMatrix<T, C,
     }
 }
 
+impl<T: MatrixElement, C: Unsigned, A: Unsigned + PowerOfTwo> AsRef<DenseMatrix<T, C, A>>
+    for DenseMatrix<T, C, A>
+{
+    fn as_ref(&self) -> &DenseMatrix<T, C, A> {
+        self
+    }
+}
+
 impl<T: MatrixElement, C: Unsigned, A: Unsigned + PowerOfTwo> Clone for DenseMatrix<T, C, A> {
     fn clone(&self) -> Self {
         let mut clone = unsafe { Self::uninitialized(self.rows) };
diff --git a/lightmotif/src/pli/dispatch.rs b/lightmotif/src/pli/dispatch.rs
index 8d548cb..4c6af58 100644
--- a/lightmotif/src/pli/dispatch.rs
+++ b/lightmotif/src/pli/dispatch.rs
@@ -16,6 +16,7 @@ use super::Threshold;
 use crate::abc::Alphabet;
 use crate::abc::Dna;
 use crate::abc::Protein;
+use crate::dense::DenseMatrix;
 use crate::dense::MatrixCoordinates;
 use crate::dense::MatrixElement;
 use crate::err::InvalidSymbol;
@@ -73,7 +74,7 @@ impl<A: Alphabet> Encode<A> for Pipeline<A, Dispatch> {
     }
 }
 
-impl Score<Dna, <Dispatch as Backend>::LANES> for Pipeline<Dna, Dispatch> {
+impl Score<f32, Dna, <Dispatch as Backend>::LANES> for Pipeline<Dna, Dispatch> {
     fn score_rows_into<S, M>(
         &self,
         pssm: M,
@@ -82,7 +83,7 @@ impl Score<Dna, <Dispatch as Backend>::LANES> for Pipeline<Dna, Dispatch> {
         scores: &mut StripedScores<f32, <Dispatch as Backend>::LANES>,
     ) where
         S: AsRef<StripedSequence<Dna, <Dispatch as Backend>::LANES>>,
-        M: AsRef<ScoringMatrix<Dna>>,
+        M: AsRef<DenseMatrix<f32, <Dna as Alphabet>::K>>,
     {
         match self.backend {
             #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
@@ -91,7 +92,7 @@ impl Score<Dna, <Dispatch as Backend>::LANES> for Pipeline<Dna, Dispatch> {
             Dispatch::Sse2 => Sse2::score_rows_into(pssm, seq.as_ref(), rows, scores),
             #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
             Dispatch::Neon => Neon::score_rows_into(pssm, seq.as_ref(), rows, scores),
-            _ => <Generic as Score<Dna, <Dispatch as Backend>::LANES>>::score_rows_into(
+            _ => <Generic as Score<f32, Dna, <Dispatch as Backend>::LANES>>::score_rows_into(
                 &Generic,
                 pssm,
                 seq.as_ref(),
@@ -102,7 +103,7 @@ impl Score<Dna, <Dispatch as Backend>::LANES> for Pipeline<Dna, Dispatch> {
     }
 }
 
-impl Score<Protein, <Dispatch as Backend>::LANES> for Pipeline<Protein, Dispatch> {
+impl Score<f32, Protein, <Dispatch as Backend>::LANES> for Pipeline<Protein, Dispatch> {
     fn score_rows_into<S, M>(
         &self,
         pssm: M,
@@ -111,7 +112,7 @@ impl Score<Protein, <Dispatch as Backend>::LANES> for Pipeline<Protein, Dispatch
         scores: &mut StripedScores<f32, <Dispatch as Backend>::LANES>,
     ) where
         S: AsRef<StripedSequence<Protein, <Dispatch as Backend>::LANES>>,
-        M: AsRef<ScoringMatrix<Protein>>,
+        M: AsRef<DenseMatrix<f32, <Protein as Alphabet>::K>>,
     {
         match self.backend {
             #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
@@ -120,7 +121,7 @@ impl Score<Protein, <Dispatch as Backend>::LANES> for Pipeline<Protein, Dispatch
             Dispatch::Sse2 => Sse2::score_rows_into(pssm, seq.as_ref(), rows, scores),
             #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
             Dispatch::Neon => Neon::score_rows_into(pssm, seq.as_ref(), rows, scores),
-            _ => <Generic as Score<Protein, <Dispatch as Backend>::LANES>>::score_rows_into(
+            _ => <Generic as Score<f32, Protein, <Dispatch as Backend>::LANES>>::score_rows_into(
                 &Generic,
                 pssm,
                 seq.as_ref(),
diff --git a/lightmotif/src/pli/mod.rs b/lightmotif/src/pli/mod.rs
index 5f06537..5c01662 100644
--- a/lightmotif/src/pli/mod.rs
+++ b/lightmotif/src/pli/mod.rs
@@ -1,5 +1,7 @@
 //! Concrete implementations of the sequence scoring pipeline.
 
+use std::ops::Add;
+use std::ops::AddAssign;
 use std::ops::Range;
 
 use crate::abc::Alphabet;
@@ -67,35 +69,35 @@ pub trait Encode<A: Alphabet> {
 }
 
 /// Used computing sequence scores with a PSSM.
-pub trait Score<A: Alphabet, C: StrictlyPositive> {
+pub trait Score<T: MatrixElement + AddAssign, A: Alphabet, C: StrictlyPositive> {
     /// Compute the PSSM scores into the given striped score matrix.
     fn score_rows_into<S, M>(
         &self,
         pssm: M,
         seq: S,
         rows: Range<usize>,
-        scores: &mut StripedScores<f32, C>,
+        scores: &mut StripedScores<T, C>,
     ) where
         S: AsRef<StripedSequence<A, C>>,
-        M: AsRef<ScoringMatrix<A>>,
+        M: AsRef<DenseMatrix<T, A::K>>,
     {
         let seq = seq.as_ref();
         let pssm = pssm.as_ref();
 
-        if seq.len() < pssm.len() || rows.len() == 0 {
+        if seq.len() < pssm.rows() || rows.len() == 0 {
             scores.resize(0, 0);
             return;
         }
 
         // FIXME?
-        scores.resize(rows.len(), (seq.len() + 1).saturating_sub(pssm.len()));
+        scores.resize(rows.len(), (seq.len() + 1).saturating_sub(pssm.rows()));
 
         let result = scores.matrix_mut();
-        let matrix = pssm.matrix();
+        let matrix = pssm;
 
         for (res_row, seq_row) in rows.enumerate() {
             for col in 0..C::USIZE {
-                let mut score = 0.0;
+                let mut score = T::default();
                 for (j, pssm_row) in matrix.iter().enumerate() {
                     let symbol = seq.matrix()[seq_row + j][col];
                     score += pssm_row[symbol.as_index()];
@@ -106,25 +108,23 @@ pub trait Score<A: Alphabet, C: StrictlyPositive> {
     }
 
     /// Compute the PSSM scores into the given striped score matrix.
-    fn score_into<S, M>(&self, pssm: M, seq: S, scores: &mut StripedScores<f32, C>)
+    fn score_into<S, M>(&self, pssm: M, seq: S, scores: &mut StripedScores<T, C>)
     where
         S: AsRef<StripedSequence<A, C>>,
-        M: AsRef<ScoringMatrix<A>>,
+        M: AsRef<DenseMatrix<T, A::K>>,
     {
         let s = seq.as_ref();
-        let m = pssm.as_ref();
         let rows = s.matrix().rows() - s.wrap();
-        Self::score_rows_into(&self, m, s, 0..rows, scores)
+        Self::score_rows_into(&self, pssm, s, 0..rows, scores)
     }
 
     /// Compute the PSSM scores for every sequence positions.
-    fn score<S, M>(&self, pssm: M, seq: S) -> StripedScores<f32, C>
+    fn score<S, M>(&self, pssm: M, seq: S) -> StripedScores<T, C>
     where
         S: AsRef<StripedSequence<A, C>>,
-        M: AsRef<ScoringMatrix<A>>,
+        M: AsRef<DenseMatrix<T, A::K>>,
     {
         let seq = seq.as_ref();
-        let pssm = pssm.as_ref();
         let mut scores = StripedScores::empty();
         self.score_into(pssm, seq, &mut scores);
         scores
@@ -242,7 +242,10 @@ impl<A: Alphabet> Pipeline<A, Generic> {
 
 impl<A: Alphabet> Encode<A> for Pipeline<A, Generic> {}
 
-impl<A: Alphabet, C: StrictlyPositive> Score<A, C> for Pipeline<A, Generic> {}
+impl<T: MatrixElement + AddAssign, A: Alphabet, C: StrictlyPositive> Score<T, A, C>
+    for Pipeline<A, Generic>
+{
+}
 
 impl<T: MatrixElement + PartialOrd, A: Alphabet, C: StrictlyPositive> Maximum<T, C>
     for Pipeline<A, Generic>
@@ -320,7 +323,7 @@ impl<A: Alphabet> Pipeline<A, Sse2> {
 
 impl<A: Alphabet> Encode<A> for Pipeline<A, Sse2> {}
 
-impl<A, C> Score<A, C> for Pipeline<A, Sse2>
+impl<A, C> Score<f32, A, C> for Pipeline<A, Sse2>
 where
     A: Alphabet,
     C: StrictlyPositive + MultipleOf<U16>,
@@ -333,7 +336,7 @@ where
         scores: &mut StripedScores<f32, C>,
     ) where
         S: AsRef<StripedSequence<A, C>>,
-        M: AsRef<ScoringMatrix<A>>,
+        M: AsRef<DenseMatrix<f32, A::K>>,
     {
         Sse2::score_rows_into(pssm, seq, rows, scores)
     }
@@ -379,7 +382,7 @@ impl<A: Alphabet> Encode<A> for Pipeline<A, Avx2> {
     }
 }
 
-impl Score<Dna, <Avx2 as Backend>::LANES> for Pipeline<Dna, Avx2> {
+impl Score<f32, Dna, <Avx2 as Backend>::LANES> for Pipeline<Dna, Avx2> {
     fn score_rows_into<S, M>(
         &self,
         pssm: M,
@@ -388,13 +391,13 @@ impl Score<Dna, <Avx2 as Backend>::LANES> for Pipeline<Dna, Avx2> {
         scores: &mut StripedScores<f32, <Avx2 as Backend>::LANES>,
     ) where
         S: AsRef<StripedSequence<Dna, <Avx2 as Backend>::LANES>>,
-        M: AsRef<ScoringMatrix<Dna>>,
+        M: AsRef<DenseMatrix<f32, <Dna as Alphabet>::K>>,
     {
         Avx2::score_rows_into_permute(pssm, seq, rows, scores)
     }
 }
 
-impl Score<Protein, <Avx2 as Backend>::LANES> for Pipeline<Protein, Avx2> {
+impl Score<f32, Protein, <Avx2 as Backend>::LANES> for Pipeline<Protein, Avx2> {
     fn score_rows_into<S, M>(
         &self,
         pssm: M,
@@ -403,7 +406,7 @@ impl Score<Protein, <Avx2 as Backend>::LANES> for Pipeline<Protein, Avx2> {
         scores: &mut StripedScores<f32, <Avx2 as Backend>::LANES>,
     ) where
         S: AsRef<StripedSequence<Protein, <Avx2 as Backend>::LANES>>,
-        M: AsRef<ScoringMatrix<Protein>>,
+        M: AsRef<DenseMatrix<f32, <Protein as Alphabet>::K>>,
     {
         Avx2::score_rows_into_gather(pssm, seq, rows, scores)
     }
@@ -458,7 +461,7 @@ impl<A: Alphabet> Encode<A> for Pipeline<A, Neon> {
     }
 }
 
-impl<A, C> Score<A, C> for Pipeline<A, Neon>
+impl<A, C> Score<f32, A, C> for Pipeline<A, Neon>
 where
     A: Alphabet,
     C: StrictlyPositive + MultipleOf<U16>,
diff --git a/lightmotif/src/pli/platform/avx2.rs b/lightmotif/src/pli/platform/avx2.rs
index ccec78b..4842dc8 100644
--- a/lightmotif/src/pli/platform/avx2.rs
+++ b/lightmotif/src/pli/platform/avx2.rs
@@ -6,24 +6,25 @@ use std::arch::x86::*;
 use std::arch::x86_64::*;
 use std::ops::Range;
 
-use typenum::consts::U32;
-use typenum::consts::U5;
-use typenum::consts::U8;
-use typenum::IsLessOrEqual;
-use typenum::NonZero;
-use typenum::Unsigned;
-
-use super::Backend;
 use crate::abc::Alphabet;
 use crate::abc::Symbol;
+use crate::dense::DenseMatrix;
 use crate::dense::MatrixCoordinates;
 use crate::err::InvalidSymbol;
+use crate::num::IsLessOrEqual;
+use crate::num::NonZero;
+use crate::num::Unsigned;
+use crate::num::U32;
+use crate::num::U5;
+use crate::num::U8;
 use crate::pli::Encode;
 use crate::pli::Pipeline;
 use crate::pwm::ScoringMatrix;
 use crate::scores::StripedScores;
 use crate::seq::StripedSequence;
 
+use super::Backend;
+
 /// A marker type for the AVX2 implementation of the pipeline.
 #[derive(Clone, Debug, Default)]
 pub struct Avx2;
@@ -96,7 +97,7 @@ where
 #[target_feature(enable = "avx2")]
 #[allow(overflowing_literals)]
 unsafe fn score_avx2_permute<A>(
-    pssm: &ScoringMatrix<A>,
+    pssm: &DenseMatrix<f32, A::K>,
     seq: &StripedSequence<A, <Avx2 as Backend>::LANES>,
     rows: Range<usize>,
     scores: &mut StripedScores<f32, <Avx2 as Backend>::LANES>,
@@ -105,6 +106,8 @@ unsafe fn score_avx2_permute<A>(
     <A as Alphabet>::K: IsLessOrEqual<U8>,
     <<A as Alphabet>::K as IsLessOrEqual<U8>>::Output: NonZero,
 {
+    use crate::dense::DenseMatrix;
+
     let data = scores.matrix_mut();
     debug_assert!(data.rows() > 0);
 
@@ -141,9 +144,9 @@ unsafe fn score_avx2_permute<A>(
         let mut s4 = _mm256_setzero_ps();
         // reset pointers to row
         let mut seqptr = seq.matrix()[i].as_ptr();
-        let mut pssmptr = pssm.matrix()[0].as_ptr();
+        let mut pssmptr = pssm[0].as_ptr();
         // advance position in the position weight matrix
-        for _ in 0..pssm.len() {
+        for _ in 0..pssm.rows() {
             // load sequence row and broadcast to f32
             debug_assert_eq!(seqptr as usize & 0x1f, 0);
             let x = _mm256_load_si256(seqptr as *const __m256i);
@@ -167,7 +170,7 @@ unsafe fn score_avx2_permute<A>(
             s4 = _mm256_add_ps(s4, b4);
             // advance to next row in PSSM and sequence matrices
             seqptr = seqptr.add(seq.matrix().stride());
-            pssmptr = pssmptr.add(pssm.matrix().stride());
+            pssmptr = pssmptr.add(pssm.stride());
         }
         // permute lanes so that scores are in the right order
         let r1 = _mm256_permute2f128_ps(s1, s2, 0x20);
@@ -187,7 +190,7 @@ unsafe fn score_avx2_permute<A>(
 #[target_feature(enable = "avx2")]
 #[allow(overflowing_literals)]
 unsafe fn score_avx2_gather<A>(
-    pssm: &ScoringMatrix<A>,
+    pssm: &DenseMatrix<f32, A::K>,
     seq: &StripedSequence<A, <Avx2 as Backend>::LANES>,
     rows: Range<usize>,
     scores: &mut StripedScores<f32, <Avx2 as Backend>::LANES>,
@@ -226,9 +229,9 @@ unsafe fn score_avx2_gather<A>(
         let mut s4 = _mm256_setzero_ps();
         // reset pointers to row
         let mut seqptr = seq.matrix()[i].as_ptr();
-        let mut pssmptr = pssm.matrix()[0].as_ptr();
+        let mut pssmptr = pssm[0].as_ptr();
         // advance position in the position weight matrix
-        for _ in 0..pssm.len() {
+        for _ in 0..pssm.rows() {
             // load sequence row and broadcast to f32
             debug_assert_eq!(seqptr as usize & 0x1f, 0);
             let x = _mm256_load_si256(seqptr as *const __m256i);
@@ -248,7 +251,7 @@ unsafe fn score_avx2_gather<A>(
             s4 = _mm256_add_ps(s4, b4);
             // advance to next row in PSSM and sequence matrices
             seqptr = seqptr.add(seq.matrix().stride());
-            pssmptr = pssmptr.add(pssm.matrix().stride());
+            pssmptr = pssmptr.add(pssm.stride());
         }
         // permute lanes so that scores are in the right order
         let r1 = _mm256_permute2f128_ps(s1, s2, 0x20);
@@ -621,24 +624,24 @@ impl Avx2 {
         <A as Alphabet>::K: IsLessOrEqual<U8>,
         <<A as Alphabet>::K as IsLessOrEqual<U8>>::Output: NonZero,
         S: AsRef<StripedSequence<A, <Avx2 as Backend>::LANES>>,
-        M: AsRef<ScoringMatrix<A>>,
+        M: AsRef<DenseMatrix<f32, A::K>>,
     {
         let seq = seq.as_ref();
         let pssm = pssm.as_ref();
 
-        if seq.wrap() < pssm.len() - 1 {
+        if seq.wrap() < pssm.rows() - 1 {
             panic!(
                 "not enough wrapping rows for motif of length {}",
-                pssm.len()
+                pssm.rows()
             );
         }
 
-        if seq.len() < pssm.len() || rows.len() == 0 {
+        if seq.len() < pssm.rows() || rows.len() == 0 {
             scores.resize(0, 0);
             return;
         }
 
-        scores.resize(rows.len(), (seq.len() + 1).saturating_sub(pssm.len()));
+        scores.resize(rows.len(), (seq.len() + 1).saturating_sub(pssm.rows()));
         #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
         unsafe {
             score_avx2_permute(pssm, seq, rows, scores)
@@ -656,24 +659,24 @@ impl Avx2 {
     ) where
         A: Alphabet,
         S: AsRef<StripedSequence<A, <Avx2 as Backend>::LANES>>,
-        M: AsRef<ScoringMatrix<A>>,
+        M: AsRef<DenseMatrix<f32, A::K>>,
     {
         let seq = seq.as_ref();
         let pssm = pssm.as_ref();
 
-        if seq.wrap() < pssm.len() - 1 {
+        if seq.wrap() < pssm.rows() - 1 {
             panic!(
                 "not enough wrapping rows for motif of length {}",
-                pssm.len()
+                pssm.rows()
             );
         }
 
-        if seq.len() < pssm.len() || rows.len() == 0 {
+        if seq.len() < pssm.rows() || rows.len() == 0 {
             scores.resize(0, 0);
             return;
         }
 
-        scores.resize(rows.len(), (seq.len() + 1).saturating_sub(pssm.len()));
+        scores.resize(rows.len(), (seq.len() + 1).saturating_sub(pssm.rows()));
         #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
         unsafe {
             score_avx2_gather(pssm, seq, rows, scores)
diff --git a/lightmotif/src/pli/platform/generic.rs b/lightmotif/src/pli/platform/generic.rs
index eac1aa7..365374d 100644
--- a/lightmotif/src/pli/platform/generic.rs
+++ b/lightmotif/src/pli/platform/generic.rs
@@ -1,16 +1,17 @@
-use typenum::consts::U1;
+use std::ops::AddAssign;
 
-use super::Backend;
 use crate::abc::Alphabet;
 use crate::dense::MatrixElement;
 use crate::num::StrictlyPositive;
-
+use crate::num::U1;
 use crate::pli::Encode;
 use crate::pli::Maximum;
 use crate::pli::Score;
 use crate::pli::Stripe;
 use crate::pli::Threshold;
 
+use super::Backend;
+
 /// A marker type for the generic implementation of the pipeline.
 #[derive(Clone, Debug, Default)]
 pub struct Generic;
@@ -21,7 +22,7 @@ impl Backend for Generic {
 
 impl<A: Alphabet> Encode<A> for Generic {}
 
-impl<A: Alphabet, C: StrictlyPositive> Score<A, C> for Generic {}
+impl<T: MatrixElement + AddAssign, A: Alphabet, C: StrictlyPositive> Score<T, A, C> for Generic {}
 
 impl<T: MatrixElement + PartialOrd, C: StrictlyPositive> Maximum<T, C> for Generic {}
 
diff --git a/lightmotif/src/pli/platform/sse2.rs b/lightmotif/src/pli/platform/sse2.rs
index 39cc5f4..4609440 100644
--- a/lightmotif/src/pli/platform/sse2.rs
+++ b/lightmotif/src/pli/platform/sse2.rs
@@ -11,6 +11,7 @@ use std::ops::Rem;
 
 use super::Backend;
 use crate::abc::Alphabet;
+use crate::dense::DenseMatrix;
 use crate::dense::MatrixCoordinates;
 use crate::num::consts::U16;
 use crate::num::MultipleOf;
@@ -31,7 +32,7 @@ impl Backend for Sse2 {
 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
 #[target_feature(enable = "sse2")]
 unsafe fn score_sse2<A: Alphabet, C: MultipleOf<<Sse2 as Backend>::LANES>>(
-    pssm: &ScoringMatrix<A>,
+    pssm: &DenseMatrix<f32, A::K>,
     seq: &StripedSequence<A, C>,
     rows: Range<usize>,
     scores: &mut StripedScores<f32, C>,
@@ -51,9 +52,9 @@ unsafe fn score_sse2<A: Alphabet, C: MultipleOf<<Sse2 as Backend>::LANES>>(
             let mut s4 = _mm_setzero_ps();
             // reset position
             let mut dataptr = seq.matrix()[i].as_ptr().add(offset);
-            let mut pssmptr = pssm.matrix()[0].as_ptr();
+            let mut pssmptr = pssm[0].as_ptr();
             // advance position in the position weight matrix
-            for _ in 0..pssm.len() {
+            for _ in 0..pssm.rows() {
                 // load sequence row and broadcast to f32
                 let x = _mm_load_si128(dataptr as *const __m128i);
                 let hi = _mm_unpackhi_epi8(x, zero);
@@ -77,7 +78,7 @@ unsafe fn score_sse2<A: Alphabet, C: MultipleOf<<Sse2 as Backend>::LANES>>(
                 }
                 // advance to next row in sequence and PSSM matrices
                 dataptr = dataptr.add(seq.matrix().stride());
-                pssmptr = pssmptr.add(pssm.matrix().stride());
+                pssmptr = pssmptr.add(pssm.stride());
             }
             // record the score for the current position
             _mm_stream_ps(rowptr.add(0x00), s1);
@@ -193,24 +194,24 @@ impl Sse2 {
         A: Alphabet,
         C: MultipleOf<<Sse2 as Backend>::LANES>,
         S: AsRef<StripedSequence<A, C>>,
-        M: AsRef<ScoringMatrix<A>>,
+        M: AsRef<DenseMatrix<f32, A::K>>,
     {
         let seq = seq.as_ref();
         let pssm = pssm.as_ref();
 
-        if seq.wrap() < pssm.len() - 1 {
+        if seq.wrap() < pssm.rows() - 1 {
             panic!(
                 "not enough wrapping rows for motif of length {}",
-                pssm.len()
+                pssm.rows()
             );
         }
 
-        if seq.len() < pssm.len() || rows.len() == 0 {
+        if seq.len() < pssm.rows() || rows.len() == 0 {
             scores.resize(0, 0);
             return;
         }
 
-        scores.resize(rows.len(), (seq.len() + 1).saturating_sub(pssm.len()));
+        scores.resize(rows.len(), (seq.len() + 1).saturating_sub(pssm.rows()));
         #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
         unsafe {
             score_sse2(pssm, seq, rows, scores);
diff --git a/lightmotif/src/pwm.rs b/lightmotif/src/pwm.rs
index 93f8b65..5550d02 100644
--- a/lightmotif/src/pwm.rs
+++ b/lightmotif/src/pwm.rs
@@ -483,7 +483,7 @@ impl<A: Alphabet> ScoringMatrix<A> {
     where
         C: StrictlyPositive,
         S: AsRef<StripedSequence<A, C>>,
-        Pipeline<A, Dispatch>: Score<A, C>,
+        Pipeline<A, Dispatch>: Score<f32, A, C>,
     {
         let pli = Pipeline::dispatch();
         pli.score(self, seq)
diff --git a/lightmotif/src/scan.rs b/lightmotif/src/scan.rs
index 51324a9..4985667 100644
--- a/lightmotif/src/scan.rs
+++ b/lightmotif/src/scan.rs
@@ -105,7 +105,7 @@ impl<'a, A: Alphabet> Scanner<'a, A> {
 
 impl<'a, A: Alphabet> Scanner<'a, A>
 where
-    Pipeline<A, Dispatch>: Score<A, C> + Maximum<f32, C>,
+    Pipeline<A, Dispatch>: Score<f32, A, C> + Maximum<f32, C>,
 {
     /// Consume the scanner to find the best hit.
     pub fn best(&mut self) -> Option<Hit> {
@@ -137,7 +137,7 @@ where
 
 impl<'a, A: Alphabet> Iterator for Scanner<'a, A>
 where
-    Pipeline<A, Dispatch>: Score<A, C> + Threshold<f32, C>,
+    Pipeline<A, Dispatch>: Score<f32, A, C> + Threshold<f32, C>,
 {
     type Item = Hit;
     fn next(&mut self) -> Option<Self::Item> {
diff --git a/lightmotif/tests/dna.rs b/lightmotif/tests/dna.rs
index 688ccd2..a6aaabf 100644
--- a/lightmotif/tests/dna.rs
+++ b/lightmotif/tests/dna.rs
@@ -36,7 +36,7 @@ const EXPECTED: &[f32] = &[
     -30.922688 , -18.678621 
 ];
 
-fn test_score_rows<C: StrictlyPositive, P: Score<Dna, C>>(pli: &P) {
+fn test_score_rows<C: StrictlyPositive, P: Score<f32, Dna, C>>(pli: &P) {
     let encoded = EncodedSequence::<Dna>::encode(SEQUENCE).unwrap();
     let mut striped = Pipeline::generic().stripe(encoded);
 
@@ -61,7 +61,7 @@ fn test_score_rows<C: StrictlyPositive, P: Score<Dna, C>>(pli: &P) {
     assert_eq!(scores.matrix()[0][0], EXPECTED[1]);
 }
 
-fn test_score<C: StrictlyPositive, P: Score<Dna, C>>(pli: &P) {
+fn test_score<C: StrictlyPositive, P: Score<f32, Dna, C>>(pli: &P) {
     let encoded = EncodedSequence::<Dna>::encode(SEQUENCE).unwrap();
     let mut striped = Pipeline::generic().stripe(encoded);
 
@@ -89,7 +89,7 @@ fn test_score<C: StrictlyPositive, P: Score<Dna, C>>(pli: &P) {
     }
 }
 
-fn test_argmax<C: StrictlyPositive, P: Score<Dna, C> + Maximum<f32, C>>(pli: &P) {
+fn test_argmax<C: StrictlyPositive, P: Score<f32, Dna, C> + Maximum<f32, C>>(pli: &P) {
     let encoded = EncodedSequence::<Dna>::encode(SEQUENCE).unwrap();
     let mut striped = Pipeline::generic().stripe(encoded);
 
@@ -106,7 +106,7 @@ fn test_argmax<C: StrictlyPositive, P: Score<Dna, C> + Maximum<f32, C>>(pli: &P)
     assert_eq!(pli.argmax(&result).map(|c| result.offset(c)), Some(18));
 }
 
-fn test_threshold<C: StrictlyPositive, P: Score<Dna, C> + Threshold<f32, C>>(pli: &P) {
+fn test_threshold<C: StrictlyPositive, P: Score<f32, Dna, C> + Threshold<f32, C>>(pli: &P) {
     let encoded = EncodedSequence::<Dna>::encode(SEQUENCE).unwrap();
     let mut striped = Pipeline::generic().stripe(encoded);
 
-- 
GitLab