packages feed

elynx-markov-0.2.1: src/ELynx/Simulate/MarkovProcessAlongTree.hs

{- |
   Description :  Work with transition probability matrices on rooted trees
   Copyright   :  (c) Dominik Schrempf 2017
   License     :  GPLv3

   Maintainer  :  dominik.schrempf@gmail.com
   Stability   :  unstable
   Portability :  non-portable (not tested)

Calculate transition probability matrices, map rate matrices on trees, populate
a tree with states according to a stationary distribution, etc.

The implementation of the Markov process is more than basic and can be improved
in a lot of ways.

-}

module ELynx.Simulate.MarkovProcessAlongTree
  ( -- * Single rate matrix.
    simulate
  , simulateAndFlatten
    -- * Mixture models.
  , simulateMixtureModel
  , simulateAndFlattenMixtureModel
  , simulateAndFlattenMixtureModelPar
  )
where

import           Control.Monad
import           Control.Monad.Primitive
import           Control.Parallel.Strategies
import           Data.Tree
import           Numeric.LinearAlgebra
import           System.Random.MWC              ( Gen
                                                , GenIO
                                                )
import           System.Random.MWC.Distributions
                                                ( categorical )

import           ELynx.Data.Tree

import           ELynx.Tools

import           ELynx.Data.MarkovProcess.RateMatrix
import           ELynx.Simulate.MarkovProcess

toProbTree :: (Measurable a) => RateMatrix -> Tree a -> Tree ProbMatrix
toProbTree q = fmap (probMatrix q . getLen)

getRootStates
  :: PrimMonad m
  => Int
  -> StationaryDistribution
  -> Gen (PrimState m)
  -> m [State]
getRootStates n d g = replicateM n $ categorical d g

-- | Simulate a number of sites for a given substitution model. Only the states
-- at the leafs are retained. The states at internal nodes are removed. This has
-- a lower memory footprint.
--
-- XXX: Improve performance. Use vectors, not lists.
simulateAndFlatten
  :: (PrimMonad m, Measurable a)
  => Int
  -> StationaryDistribution
  -> ExchangeabilityMatrix
  -> Tree a
  -> Gen (PrimState m)
  -> m [[State]]
simulateAndFlatten n d e t g = do
  let q  = fromExchangeabilityMatrix e d
      pt = toProbTree q t
  is <- getRootStates n d g
  simulateAndFlatten' is pt g

-- This is the heart of the simulation. Take a tree and a list of root states.
-- Recursively jump down the branches to the leafs. Forget states at internal.
simulateAndFlatten'
  :: (PrimMonad m)
  => [State]
  -> Tree ProbMatrix
  -> Gen (PrimState m)
  -> m [[State]]
simulateAndFlatten' is (Node p f) g = do
  is' <- mapM (\i -> jump i p g) is
  if null f
    then return [is']
    else concat <$> sequence [ simulateAndFlatten' is' t g | t <- f ]

-- | Simulate a number of sites for a given substitution model. Keep states at
-- internal nodes. The result is a tree with the list of simulated states as
-- node labels.
simulate
  :: (PrimMonad m, Measurable a)
  => Int
  -> StationaryDistribution
  -> ExchangeabilityMatrix
  -> Tree a
  -> Gen (PrimState m)
  -> m (Tree [State])
simulate n d e t g = do
  let q  = fromExchangeabilityMatrix e d
      pt = toProbTree q t
  is <- getRootStates n d g
  simulate' is pt g

-- This is the heart of the simulation. Take a tree and a list of root states.
-- Recursively jump down the branches to the leafs.
simulate'
  :: (PrimMonad m)
  => [State]
  -> Tree ProbMatrix
  -> Gen (PrimState m)
  -> m (Tree [State])
simulate' is (Node p f) g = do
  is' <- mapM (\i -> jump i p g) is
  f'  <- sequence [ simulate' is' t g | t <- f ]
  return $ Node is' f'

toProbTreeMixtureModel
  :: (Measurable a) => [RateMatrix] -> Tree a -> Tree [ProbMatrix]
toProbTreeMixtureModel qs =
  fmap (\a -> [ probMatrix q . getLen $ a | q <- qs ] `using` parList rpar)

getComponentsAndRootStates
  :: PrimMonad m
  => Int
  -> Vector R
  -> [StationaryDistribution]
  -> Gen (PrimState m)
  -> m ([Int], [State])
getComponentsAndRootStates n ws ds g = do
  cs <- replicateM n $ categorical ws g
  is <- sequence [ categorical (ds !! c) g | c <- cs ]
  return (cs, is)

-- | Simulate a number of sites for a given set of substitution models with
-- corresponding weights. Forget states at internal nodes. See also
-- 'simulateAndFlatten'.
simulateAndFlattenMixtureModel
  :: (PrimMonad m, Measurable a)
  => Int
  -> Vector R
  -> [StationaryDistribution]
  -> [ExchangeabilityMatrix]
  -> Tree a
  -> Gen (PrimState m)
  -> m [[State]]
simulateAndFlattenMixtureModel n ws ds es t g = do
  let qs = zipWith fromExchangeabilityMatrix es ds
      pt = toProbTreeMixtureModel qs t
  (cs, is) <- getComponentsAndRootStates n ws ds g
  simulateAndFlattenMixtureModel' is cs pt g

simulateAndFlattenMixtureModel'
  :: (PrimMonad m)
  => [State]
  -> [Int]
  -> Tree [ProbMatrix]
  -> Gen (PrimState m)
  -> m [[State]]
simulateAndFlattenMixtureModel' is cs (Node ps f) g = do
  is' <- sequence [ jump i (ps !! c) g | (i, c) <- zip is cs ]
  if null f
    then return [is']
    else concat
      <$> sequence [ simulateAndFlattenMixtureModel' is' cs t g | t <- f ]

-- | See 'simulateAndFlattenMixtureModel', parallel version;
-- needs to be run in IO monad.
simulateAndFlattenMixtureModelPar
  :: Measurable a
  => Int
  -> Vector R
  -> [StationaryDistribution]
  -> [ExchangeabilityMatrix]
  -> Tree a
  -> GenIO
  -> IO [[[State]]]
simulateAndFlattenMixtureModelPar n ws ds es t g = do
  let qs = zipWith fromExchangeabilityMatrix es ds
      pt = toProbTreeMixtureModel qs t
  parComp
    n
    (\n' g' -> do
      (cs, is) <- getComponentsAndRootStates n' ws ds g'
      simulateAndFlattenMixtureModel' is cs pt g'
    )
    g

-- | Simulate a number of sites for a given set of substitution models with
-- corresponding weights. Keep states at internal nodes. See also
-- 'simulate'.
simulateMixtureModel
  :: (PrimMonad m, Measurable a)
  => Int
  -> Vector R
  -> [StationaryDistribution]
  -> [ExchangeabilityMatrix]
  -> Tree a
  -> Gen (PrimState m)
  -> m (Tree [State])
simulateMixtureModel n ws ds es t g = do
  let qs = zipWith fromExchangeabilityMatrix es ds
      pt = toProbTreeMixtureModel qs t
  (cs, is) <- getComponentsAndRootStates n ws ds g
  simulateMixtureModel' is cs pt g

-- See 'simulateAlongProbTree', only we have a number of mixture components. The
-- starting states and the components for each site have to be provided.
simulateMixtureModel'
  :: (PrimMonad m)
  => [State]
  -> [Int]
  -> Tree [ProbMatrix]
  -> Gen (PrimState m)
  -> m (Tree [State])
simulateMixtureModel' is cs (Node ps f) g = do
  is' <- sequence [ jump i (ps !! c) g | (i, c) <- zip is cs ]
  f'  <- sequence [ simulateMixtureModel' is' cs t g | t <- f ]
  return $ Node is' f'