rustyhmmer 0.1.1

Pure-Rust HMMER3 hmmsearch with byte-identical --tblout output
Documentation














use crate::alphabet::{AMINO_FREQ, K, KP};
use crate::hmmfile::P7Hmm;
use crate::msv::null_one;



fn degen_set(code: usize) -> &'static [usize] {
    match code {
        21 => &[11, 2],  
        22 => &[7, 9],   
        23 => &[13, 3],  
        24 => &[8],      
        25 => &[1],      
        26 => &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], 
        _ => &[],
    }
}

pub struct BiasFilter {
    
    eo: Vec<[f32; 2]>,
    
    t1: [f32; 3],
    
    pi: [f32; 2],
}

impl BiasFilter {
    
    
    pub fn build(hmm: &P7Hmm) -> Option<Self> {
        let compo = hmm.compo?; 
        let l1 = hmm.m as f32 / 8.0; 

        
        
        let mut eo = vec![[1.0f32; 2]; KP];
        for x in 0..K {
            
            eo[x][0] = 1.0;
            eo[x][1] = compo[x] / AMINO_FREQ[x] as f32;
        }
        
        eo[K] = [1.0, 1.0];
        eo[KP - 2] = [1.0, 1.0];
        eo[KP - 1] = [1.0, 1.0];
        
        for code in (K + 1)..=(KP - 3) {
            let set = degen_set(code);
            
            let (mut n0, mut n1, mut den) = (0.0f32, 0.0f32, 0.0f32);
            for &y in set {
                n0 += AMINO_FREQ[y] as f32; 
                n1 += compo[y]; 
                den += AMINO_FREQ[y] as f32; 
            }
            eo[code][0] = if den > 0.0 { n0 / den } else { 0.0 };
            eo[code][1] = if den > 0.0 { n1 / den } else { 0.0 };
        }

        
        let t1 = [1.0 / (l1 + 1.0), l1 / (l1 + 1.0), 1.0];

        Some(BiasFilter { eo, t1, pi: [0.999, 0.001] })
    }

    
    
    pub fn filter_score(&self, dsq: &[u8], l: usize) -> f32 {
        if l == 0 {
            return 0.0;
        }
        
        let p1 = l as f32 / (l as f32 + 1.0);
        let t: [[f32; 3]; 2] = [[p1, 1.0 - p1, 1.0], self.t1];

        
        let x1 = dsq[1] as usize;
        let mut dp = [self.eo[x1][0] * self.pi[0], self.eo[x1][1] * self.pi[1]];
        let mut max = dp[0].max(dp[1]);
        dp[0] /= max;
        dp[1] /= max;
        let mut logsc = (max as f64).ln() as f32;

        
        for i in 2..=l {
            let x = dsq[i] as usize;
            let mut nd = [0.0f32; 2];
            for k in 0..2 {
                let mut s = 0.0f32;
                for m in 0..2 {
                    s += dp[m] * t[m][k];
                }
                nd[k] = s * self.eo[x][k];
            }
            max = nd[0].max(nd[1]);
            dp[0] = nd[0] / max;
            dp[1] = nd[1] / max;
            logsc += (max as f64).ln() as f32;
        }

        
        let end = dp[0] * t[0][2] + dp[1] * t[1][2];
        logsc += (end as f64).ln() as f32;

        
        
        logsc + null_one(l)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::msv::MsvProfile;
    use crate::seqio::read_fasta;

    #[test]
    fn bias_passes_all_golden_hits() {
        
        let hmm = P7Hmm::read_all(&format!("{}/testdata/globins4.hmm", env!("CARGO_MANIFEST_DIR")))
            .unwrap()
            .pop()
            .unwrap();
        let mp = MsvProfile::build(&hmm);
        let bf = BiasFilter::build(&hmm).expect("globins4 has COMPO");
        let seqs = read_fasta(&format!("{}/testdata/globins45.fa", env!("CARGO_MANIFEST_DIR")))
            .unwrap();
        let f1 = 0.02_f64;
        let mut n_pass = 0;
        for s in &seqs {
            let l = s.len();
            let usc = mp.msv_score(&s.dsq, l);
            
            if mp.pvalue(usc, crate::msv::null_one(l)) > f1 {
                continue;
            }
            let filtersc = bf.filter_score(&s.dsq, l);
            if mp.pvalue(usc, filtersc) <= f1 {
                n_pass += 1;
            }
        }
        assert_eq!(n_pass, 45, "bias filter pass count {n_pass} != C's 45");
    }
}