packages feed

MC-Fold-DP-0.1.0.0: BioInf/MCFoldDP.hs

{-# LANGUAGE RecordWildCards #-}

-- |

module BioInf.MCFoldDP where

import Control.Arrow (first,second)
import Control.Monad
import Control.Monad.ST
import Data.List (find,sort)
import Data.Tuple.Select -- (sel5)
import qualified Data.Vector.Unboxed as VU

import Biobase.DataSource.MCFold
import Biobase.Constants
import Biobase.RNA
import Biobase.RNA.Hashes
import Biobase.RNA.NucBounds
import Biobase.Structure
import Data.PrimitiveArray
import Data.PrimitiveArray.Ix



-- | Folding wrapper

fold :: MotifDB -> Primary -> Tables
fold db inp = runST $ foldST db inp

-- | Folding in the ST monad. the number of dncm tables is the same as the
-- number of known double NCMs.

foldST :: MotifDB -> Primary -> ST s Tables
foldST db inp = do
  let n = VU.length inp -1
  (sncm,sncmM)     <- mkTable2 n
  (dncms,dncmMs)   <- liftM unzip . mapM (const $ mkTable2 n) $ VU.toList knownDoubleNCM
  (tmp,tmpM)       <- mkTable2 n
  (mbr,mbrM)       <- mkTable2 n
  (mbr1,mbr1M)     <- mkTable2 n
  (extern,externM) <- mkTable2With 0 n
  forM_ [n,n-1 .. 0] $ \i -> forM_ [i,i+1 .. n] $ \j -> do

    -- single NCM calculation, together with multibranched loops / large
    -- interior loops
    --
    -- TODO check if large interior loops are ok?! Do we even want those, or
    -- should they come solely from NCMs? Have an option for that
    let sncmIJ = fncmSingle db inp i j
    -- let multiIJ = if j-i-1>0 then (VU.minimum . VU.map (\k -> mbr!(i+1,k)+mbr1!(k+1,j-1)) $ VU.enumFromN (i+1) (j-i-1)) else eInf
    let multiIJ = vuminimum $ fMulti db inp i j mbr mbr1 -- if j-i-1>0 then VU.minimum . VU.map snd $ fMulti db inp i j mbr mbr1 else eInf
    let interiorIJ = vuminimum $ fInterior db inp i j tmp -- VU.minimum . VU.map snd $ fInterior db inp i j tmp
    writeM sncmM (i,j) $ minimum [sncmIJ, multiIJ, interiorIJ]
    -- double NCM calculation
    forM (zip3 dncms dncmMs $ VU.toList knownDoubleNCM) $ \(dncm, dncmM, ((di,dj),_)) -> do
      let k = i+di-1
      let l = j-dj+1
      -- (1) Begin a stem (dNCM follows sNCM)
      let dsIJ = fncmDS db inp i j k l sncm
      -- (2) continue a stem (dNCM follows dNCM)
      let ddIJ = VU.minimum . VU.map snd $ fncmDD db inp i j k l dncms
      writeM dncmM (i,j) $ dsIJ `min` ddIJ

    -- fill helper table which makes mbr calculations ~2x faster
    writeM tmpM (i,j) $ minimum $ map (!(i,j)) dncms

    -- fill mbr table
    let upIJ = if (i+1<j) then mbr!(i,j-1) else eInf
    let stemIJ = vuminimum $ fMStem i j tmp -- if (i<j) then VU.minimum . VU.map snd $ fMStem i j tmp else eInf
    -- let mbrstemIJ = if (i+2<j) then VU.minimum $ VU.map (\k -> mbr!(i,k) + tmp!(k+1,j)) $ VU.enumFromN (i+1) (j-i-1) else eInf
    let mbrstemIJ = if (i+2<j) then VU.minimum . VU.map snd $ fMMbrStem i j mbr tmp else eInf
    writeM mbrM (i,j) $ minimum [upIJ,stemIJ,mbrstemIJ]

    -- fill mbr1 table
    let stem1IJ = tmp!(i,j) -- just a mnemonic
    let up1IJ = if (i+1<j) then mbr1!(i,j-1) else eInf
    writeM mbr1M (i,j) $ stem1IJ `min` up1IJ

  -- fill extern table
  let j=n
  forM_ [n-1,n-2..0] $ \i -> do
    let unpairedExt = extern!(i+1,j)
    let stemExtExt = if (i+2<j) then (VU.minimum . VU.map sel3 $ fStemExtExt dncms extern i j) else eInf
    let stemExt = if (i+2<j) then (VU.minimum . VU.map sel3 $ fStemExt dncms i j) else eInf
    writeM externM (i,j) $ minimum [unpairedExt, stemExtExt, stemExt] --, sncm!(i,j)]

  return (sncm,dncms,mbr,mbr1,extern)

-- | Backtracking suboptimal results

-- backtrack :: MotifDB -> Double -> Primary -> Tables -> [(Double,Secondary)]
backtrack db delta inp (sncm,dncms,mbr,mbr1,extern) = outp where
  outp
    | bE >= -0.0001 = [(0,Secondary (n+1) [])]
    | otherwise = map (first (bE+delta-) . second (Secondary (n+1) . sort)) $ externbt delta 0 n
  externbt d i j =
    -- unpaired nucleotide to the left
    [ (e,x)
    | i<j-1
    , let bestE = extern ! (i+1,j)
    , let d' = extern ! (i,j) - bestE + d
    , d'>=0
    , (e,x) <- externbt d' (i+1) j
    ] ++
    -- stem
    [ (e,x)
    | i<j
    , (idx,k,bestE) <- VU.toList $ fStemExt dncms i j
    , let d' = extern!(i,j) - bestE + d
    , d'>=0
    , (e,x) <- dncmbt d' idx i k
    ] ++
    -- two or more stems
    [ (ey,x++y)
    | i<j
    , (idx,k,bestE) <- VU.toList $ fStemExtExt dncms extern i j
    , let d' = extern!(i,j) - bestE + d
    , d'>=0
    , (ex,x) <- dncmbt d' idx i k
    , ex>=0
    , (ey,y) <- externbt ex (k+1) j
    , ey>=0
    ]
  dncmbt d idx i j =
    -- D-D
    [ (e,(i,j):x)
    | let (di,dj) = fst $ knownDoubleNCM VU.! idx
    , let dncm = dncms!!idx
    , let k = i+di-1, let l = j-dj+1
    , (nidx,bestE) <- VU.toList $ fncmDD db inp i j k l dncms
    , let d' = dncm!(i,j) - bestE + d
    , d'>=0
    , (e,x) <- dncmbt d' nidx k l
    ] ++
    -- D-S
    [ (e,(i,j):x)
    | let (di,dj) = fst $ knownDoubleNCM VU.! idx
    , let dncm = dncms!!idx
    , let k = i+di-1, let l = j-dj+1
    , let bestE = fncmDS db inp i j k l sncm
    , let d' = dncm!(i,j) - bestE + d
    , d'>=0
    , (e,x) <- sncmbt d' k l
    ]
  sncmbt d i j =
    [ (d',[(i,j)])
    | j-i>=3
    , let bestE = fncmSingle db inp i j
    , let d' = sncm!(i,j) - bestE + d
    , d'>=0
    ] ++
    -- sNCM enclosing a multibranched loop
    [ (ey,(i,j):x++y)
    | j-i>3
    , (k,bestE) <- VU.toList $ fMulti db inp i j mbr mbr1
    , let d' = sncm!(i,j) - bestE + d
    , d'>=0
    , (ex,x) <- mbrbt d' (i+1) k
    , ex>=0
    , (ey,y) <- mbr1bt ex (k+1) (j-1)
    ] ++
    -- sNCM inclosing an interior loop
    [ (e,(i,j):x)
    | idx <- [0 .. VU.length knownDoubleNCM -1]
    , let dncm = dncms!!idx
    , ((k,l),bestE) <- VU.toList $ fInterior db inp i j dncm
    , let d' = sncm!(i,j) - bestE + d
    , d'>=0
    , (e,x) <- dncmbt d' idx k l
    ]
    -- TODO enclosing an interior loop
  mbrbt d i j =
    -- unpaired to the right
    [ (x,z)
    | i+1<j
    , let bestE = mbr!(i,j-1)
    , let d' = mbr!(i,j) - bestE + d
    , d'>=0
    , (x,z) <- mbrbt d' i (j-1)
    ] ++
    -- a stem at k,j
    [ (x,z)
    | i+1<j
    , idx <- [0..VU.length knownDoubleNCM -1]
    , let dncm = dncms!!idx
    , (k,bestE) <- VU.toList $ fMStem i j dncm
    , let d' = mbr!(i,j) - bestE + d
    , d'>=0
    , (x,z) <- dncmbt d' idx k j
    ] ++
    -- two or more stems
    [ (ey,x++y)
    | i+2<j
    , idx <- [0..VU.length knownDoubleNCM -1]
    , let dncm = dncms!!idx
    , (k,bestE) <- VU.toList $ fMMbrStem i j mbr dncm
    , let d' = mbr!(i,j) - bestE + d
    , d'>=0
    , (ex,x) <- mbrbt d' i k
    , ex>=0
    , (ey,y) <- dncmbt ex idx (k+1) j
    ]
  mbr1bt d i j =
    -- add a stem at i j
    [ (x,z)
    | i+1<j
    , idx <- [0..VU.length knownDoubleNCM -1]
    , let dncm = dncms!!idx
    , let bestE = dncm!(i,j)
    , let d' = mbr1!(i,j) - bestE + d
    , d'>=0
    , (x,z) <- dncmbt d' idx i j
    ] ++
    -- unpaired to the right
    [ (x,z)
    | i+1<j
    , let bestE = mbr1!(i,j-1)
    , let d' = mbr1!(i,j) - bestE + d
    , d'>=0
    , (x,z) <- mbr1bt d' i (j-1)
    ]
  n = VU.length inp -1
  bE = extern ! (0,n)



-- * Combining NCMs

-- | singleNCM insertion

fncmSingle :: MotifDB -> Primary -> Int -> Int -> Double
fncmSingle MotifDB{..} inp i j
  | l<4 = eInf
  | otherwise = maybe 0 ((!ci) . snd) $ find ((l==).fst) $ sCycles where
  ci = mkHashedPrimary (minExtended,maxExtended) $ VU.slice i l inp
  l = j-i+1
{-# INLINE fncmSingle #-}

-- | double NCM extend single NCM. We do not care that this is comparatively
-- slow as it is called only a few times, anyway.
--
-- TODO make faster (better lookup system)
-- TODO otherwise case
-- TODO eats another ~10% performance

fncmDS :: MotifDB -> Primary -> Int -> Int -> Int -> Int -> Table2 -> Double
fncmDS MotifDB{..} inp i j k l sncm
  | k>=l = eInf
  -- in case we have one of the three known single NCMs
  | Just hinge <- ((di,dj),len) `lookup` dsConnect
  , Just ncm   <- (di,dj) `lookup` dCycles
  = sncm!(k,l) + hinge!(inp `VU.unsafeIndex` k, inp `VU.unsafeIndex` l) + ncm!ci
--  | i==1 && j==7 && di==2 && dj==2 = error $ show (di,dj,len)
  -- the single NCM is larger
  | otherwise = sncm!(k,l)
  where
    di  = k-i+1
    dj  = j-l+1
    len = l-k+1
    ci  = mkHashedPrimary (minExtended,maxExtended) $ VU.slice i di inp VU.++ VU.slice l dj inp
{-# INLINE fncmDS #-}

-- | double NCM extending another double NCM.
--
-- TODO this one could profit from performance improvements. But check first vs. multibranch timings
-- TODO remove otherwise case
-- TODO improve performance, eats ~66% of total time
-- TODO improve: return empty vector on error, write special minimum function that has eInf on empty

fncmDD :: MotifDB -> Primary -> Int -> Int -> Int -> Int -> [Table2] -> VU.Vector (Int,Double)
fncmDD MotifDB{..} inp i j k l dncms = VU.fromList $ zipWith3 f (map fst $ VU.toList knownDoubleNCM) [0..] dncms where
  f (dk,dl) idx dncm
    | k+2>=l = (-1,eInf)
    | Just hinge <- ((di,dj),(dk,dl)) `lookup` ddConnect
    , Just ncm   <- (di,dj) `lookup` dCycles
    = (idx,dncm!(k,l) + hinge!(inp `VU.unsafeIndex` k, inp `VU.unsafeIndex` l) + ncm!ci)
    | otherwise = (-1,eInf)
    where
      di = k-i+1
      dj = j-l+1
      ci = mkHashedPrimary (minExtended,maxExtended) $ VU.slice i di inp VU.++ VU.slice l dj inp
{-# INLINE fncmDD #-}

-- | Add one stem for "external" calculations
--
-- TODO make efficient

fStemExt :: [Table2] -> Int -> Int -> VU.Vector (Int,Int,Double) -- (dncms!!,k,value)
fStemExt dncms i j = VU.fromList xs where
  xs = [ (d,k,dncm!(i,k))
       | (d,dncm) <- zip [0..] dncms
       , k <- [i+1..j]
       ]
{-# INLINE fStemExt #-}

-- | Combine stems for "external" calculations

fStemExtExt :: [Table2] -> Table2 -> Int -> Int -> VU.Vector (Int,Int,Double)
fStemExtExt dncms extern i j = VU.fromList xs where
  xs = [ (d,k,dncm!(i,k) + extern!(k+1,j))
       | (d,dncm) <- zip [0..] dncms
       , k <- [i+1..j-1]
       ]
{-# INLINE fStemExtExt #-}

-- | Close a multibranched loop with a singleNCM
--
-- TODO close with singleNCM

fMulti :: MotifDB -> Primary -> Int -> Int -> Table2 -> Table2 -> VU.Vector (Int,Double)
fMulti db inp i j mbr mbr1 = xs where
  xs = VU.map (\k -> (k, mbr!(i+1,k) + mbr1!(k+1,j-1))) $ (VU.enumFromN (i+1) (j-i-1))
{-# INLINE fMulti #-}

-- | Connect a partial multibranched structure with a hairpin. Note that the
-- "dncm" hairpin part wants one table, not the list of all tables.

fMMbrStem :: Int -> Int -> Table2 -> Table2 -> VU.Vector (Int,Double)
fMMbrStem i j mbr dncm = VU.map (\k -> (k,mbr!(i,k) + dncm!(k+1,j))) $ VU.enumFromN (i+1) (j-i-1)
{-# INLINE fMMbrStem #-}

-- | Add the first stem

fMStem :: Int -> Int -> Table2 -> VU.Vector (Int,Double)
fMStem i j dncm = VU.map (\k -> (k,dncm!(k,j))) $ VU.enumFromN i (j-i)
{-# INLINE fMStem #-}

-- | Interior loops of some size
--
-- TODO this could profit from a log-based scoring function

fInterior :: MotifDB -> Primary -> Int -> Int -> Table2 -> VU.Vector ((Int,Int),Double)
{-
fInterior MotifDB{..} inp i j dncm = VU.fromList $ ((-1,-1),eInf) : xs where
  xs =  [ ((k,l),dncm!(k,l))
        | k<-[i+1 .. i+10]
        , l<-[j-1,j-2 .. j-10]
        , k<l
        , i+1/=k || j-1/=l
        ] -}
fInterior MotifDB{..} inp i j dncm = res where
  res = VU.map (\(k,l) -> ((k,l),dncm!(k,l))) . VU.filter (\(k,l) -> k<l && (i+1/=k || j-1/=l)) $ VU.unfoldr f (i+1,j-1)
  f (k,l)
    | i+10< k   = Nothing
    | j-10==l   = Just ((k,l),(k+1,j-1)) -- next element, new seed
    | otherwise = Just ((k,l),(k  ,l-1))
  {-# INLINE f #-}
{-# INLINE fInterior #-}

-- * Helper functions

-- | Extract final score

getScore :: Tables -> Double
getScore ts = score where
  t = sel5 ts
  (_,(n,_)) = bounds t
  score = t!(0,n)

-- | The default two-dim table

mkTable2 n = mkTable2With eInf n

-- | Create a 2-dim table

mkTable2With v n = do
  tM <- fromAssocsM (0,0) (n,n) v []
  t <- unsafeFreezeM tM
  return (t,tM)

-- | minimum for unboxed vectors, capturing possible 0-length. Vectors in this
-- algorithm \always\ carry positional information.
--
-- TODO the next version of the vector library should handle "VU.map snd"
-- better

vuminimum xs = VU.foldl' (\a (_,x) -> min a x) eInf xs
{-# INLINE vuminimum #-}
-- vuminimum = VU.foldl' min eInf . VU.map snd



-- * types and newtypes

type Table2 = PrimArray (Int,Int) Double
type Table4 = PrimArray (Int,Int,Int,Int) Double
type Tables = (Table2,[Table2],Table2,Table2,Table2)