biohazard-0.6.1: tools/SimpleSeed.hs
{-# LANGUAGE OverloadedStrings, BangPatterns, RecordWildCards #-}
{-# OPTIONS_GHC -Wall #-}
module SimpleSeed where
import Bio.Base
import Bio.Bam.Rec
import Data.Bits
import Data.List
import Data.Maybe
import qualified Data.IntMap as IM
import qualified Data.Vector.Generic as V
import qualified Data.Vector.Unboxed as U
-- | Discontiguous template "12 of 16", stolen from MegaBLAST:
-- 1,110,110,110,110,111, with two bits per base gives 0xFCF3CF3F
template :: Int
template = 0xFCF3CF3F
create_seed_words :: [Nucleotides] -> [(Int, Int)]
create_seed_words = drop 32 . go 0x0 (-16) 0x0 0
where
go !accf !i !accr !ir s =
(accf .&. template, i) : (accr .&. template, ir) : case s of
[ ] -> []
(Ns n:ns) -> go (accf `shiftR` 2 .|. (codef U.! fromIntegral n) `shiftL` 30) (i+1)
(accr `shiftL` 2 .|. (coder U.! fromIntegral n)) (ir-1) ns
-- These codes are chosen so that ambiguity codes result in zeroes.
-- The seed word 0, which would otherwise be the low-complexity and
-- useless poly-A, is later ignored.
codef, coder :: U.Vector Int
codef = U.fromList [0,0,1,0,2,0,0,0,3,0,0,0,0,0,0,0]
coder = U.fromList [0,3,2,0,1,0,0,0,0,0,0,0,0,0,0,0]
-- Turns a list of seed words into a map. Only the first entry is used,
-- duplicates are discarded silenty.
data I2 = I2 !Int !Int
newtype SeedMap = SM { unSM :: IM.IntMap Int }
deriving Show
create_seed_map :: [Nucleotides] -> SeedMap
create_seed_map = SM . cleanup . foldl' (\m (k,v) -> IM.insertWith' add k v m) IM.empty .
map (\(x,y) -> (x,(I2 1 y))) . create_seed_words . pad
where pad ns = ns ++ take 15 ns
add (I2 x i) (I2 y _) = I2 (x+y) i
cleanup = IM.mapMaybe $ \(I2 n j) -> if n < 8 then Just j else Nothing
create_seed_maps :: [[Nucleotides]] -> SeedMap
create_seed_maps = SM . IM.unionsWith const . map (unSM . create_seed_map)
-- | Actual seeding. We take every hit and guesstimate an alignment
-- region from it (by adding the overhanging sequence parts and rounding
-- a bit up). Regions are overlapped into larger ones, counting votes.
-- The region with the most votes is used as seed region. (This will
-- occasionally result in a very long initial alignment. We can afford
-- that.)
--
-- If we have PE data where only one read is seeded, we can either
-- discard the pair or align the second mate very expensively. While
-- possible, that sounds rather expensive and should probably depend on
-- the quality of the first mates' alignment. Generally, we may want to
-- check the quality of the initial alignment anyway.
--
-- For proper overlapping, we need to normalize each region to strictly
-- positive or strictly negative coordinates. After sorting and
-- overlapping, we only need to check if the last region overlaps the
-- first---there can be only one such overlap per strand. We should
-- probably discard overly long regions.
do_seed :: Int -> SeedMap -> BamRec -> Maybe (Int,Int)
do_seed ln (SM sm) BamRec{..} = -- do S.hPut stdout $ S.concat [ b_qname, key, ": ", S.pack (shows b_seq "\n") ]
-- mapM_ (\x -> hPutStrLn stdout $ " " ++ show x) rgns
case rgns of
[ ] -> Nothing -- putStrLn "discard"
{- (a,b,_) : _ | a > 20000 || a < (-20000) -> error $ concat [
"Weird region: ",
shows (a,b,ln) "; ",
"Primitive regions: ",
shows (rgns_fwd ++ rgns_rev) "; ",
"Resulting regions: ",
show rgns ] -}
(a,b,_) : _ -> Just (a,b) -- putStrLn $ "seed to " ++ shows a ".." ++ shows b " ("
-- ++ shows (b-a) "/" ++ shows (V.length b_seq) ")"
where
seeds = filter ((/= 0) . fst) $ filter ((/= template) . fst) $
filter ((>= 0) . snd) $ create_seed_words $ V.toList b_seq
more x = (x * 9) `div` 8 + 16
rgns = sortBy (\(_,_,c) (_,_,z) -> compare z c) $ filter reasonably_short $
(wrap_with id $ overlap $ sort $ map norm_right rgns_fwd) ++
(wrap_with norm_left $ overlap $ sort $ map norm_left rgns_rev)
(rgns_fwd, rgns_rev) = let put (f,r) (i,j) | j >= 0 = (rgn:f, r)
| otherwise = (f, rgn:r)
where rgn = (j - more i, j + more (V.length b_seq - i), 1::Int)
in foldl put ([],[]) [ (i,j) | (k,i) <- seeds, j <- maybeToList $ IM.lookup k sm ]
norm_right (a,b,n) = if a < 0 then (a+ln, b+ln, n) else (a,b,n)
norm_left (a,b,n) = if b >= 0 then (a-ln, b-ln, n) else (a,b,n)
wrap_with _ [ ] = []
wrap_with _ [ r ] = [r]
wrap_with f rs@((x,y,n):rs')
| i <= y+ln && x+ln <= j = f (min (x+ln) i, max (y+ln) j, n+m) : init rs'
| otherwise = rs
where
(i,j,m) = last rs
overlap ( (x,y,n) : (i,j,m) : rs ) | i <= y = overlap ( (x,max y j,n+m) : rs )
overlap ( (x,y,n) : rs ) = (x,y,n) : overlap rs
overlap [] = []
-- First cut: reasonable is less than the whole MT. Tuning can
-- come later.
reasonably_short (x,y,_) = y-x < ln