packages feed

cobot-tools-0.1.2.0: src/Bio/Tools/Sequence/CodonOptimization/Algo.hs

module Bio.Tools.Sequence.CodonOptimization.Algo
    ( optimizeAA
    , optimizeDNA
    , score
    , scoreSequence
    , scoreCmp
    ) where

import           Bio.NucleicAcid.Nucleotide                     (symbol)
import           Bio.NucleicAcid.Nucleotide.Type                (DNA (..))
import           Bio.Protein.AminoAcid.Type                     (AA (..))
import           Bio.Tools.Sequence.CodonOptimization.Constants (ak2Codon, ak2MaxFrequCodon,
                                                                 codon2ak,
                                                                 codonFrequencies, motiveScoreWindow, defaultMotiveScore, 
                                                                 forbiddenMotiveScore)
import           Bio.Tools.Sequence.CodonOptimization.Types     (CodonScoreConfig (..),
                                                                 standardTemperature)
import           Bio.Tools.Sequence.ViennaRNA.Fold              (fold)
import           Data.List                                      (foldl',
                                                                 maximumBy,
                                                                 take)
import           Data.Map                                       as Map (lookup)
import           Data.Maybe                                     (fromMaybe)
import           Text.Regex.TDFA                                ((=~))

-- | 'optimizeDNA' function does translation from [DNA] to [AA] and then calls 'optimizeAA'
optimizeDNA :: CodonScoreConfig -- ^ Config data object. Contains main parameters of codon-optimization and all parameters for scoring function
            -> [DNA]            -- ^ Initial, not optimized nucleotide sequence
            -> [DNA]            -- ^ Result, optimized nucleotide sequence
optimizeDNA cfg dna = optimizeAA cfg (translate dna)
  where
    translate :: [DNA] -> [AA]
    translate [] = []
    translate dnaSeq =
        case Map.lookup (take 3 dnaSeq) codon2ak of
            Just ak -> ak : translate (drop 3 dnaSeq)
            _       -> error $ "Unknown codon: " ++ show (take 3 dnaSeq)

-- | 'optimizeAA' function does codon-optimisation for incoming amino-acid sequence.
-- Incoming amino-acid sequence transformed to nucleotide sequence and optimized used the codon-optimization algorithm.
-- Algorithm described here doi: 10.1007/s11693-010-9062-3
optimizeAA :: CodonScoreConfig  -- ^ Config data object. Contains main parameters of codon-optimization and all parameters for scoring function
           -> [AA]              -- ^ Initial, not optimized amino-acid sequence
           -> [DNA]             -- ^ Result, optimized nucleotide sequence
optimizeAA cfg@(CodonScoreConfig organism initLen winLen _ _ _ _ _ _ _ _ _ _) aa = foldl' concatByScore initial variants
  where
    lenAA = length aa
    variants = generateVariants (drop initLen aa) winLen
    fequCodonsMap = ak2MaxFrequCodon organism
    initial = concatMap (\ak -> maybe "" fst (Map.lookup ak fequCodonsMap)) (take initLen aa)

    -- | 'concatByScore' function gets maximum by score variable string and then concat it to result string
    concatByScore :: [DNA]   -- ^ initial string
                  -> [[DNA]] -- ^ list of variable string
                  -> [DNA]   -- ^ result string
    concatByScore result vars
        | length result == 3 * (lenAA - winLen - 1) = result ++ maximumBy (scoreCmp cfg result) vars
        | otherwise = result ++ take 3 (maximumBy (scoreCmp cfg result) vars)


-- | 'generateVariants' function generates list of all possible variants of nucleotide sequence for amino-acid sequence.
-- It is just recursive execution of 'windowVariants' for all amino-acid sequence.
-- Example: generateVariants [PHE,TRP,GLU,MET] 2 => [[[DT,DT,DT,DT,DG,DG,DG,DA,DA], [DT,DT,DC,DT,DG,DG,DG,DA,DA], [DT,DT,DT,DT,DG,DG,DG,DA,DG], [DT,DT,DC,DT,DG,DG,DG,DA,DG]],
--                                        [[DT,DG,DG,DG,DA,DA,DA,DT,DG], [DT,DG,DG,DG,DA,DG,DA,DT,DG]]]
-- Returns empty list in case of empty incoming string
generateVariants :: [AA]      -- ^ amino-acid sequence
                 -> Int       -- ^ length of window. means how much amino-acid from the right side side will be taken during scoring variant for one codon
                 -> [[[DNA]]] -- ^ result list. for each amino-acid now there is list of all variants for nucleotide sequence
generateVariants [] _ = []
generateVariants aa winLen
    | length aa == winLen = []
    | otherwise = windowVariants aa winLen : generateVariants (drop 1 aa) winLen

-- | 'windowVariants' function generates list of all possible variants of nucleotide sequence for amino-acid window.
-- Example: windowVariants [PHE,TRP,GLU,MET] 2 => [[DT,DT,DT,DT,DG,DG,DG,DA,DA], [DT,DT,DC,DT,DG,DG,DG,DA,DA], [DT,DT,DT,DT,DG,DG,DG,DA,DG], [DT,DT,DC,DT,DG,DG,DG,DA,DG]]
-- Returns empty list in case of empty incoming string
windowVariants :: [AA] -> Int -> [[DNA]]
windowVariants sequ winLen = map concat . mapM getCodons . take (winLen + 1) $ sequ

-- | 'getCodons' function gets list of codons for amino-acid
-- Example: 'getCodons' PRO => [[DC,DC,DT], [DC,DC,DC], [DC,DC,DA], [DC,DC,DG]]
-- Returns empty list in case of unknown amino-acid
getCodons :: AA -> [[DNA]]
getCodons ak = fromMaybe [] (Map.lookup ak ak2Codon)

-- | 'scoreSequence' function calculates the average score for full sequence
scoreSequence :: CodonScoreConfig -> [DNA] -> Double
scoreSequence cnf@(CodonScoreConfig _ initLen winLen _ _ _ _ _ _ _ _ _ _) nkSequ = sum res / realToFrac (length res)
  where
    res = scr ((initLen + winLen + 1) * 3) []

    scr :: Int -> [Double] -> [Double]
    scr partLen acc | partLen > length nkSequ = acc
                    | otherwise = scr (partLen + winLen * 3) (score cnf (take partLen nkSequ) : acc)

-- | 'score' function gets scoring for incoming string.
-- Scoring function is a composite function of several scoring. More about scoring algorithm see here doi: 10.1007/s11693-010-9062-3
score :: CodonScoreConfig  -- ^ Config data object. Contains main parameters of codon-optimization and all parameters for scoring function
      -> [DNA]             -- ^ nucleotide sequence to score
      -> Double            -- ^ result score value
score (CodonScoreConfig organism _ winLen codonUsageWeight gcWeight gcFactor gcWindow rnaFoldingWeight
                        rnaFoldingFactor rnaFoldingWindow forbiddenDNAWeight gcContentDesired forbiddenRegexp) nkSequ =
    scoreCU + scoreGC - scoreMT - realToFrac scoreRNAFold
  where
    sequLen = length nkSequ
    optimizedLen = sequLen - (winLen + 1) * 3
    scoreGC =
        if optimizedLen < gcWindow - optimizedLen  -- check if we have enough sequence for gcWindow
            then 0
            else realToFrac gcWeight * gcScore (drop (sequLen - gcWindow) nkSequ)
    scoreCU = realToFrac codonUsageWeight * codonUsage (drop optimizedLen nkSequ)
    scoreMT = realToFrac forbiddenDNAWeight * motiveScore nkSequ
    scoreRNAFold = scoreRnaf nkSequ

    -- | 'gcScore' function for the GC content.
    -- It is negatively counted absolute difference between the desired GC content and the GC content of a test sequence.
    gcScore :: [DNA] -> Double
    gcScore sequ = -abs (gc / (at + gc) * 100 - realToFrac gcContentDesired) ** gcFactor
      where
        gc = realToFrac $ length $ filter (\s -> s == DC || s == DG) sequ
        at = realToFrac $ length $ filter (\s -> s == DA || s == DT) sequ

    -- | 'codonUsage' function gets higest score for most frequently used codons
    codonUsage :: [DNA] -> Double
    codonUsage sequ = (cai ** (1 / codonCount)) * 100
      where
        codonCount = realToFrac (length sequ) / 3
        cai = countWeight sequ 1.0

        -- | 'countWeight' function is recursive counting weight for incoming string according to codon usage frequencies
        countWeight :: [DNA] -> Double -> Double
        countWeight [] acc = acc
        countWeight windowSeq acc = countWeight (drop 3 windowSeq) (acc * fromMaybe 0 (countWeightMb windowSeq))

        countWeightMb :: [DNA] -> Maybe Double
        countWeightMb str = do
            let codon = take 3 str
            codonFreq         <- Map.lookup codon (codonFrequencies organism)
            ak                <- Map.lookup codon codon2ak
            (_, codonMaxFreq) <- Map.lookup ak (ak2MaxFrequCodon organism)
            return $ codonFreq / codonMaxFreq

    -- | 'motiveScore' counts score for the occurrence of desired and unwanted DNA motifs.
    motiveScore :: [DNA] -> Double
    motiveScore sequ =
        if any (drop (length sequ - motiveScoreWindow) (map symbol sequ) =~) forbiddenRegexp
            then forbiddenMotiveScore
            else defaultMotiveScore


    -- | 'scoreRnaf' counts energy of RNA folding
    scoreRnaf :: [DNA] -> Int
    scoreRnaf sequ = truncate $ rnaFoldingWeight * (abs result ** rnaFoldingFactor)
      where
        result = fst $ fold standardTemperature (drop (length sequ - rnaFoldingWindow) sequ)

-- | 'scoreCmp' is compare function for two strings using 'score' function
scoreCmp :: CodonScoreConfig -> [DNA] -> [DNA] -> [DNA] -> Ordering
scoreCmp cfg optimized str1 str2 = compare score1 score2
  where
    score1 = score cfg (optimized ++ str1)
    score2 = score cfg (optimized ++ str2)