epi-sim-0.7.0: src/Epidemic/Utility.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Epidemic.Utility ( initialIdentifier
, inhomExponential
, randomPerson
, maybeToRight
, newPerson
, isReconTreeLeaf
, simulationWithSystem
, simulationWithFixedSeed
, simulationWithGenIO
) where
import Control.Monad.Primitive (PrimMonad, PrimState)
import qualified Data.List as List
import qualified Data.Maybe as Maybe
import qualified Data.Vector as V
import Epidemic
import Epidemic.Types.Events
import Epidemic.Types.Parameter
import Epidemic.Types.Population
import Epidemic.Types.Simulation
import Epidemic.Types.Time (AbsoluteTime (..),
TimeDelta (..), Timed (..),
cadlagValue, nextTime,
timeAfterDelta)
import System.Random.MWC
import System.Random.MWC.Distributions (exponential)
initialIdentifier :: Identifier
initialIdentifier = Identifier 1
-- | A new person constructed from the given identifier and a new identifier.
newPerson :: Identifier -> (Person, Identifier)
newPerson idntty@(Identifier idInt) = (Person idntty, Identifier (idInt + 1))
-- | An element of a vector and the vector with that element removed.
selectElem :: V.Vector a -> Int -> (a, V.Vector a)
selectElem v n
| n == 0 = (V.head v, V.tail v)
| otherwise =
let (foo, bar) = V.splitAt n v
in (V.head bar, foo V.++ (V.tail bar))
-- | A random person and the remaining group of people after they have been
-- sampled with removal.
randomPerson :: People -> GenIO -> IO (Person, People)
randomPerson people@(People persons) gen = do
u <- uniform gen
let personIx = floor (u * (fromIntegral $ numPeople people :: Double))
(person, remPeople) = selectElem persons personIx
in return (person, People remPeople)
type NName = Maybe String
type NLength = Maybe Double
data NBranch =
NBranch NSubtree NLength
deriving (Eq)
instance Show NBranch where
show (NBranch st (Just l)) = show st ++ ":" ++ show l
show (NBranch st Nothing) = show st
data NBranchSet =
NBranchSet [NBranch]
deriving (Eq)
instance Show NBranchSet where
show (NBranchSet bs) = "(" ++ (List.intercalate "," (map show bs)) ++ ")"
data NSubtree
= NLeaf NName
| NInternal NBranchSet
deriving (Eq)
instance Show NSubtree where
show (NLeaf (Just n)) = n
show (NLeaf Nothing) = ""
show (NInternal bs) = show bs
data NTree =
NTree [NBranch]
deriving (Eq)
instance Show NTree where
show (NTree bs) = show (NBranchSet bs) ++ ";"
-- | The number of elements of the list that map to @True@ under the predicate.
count' :: (a -> Bool) -> [a] -> Int
count' p xs = sum [if p x then 1 else 0 | x <- xs]
-- | Run a simulation described by a configuration object and the model's
-- @allEvents@ style function (see the example in
-- "Epidemic.Model.InhomogeneousBDSCOD") using the provided PRNG.
simulationWithGenIO ::
(ModelParameters a b, Population b)
=> SimulationConfiguration a b c
-> (a -> AbsoluteTime -> Maybe (TerminationHandler b c) -> SimulationState b c -> GenIO -> IO (SimulationState b c))
-> GenIO
-> IO (Either (Maybe c) [EpidemicEvent])
simulationWithGenIO config@SimulationConfiguration {..} allEventsFunc gen =
if scRequireCherry
then
simulationAtLeastCherry config allEventsFunc gen
else do
simState <-
allEventsFunc
scRates
(timeAfterDelta scStartTime scSimDuration)
scTerminationHandler
(SimulationState (scStartTime, [], scPopulation, scNewIdentifier))
gen
return $ case simState of
SimulationState (_, events, _, _) -> Right $ List.sort events
TerminatedSimulation maybeSummary -> Left maybeSummary
-- | Run a simulation using a fixed PRNG random seed.
simulationWithFixedSeed ::
(ModelParameters a b, Population b)
=> SimulationConfiguration a b c
-> (a -> AbsoluteTime -> Maybe (TerminationHandler b c) -> SimulationState b c -> GenIO -> IO (SimulationState b c))
-> IO (Either (Maybe c) [EpidemicEvent])
simulationWithFixedSeed config allEventsFunc = do
gen <- genIOFromFixed
simulationWithGenIO config allEventsFunc gen
-- | Simulation conditioned upon there being at least two sequenced samples.
simulationAtLeastCherry ::
(ModelParameters a b, Population b)
=> SimulationConfiguration a b c
-> (a -> AbsoluteTime -> Maybe (TerminationHandler b c) -> SimulationState b c -> GenIO -> IO (SimulationState b c))
-> GenIO
-> IO (Either (Maybe c) [EpidemicEvent])
simulationAtLeastCherry config@SimulationConfiguration {..} allEventsFunc gen = do
simState <-
allEventsFunc
scRates
(timeAfterDelta scStartTime scSimDuration)
scTerminationHandler
(SimulationState (scStartTime, [], scPopulation, scNewIdentifier))
gen
case simState of
SimulationState (_, events, _, _) ->
if count' isReconTreeLeaf events >= 2
then return $ Right $ List.sort events
else simulationAtLeastCherry config allEventsFunc gen
TerminatedSimulation maybeSummary -> return $ Left maybeSummary
-- | Run a simulation described by a configuration object but using a random
-- seed generated by the system rather than a seed
simulationWithSystem ::
(ModelParameters a b, Population b)
=> SimulationConfiguration a b c
-> (a -> AbsoluteTime -> Maybe (TerminationHandler b c) -> SimulationState b c -> GenIO -> IO (SimulationState b c))
-> IO (Either (Maybe c) [EpidemicEvent])
simulationWithSystem config@SimulationConfiguration {..} allEventsFunc = do
simState <-
withSystemRandom $ \g ->
allEventsFunc
scRates
(timeAfterDelta scStartTime scSimDuration)
scTerminationHandler
(SimulationState (scStartTime, [], scPopulation, scNewIdentifier))
g
case simState of
SimulationState (_, events, _, _) ->
if scRequireCherry
then (if count' isReconTreeLeaf events >= 2
then return $ Right $ List.sort events
else simulationWithSystem config allEventsFunc)
else return $ Right $ List.sort events
TerminatedSimulation maybeSummary -> return $ Left maybeSummary
-- | Predicate for whether an epidemic event will appear as a leaf in the
-- reconstructed tree. For scheduled sequenced samples this will only return
-- true if there was at least one lineage observed.
isReconTreeLeaf :: EpidemicEvent -> Bool
isReconTreeLeaf e =
case e of
IndividualSample {..} -> indSampSeq
PopulationSample {..} -> popSampSeq && not (nullPeople popSampPeople)
_ -> False
-- | The number of lineages at the end of a simulation.
finalSize ::
[EpidemicEvent] -- ^ The events from the simulation
-> Integer
finalSize = foldl (\x y -> x + eventPopDelta y) 1
-- | Generate exponentially distributed random variates with inhomogeneous rate
-- starting from a particular point in time.
--
-- Assuming the @stepFunc@ is the intensity of arrivals and @t0@ is the start
-- time this returns @t1@ the time of the next arrival.
inhomExponential ::
PrimMonad m
=> Timed Double -- ^ Step function
-> AbsoluteTime -- ^ Start time
-> Gen (PrimState m) -- ^ Generator
-> m (Maybe AbsoluteTime)
inhomExponential stepFunc t0 = randInhomExp t0 stepFunc
-- | Generate exponentially distributed random variates with inhomogeneous rate.
--
-- __TODO__ The algorithm used here generates more variates than are needed. It
-- would be nice to use a more efficient implementation.
--
randInhomExp ::
PrimMonad m
=> AbsoluteTime -- ^ Timer
-> Timed Double -- ^ Step function
-> Gen (PrimState m) -- ^ Generator.
-> m (Maybe AbsoluteTime)
randInhomExp crrT stepFunc gen =
let crrR = cadlagValue stepFunc crrT
nxtT = nextTime stepFunc crrT
in if Maybe.isJust crrR && Maybe.isJust nxtT
then do
crrD <- exponential (Maybe.fromJust crrR) gen
let propT = timeAfterDelta crrT (TimeDelta crrD)
if propT < Maybe.fromJust nxtT
then return $ Just propT
else randInhomExp (Maybe.fromJust nxtT) stepFunc gen
else return Nothing
-- | Helper function for converting between the Maybe monad and the Either
-- monad.
maybeToRight :: a -> Maybe b -> Either a b
maybeToRight a maybeB =
case maybeB of
(Just b) -> Right b
Nothing -> Left a