packages feed

elynx-seq-0.2.1: src/ELynx/Data/Sequence/Sequence.hs

{-# LANGUAGE OverloadedStrings #-}

{- |
Module      :  ELynx.Data.Sequence
Description :  Hereditary sequences
Copyright   :  (c) Dominik Schrempf 2018
License     :  GPL-3.0-or-later

Maintainer  :  dominik.schrempf@gmail.com
Stability   :  unstable
Portability :  portable

Creation date: Thu Oct  4 18:54:51 2018.

This module is to be imported qualified.

-}

module ELynx.Data.Sequence.Sequence
  ( -- * Types
    Name
  , Description
  , Characters
  , Sequence(..)
  -- * Input
  , fromByteString
  -- * Output
  , toByteString
  , header
  , summarize
  , summarizeSequences
  , body
  -- * Analysis
  , length
  , equalLength
  , longest
  -- * Manipulation
  , trim
  , concat
  , concatSequences
  -- * Filtering
  , filterShorterThan
  , filterLongerThan
  , filterStandard
  )
where

import           Control.Parallel.Strategies
import qualified Data.ByteString.Lazy.Char8    as L
import           Data.List                      ( maximumBy )
import           Data.Ord                       ( comparing )
import qualified Data.Vector.Unboxed           as V
import           Prelude                 hiding ( concat
                                                , length
                                                )
import qualified Prelude                       as Pr
                                                ( length )
import qualified Text.Printf                   as P

import qualified ELynx.Data.Alphabet.Alphabet  as A
import           ELynx.Data.Alphabet.Character
import           ELynx.Data.Sequence.Defaults
import           ELynx.Tools

-- | For now, 'Name's are just 'L.ByteString's.
type Name = L.ByteString

-- | The description of a sequence.
type Description = L.ByteString

-- | The vector of characters of a sequence.
type Characters = V.Vector Character

-- | Convert byte string to sequence characters.
fromByteString :: L.ByteString -> Characters
fromByteString = V.fromList . map fromChar . L.unpack

-- | Convert sequence characters to byte string.
toByteString :: Characters -> L.ByteString
toByteString = L.pack . map toChar . V.toList

-- | Sequences have a name, a possibly empty description, a code and hopefully a
-- lot of data.
data Sequence = Sequence { name        :: Name
                         , description :: Description
                         , alphabet    :: A.Alphabet
                         , characters  :: Characters }
  deriving (Show, Eq)

getInfo :: Sequence -> L.ByteString
getInfo s = L.unwords
  [ alignLeft nameWidth (name s)
  , alignRight fieldWidth (L.pack $ show $ alphabet s)
  , alignRight fieldWidth (L.pack . show $ len)
  , alignRight fieldWidth (L.pack $ P.printf "%2.2f" pGaps)
  ]
 where
  len   = length s
  nGaps = countGaps s
  pGaps = 100 * fromIntegral nGaps / fromIntegral len :: Double

-- | Trim and show a 'Sequence'.
summarize :: Sequence -> L.ByteString
summarize s = L.unwords
  [getInfo s, summarizeByteString summaryLength $ toByteString (characters s)]

-- | Trim and show a list of 'Sequence's.
summarizeSequences :: [Sequence] -> L.ByteString
summarizeSequences ss = header ss <> body (take summaryNSequences ss)

-- | Header printed before 'Sequence' list.
tableHeader :: L.ByteString
tableHeader = L.unwords
  [ alignLeft nameWidth "Name"
  , alignRight fieldWidth "Code"
  , alignRight fieldWidth "Length"
  , alignRight fieldWidth "Gaps [%]"
  , "Sequence"
  ]

-- | A short description of the sequence.
header :: [Sequence] -> L.ByteString
header ss =
  L.unlines
    $  reportIfSubsetIsShown
    ++ [ L.pack
       $  "For each sequence, the "
       ++ show summaryLength
       ++ " first bases are shown."
       , L.pack $ "List contains " ++ show (Pr.length ss) ++ " sequences."
       , ""
       , tableHeader
       ]
 where
  l = Pr.length ss
  s =
    show summaryNSequences
      ++ " out of "
      ++ show (Pr.length ss)
      ++ " sequences are shown."
  reportIfSubsetIsShown | l > summaryNSequences = [L.pack s]
                        | otherwise             = []

-- | Trim and show a list of 'Sequence's.
body :: [Sequence] -> L.ByteString
body ss = L.unlines (map summarize ss `using` parListChunk 5 rdeepseq)

-- | Calculate length of 'Sequence'.
length :: Sequence -> Int
length = fromIntegral . V.length . characters

-- | Check if all 'Sequence's have equal length.
equalLength :: [Sequence] -> Bool
equalLength = allEqual . map length

-- | Find the longest 'Sequence' in a list.
longest :: [Sequence] -> Sequence
longest = maximumBy (comparing length)

-- | Count number of gaps or unknown characters in sequence.
countGaps :: Sequence -> Int
countGaps s = V.length . V.filter (A.isGap $ alphabet s) $ characters s

-- | Trim to given length.
trim :: Int -> Sequence -> Sequence
trim n (Sequence nm d a cs) = Sequence nm d a (V.take (fromIntegral n) cs)

-- | Concatenate two sequences. 'Name's have to match.
concat :: Sequence -> Sequence -> Sequence
concat (Sequence i d c cs) (Sequence j f k ks)
  | i /= j
  = error
    $  "concatenate: Sequences do not have equal names: "
    ++ L.unpack i
    ++ ", "
    ++ L.unpack j
    ++ "."
  | d /= f
  = error
    $  "concatenate: Sequences do not have equal descriptions: "
    ++ L.unpack d
    ++ ", "
    ++ L.unpack f
    ++ "."
  | c /= k
  = error
    $  "concatenate: Sequences do not have equal alphabets: "
    ++ show c
    ++ ", "
    ++ show k
    ++ "."
  | otherwise
  = Sequence i d c (cs <> ks)

-- | Concatenate a list of sequences, see 'concat'.
concatSequences :: [[Sequence]] -> [Sequence]
concatSequences []   = error "concatenateSequences: Nothing to concatenate."
concatSequences [ss] = ss
concatSequences sss  = foldl1 (zipWith concat) sss

-- | Only take 'Sequence's that are shorter than a given number.
filterShorterThan :: Int -> [Sequence] -> [Sequence]
filterShorterThan n = filter (\x -> length x < n)

-- | Only take 'Sequence's that are longer than a given number.
filterLongerThan :: Int -> [Sequence] -> [Sequence]
filterLongerThan n = filter (\x -> length x > n)

-- | Only take 'Sequence's that contain at least on non-IUPAC character.
filterStandard :: [Sequence] -> [Sequence]
filterStandard = filter (\s -> anyStandard (alphabet s) s)

-- Are all characters IUPAC characters?
anyStandard :: A.Alphabet -> Sequence -> Bool
anyStandard a s = V.any (A.isStd a) cs where cs = characters s