biohazard-0.6.5: tools/gt-scan.hs
{-# LANGUAGE OverloadedStrings, BangPatterns, RecordWildCards, FlexibleContexts, TypeFamilies #-}
-- Scan file with GT likelihoods, fit something...
--
-- First iteration: Mitochondrion only. We don't need to fit
-- anything. So far, the likelihoods behave strangely in that smaller
-- \theta is always better, as long as it doesn't become zero.
--
-- Second iteration: Mitochondrion only, but with a divergence
-- parameter. Needs to be scanned in parallel with a TwoBit file.
import Bio.Base
import Bio.Bam.Header
import Bio.Genocall.AvroFile
import Bio.Iteratee
import Bio.TwoBit
import Bio.Util.AD
import Bio.Util.Numeric
import Data.Avro
import Data.List ( intercalate )
import Data.MiniFloat ( mini2float )
import Data.Strict.Tuple ( Pair((:!:)) )
import Numeric ( showFFloat )
import qualified Data.Vector.Storable as S
import qualified Data.Vector.Unboxed as U
import qualified Data.Vector.Unboxed.Mutable as UM
main :: IO ()
main = do hg19 <- openTwoBit "/mnt/datengrab/hg19.2bit"
mtbl <- UM.replicate (max_lk-min_lk+1) 0
let all_lk tbl (p1 :!: p2) ref site = (lk0 p1 site :!:) `fmap` lk1 tbl p2 ref site
p0 :!: pe <- enumDefaultInputs >=> run $
joinI $ readAvroContainer $ \meta -> do
foldStreamM (lk_block (getRefseqs meta) (all_lk mtbl) hg19) (1 :!: 1)
tbl <- U.unsafeFreeze mtbl
-- optimize llk1 vs. d argument.
let plainfn :: U.Vector Double -> Double
plainfn args = llk1 tbl (unPr pe) $ args U.! 0
combofn :: U.Vector Double -> (Double, U.Vector Double)
combofn args = case llk1 tbl (C (unPr pe)) $ D (args U.! 0) (U.singleton 1) of
(D x dx) -> ( x, dx )
params = defaultParameters { printFinal = False, verbose = {- Verbose -} Quiet, maxItersFac = 20 }
(x,q,s) <- optimize params 0.0001 (U.singleton 0.01)
(VFunction plainfn)
(VGradient $ snd . combofn)
(Just $ VCombined combofn)
-- print $ llk1 tbl (C (unPr pe)) (D 0.001 (U.singleton 1))
putStrLn $ intercalate "\t"
[ showNum (round $ unPr p0 :: Int), showFFloat (Just 5) (sigmoid2 $ x S.! 0) []
, show q, showNum (round $ finalValue s :: Int), show s ]
-- print (map sigmoid2 $ S.toList x, q, s)
-- | Scans block together with reference sequence. Folds a monadic
-- action over the called sites.
lk_block :: Monad m => Refs -> (b -> Nucleotide -> GenoCallSite -> m b) -> TwoBitFile -> b -> GenoCallBlock -> m b
lk_block refs f tbf b GenoCallBlock{..} = foldM3f b start_position refseq called_sites
where
refseq = getLazySubseq tbf $ Pos (sq_name $ getRef refs reference_name) start_position
foldM2 acc (x:xs) (y:ys) = do !acc' <- f acc x y ; foldM2 acc' xs ys
foldM2 acc [ ] _ = return acc
foldM2 acc _ [ ] = return acc
-- XXX terrible hack to deal with PhiX! Remove this as soon as
-- sensible!
foldM3f acc n (x:xs) (y:ys)
| n `elem` bad = foldM3f acc (succ n) xs ys
| otherwise = do !acc' <- f acc x y ; foldM3f acc' (succ n) xs ys
foldM3f acc _ [ ] _ = return acc
foldM3f acc _ _ [ ] = return acc
bad = [1400,1643]
-- bad = [586,832,1649,2810,4517]
{- p_block tbf GenoCallBlock{..} = do
printf "Block %s:%d-%d\n" (show reference_name) start_position
(start_position + length called_sites)
zipWithM_ (curry print) refseq (map snp_likelihoods called_sites)
where
refseq = getLazySubseq tbf (Pos (encodeUtf8 reference_name) start_position) -}
-- | Likelihood with flat prior (no parameters).
lk0 :: Prob -> GenoCallSite -> Prob
lk0 !pp GenoCallSite{..} | U.length snp_likelihoods == 4 =
pp * 0.25 * U.sum (U.map (Pr . negate . mini2float) snp_likelihoods)
| otherwise = pp
-- | Likelihood precomputation. Total likelihood computes as product
-- over sites @i@ with reference alleles @X_i@:
-- @
-- L(d) = \prod_i ( (1-d) * GL(X_i) + 1/3 * d * \sum_{Y/=X_i} GL(Y) )
-- = \prod_i GL(X_i) * \prod_i ( 1 - d + 1/3 * d * \sum_{Y/=X_i} GL(Y)/GL(X) )
-- @
--
-- We compute the first term on the first pass and tabulate a quantized
-- form of the second term: @round (log \sum_{Y/=X_i} GL(Y)/GL(X))@.
-- (Maybe add a scaling factor, though the plain natural log seems
-- pretty good.)
type LkTableM = UM.IOVector Int
type LkTable = U.Vector Int
min_lk, max_lk :: Int
min_lk = -256
max_lk = 255
-- | Likelihood with one parameter, the divergence. Computes one
-- part directly, bins the variable part into a mutable table.
lk1 :: LkTableM -> Prob -> Nucleotide -> GenoCallSite -> IO Prob
lk1 tbl !pp ref GenoCallSite{..} | U.length snp_likelihoods == 4 = do
let lx = Pr . negate . mini2float $ snp_likelihoods U.! fromEnum ref
odds = U.ifoldl' (\a i v -> if i == fromEnum ref then a else a + Pr (- mini2float v)) 0 snp_likelihoods / lx
qq = round (unPr odds) `min` max_lk `max` min_lk - min_lk
UM.write tbl qq . succ =<< UM.read tbl qq
return $! pp * lx
| otherwise = return pp
-- | Actual negative log-likelihood. Gets a table and a divergence
-- value. Returns likelihoods and first two derivatives with respect to
-- the divergence value.
llk1 :: (Ord a, Floating a) => LkTable -> a -> a -> a
llk1 tbl p d = U.ifoldl' step (-p) tbl
where
!d1 = log1p (- sigmoid2 d)
!d3 = log (sigmoid2 d) - log 3
step acc qq num = acc - fromIntegral num * (d1 <#> d3 + fromIntegral (min_lk + qq))