packages feed

elynx-markov-0.2.1: src/ELynx/Data/MarkovProcess/MixtureModel.hs

{- |
Module      :  ELynx.Data.MarkovProcess.MixtureModel
Description :  Mixture models are a set of substitution models with weights
Copyright   :  (c) Dominik Schrempf 2020
License     :  GPL-3.0-or-later

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

Creation date: Tue Jan 29 19:17:40 2019.

To be imported qualified.

-}

module ELynx.Data.MarkovProcess.MixtureModel
  ( -- * Types
    Weight
  , Component
  , MixtureModel(name)
  , -- * Getters
    getAlphabet
  , getWeights
  , getSubstitutionModels
    -- * Building mixture models
  , fromSubstitutionModels
    -- * Transformations
  , concatenate
  , scale
  , normalize
  , appendNameComponents
  -- * Output
  , summarizeComponent
  , summarize
  )
where

import           Prelude                 hiding ( map
                                                , head
                                                , zipWith
                                                , length
                                                )

import qualified Data.ByteString.Builder       as L
import qualified Data.ByteString.Lazy.Char8    as L
import           Data.List.NonEmpty      hiding ( zip )
import           Data.Semigroup

import           ELynx.Data.Alphabet.Alphabet

import           ELynx.Tools

import qualified ELynx.Data.MarkovProcess.SubstitutionModel
                                               as S

-- | Mixture model component weight.
type Weight = Double

-- | A mixture model component has a weight and a substitution model.
data Component = Component
  { weight     :: Weight
  , substModel :: S.SubstitutionModel
  }
  deriving (Show, Read)

-- | A mixture model with its components.
data MixtureModel = MixtureModel
  { name       :: S.Name        -- ^ Name
  , alphabet   :: Alphabet
  , components :: NonEmpty Component
  }
  deriving (Show, Read)

-- | Get alphabet used with mixture model. Throws error if components use
-- different 'Alphabet's.
getAlphabet :: MixtureModel -> Alphabet
getAlphabet = alphabet

-- | Get weights.
getWeights :: MixtureModel -> NonEmpty Weight
getWeights = map weight . components

-- | Get substitution models.
getSubstitutionModels :: MixtureModel -> NonEmpty S.SubstitutionModel
getSubstitutionModels = map substModel . components

-- | Create a mixture model from a list of substitution models.
fromSubstitutionModels
  :: S.Name -> NonEmpty Weight -> NonEmpty S.SubstitutionModel -> MixtureModel
fromSubstitutionModels n ws sms = if allEqual $ toList alphs
  then MixtureModel n (head alphs) comps
  else error
    "fromSubstitutionModels: alphabets of substitution models are not equal."
 where
  comps = zipWith Component ws sms
  alphs = map S.alphabet sms

-- | Concatenate mixture models.
concatenate :: S.Name -> NonEmpty MixtureModel -> MixtureModel
concatenate n mms = fromSubstitutionModels n ws sms
 where
  comps = sconcat $ map components mms
  ws    = map weight comps
  sms   = map substModel comps

scaleComponent :: Double -> Component -> Component
scaleComponent s c = c { substModel = s' } where s' = S.scale s $ substModel c

-- | Scale all substitution models of the mixture model.
scale :: Double -> MixtureModel -> MixtureModel
scale s m = m { components = cs' }
 where
  cs  = components m
  cs' = map (scaleComponent s) cs

-- | Globally normalize a mixture model so that on average one event happens per
-- unit time.
normalize :: MixtureModel -> MixtureModel
normalize mm = scale (1 / c) mm
 where
  c       = sum $ zipWith (*) weights scales
  weights = getWeights mm
  scales  = map S.totalRate $ getSubstitutionModels mm

appendNameComponent :: S.Name -> Component -> Component
appendNameComponent n c = c { substModel = s' }
  where s' = S.appendName n $ substModel c

-- | Append byte string to all substitution models of mixture model.
appendNameComponents :: S.Name -> MixtureModel -> MixtureModel
appendNameComponents n m = m { components = cs' }
 where
  cs  = components m
  cs' = map (appendNameComponent n) cs

-- | Summarize a mixture model component; lines to be printed to screen or log.
summarizeComponent :: Component -> [L.ByteString]
summarizeComponent c =
  L.pack "Weight: "
    <> (L.toLazyByteString . L.doubleDec $ weight c)
    :  S.summarize (substModel c)

-- | Summarize a mixture model; lines to be printed to screen or log.
summarize :: MixtureModel -> [L.ByteString]
summarize m =
  [ L.pack $ "Mixture model: " ++ name m ++ "."
    , L.pack $ "Number of components: " ++ show n ++ "."
    ]
    ++ detail
 where
  n      = length $ components m
  detail = if n <= 100
    then concat
      [ L.pack ("Component " ++ show i ++ ":") : summarizeComponent c
      | (i, c) <- zip [1 :: Int ..] (toList $ components m)
      ]
    else []