epi-sim-0.7.0: src/Epidemic/Model/LogisticBDSD.hs
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RecordWildCards #-}
module Epidemic.Model.LogisticBDSD
( configuration
, randomEvent
, LogisticBDSDParameters(..)
, LogisticBDSDPopulation(..)
) where
import Data.Maybe (fromJust)
import qualified Data.Vector as V
import qualified Data.Vector.Generic as G
import Epidemic (firstScheduled, noScheduledEvent)
import Epidemic.Types.Events (EpidemicEvent(..))
import Epidemic.Types.Time
( AbsoluteTime(..)
, Timed(..)
, TimeDelta(..)
, asTimed
, timeAfterDelta
)
import Epidemic.Types.Parameter
( ModelParameters(..)
, Probability
, Rate
)
import Epidemic.Types.Population
( Identifier(..)
, People(..)
, Population(..)
, addPerson
, nullPeople
, numPeople
)
import Epidemic.Types.Simulation
( SimulationConfiguration(..)
, SimulationRandEvent(..), TerminationHandler(..)
)
import Epidemic.Utility
( initialIdentifier
, maybeToRight
, newPerson
, randomPerson
)
import System.Random.MWC (GenIO)
import System.Random.MWC.Distributions (bernoulli, categorical, exponential)
-- | The parameters of the logistic-BDSD process. This process allows for
-- infections, removals, sampling and disasters.
data LogisticBDSDParameters =
LogisticBDSDParameters
{ paramsBirthRate :: Rate
, paramsCapacity :: Int
, paramsDeathRate :: Rate
, paramsSamplingRate :: Rate
, paramsDisasters :: Timed Probability
}
deriving (Show)
newtype LogisticBDSDPopulation =
LogisticBDSDPopulation People
deriving (Show)
-- | The per lineage birth rate accounting for the population size.
logisticBirthRate :: LogisticBDSDParameters -> LogisticBDSDPopulation -> Rate
logisticBirthRate LogisticBDSDParameters {..} (LogisticBDSDPopulation pop) =
let propCapacity = fromIntegral (numPeople pop) / fromIntegral paramsCapacity
in paramsBirthRate * (1.0 - propCapacity)
instance ModelParameters LogisticBDSDParameters LogisticBDSDPopulation where
rNaught _ _ _ = Nothing
eventRate (LogisticBDSDPopulation pop) LogisticBDSDParameters {..} _ =
let propCapcity = fromIntegral (numPeople pop) / fromIntegral paramsCapacity
br = paramsBirthRate * (1.0 - propCapcity)
in Just $ br + paramsDeathRate + paramsSamplingRate
birthProb lpop lparam absTime = do
er <- eventRate lpop lparam absTime
Just $ br / er
where
br = logisticBirthRate lparam lpop
eventWeights currPop params@LogisticBDSDParameters {..} _ =
let logisticBR = logisticBirthRate params currPop
in Just $ V.fromList [logisticBR, paramsDeathRate, paramsSamplingRate]
instance Population LogisticBDSDPopulation where
susceptiblePeople _ = Nothing
infectiousPeople (LogisticBDSDPopulation people) = Just people
removedPeople _ = Nothing
isInfected (LogisticBDSDPopulation people) = not $ nullPeople people
-- | Create an simulation configuration or return an error message if this is
-- not possible.
configuration ::
TimeDelta
-> Bool -- ^ condition upon at least two sequenced samples.
-> Maybe (LogisticBDSDPopulation -> Bool, [EpidemicEvent] -> s) -- ^ values for termination handling.
-> (Rate, Int, Rate, Rate, [(AbsoluteTime, Probability)])
-> Either String (SimulationConfiguration LogisticBDSDParameters LogisticBDSDPopulation s)
configuration simDuration atLeastCherry maybeTHFuncs (birthRate, capacity, deathRate, samplingRate, disasterSpec)
| minimum [birthRate, deathRate, samplingRate] < 0 =
Left "negative rate provided"
| capacity < 1 = Left "insufficient population capacity"
| otherwise = do
disasterTP <-
maybeToRight
"could not construct timed probability"
(asTimed disasterSpec)
let logBDSDParams =
LogisticBDSDParameters
birthRate
capacity
deathRate
samplingRate
disasterTP
(seedPerson, newId) = newPerson initialIdentifier
logBDSDPop = LogisticBDSDPopulation (People $ V.singleton seedPerson)
termHandler = do (f1, f2) <- maybeTHFuncs
return $ TerminationHandler f1 f2
in return $
SimulationConfiguration
logBDSDParams
logBDSDPop
newId
(AbsoluteTime 0)
simDuration
termHandler
atLeastCherry
-- | Defines how a single random event is simulated in this model.
randomEvent :: SimulationRandEvent LogisticBDSDParameters LogisticBDSDPopulation
randomEvent = SimulationRandEvent randEvent'
randEvent' ::
LogisticBDSDParameters
-> AbsoluteTime
-> LogisticBDSDPopulation
-> Identifier
-> GenIO
-> IO (AbsoluteTime, EpidemicEvent, LogisticBDSDPopulation, Identifier)
randEvent' params@LogisticBDSDParameters {..} currTime currPop@(LogisticBDSDPopulation currPpl) currId gen =
let netEventRate = (fromJust $ eventRate currPop params currTime)
popSizeDouble = fromIntegral $ numPeople currPpl
(Just weightsVec) = eventWeights currPop params currTime
in do delay <- exponential (netEventRate * popSizeDouble) gen
let newEventTime = timeAfterDelta currTime (TimeDelta delay)
if noScheduledEvent currTime newEventTime paramsDisasters
then do
eventIx <- categorical weightsVec gen
(randPerson, otherPeople) <- randomPerson currPpl gen
return $
case eventIx of
0 ->
let (infectedPerson, newId) = newPerson currId
infEvent =
Infection newEventTime randPerson infectedPerson
newPop =
LogisticBDSDPopulation
(addPerson infectedPerson currPpl)
in (newEventTime, infEvent, newPop, newId)
1 ->
( newEventTime
, Removal newEventTime randPerson
, LogisticBDSDPopulation otherPeople
, currId)
2 ->
( newEventTime
, IndividualSample newEventTime randPerson True
, LogisticBDSDPopulation otherPeople
, currId)
_ -> error "do not recognise the type of event index."
else let (Just dsstr@(dsstrTime, _)) =
firstScheduled currTime paramsDisasters
in do (schdEvent, postEventPpl) <-
randomDisasterEvent dsstr currPop gen
return (dsstrTime, schdEvent, postEventPpl, currId)
-- | Return a randomly sampled Disaster event
-- TODO Move this into the epidemic module to keep things DRY.
randomDisasterEvent ::
(AbsoluteTime, Probability) -- ^ Time and probability of sampling in the disaster
-> LogisticBDSDPopulation -- ^ The state of the population prior to the disaster
-> GenIO
-> IO (EpidemicEvent, LogisticBDSDPopulation)
randomDisasterEvent (dsstrTime, dsstrProb) (LogisticBDSDPopulation (People currPpl)) gen = do
randBernoullis <- G.replicateM (V.length currPpl) (bernoulli dsstrProb gen)
let filterZip predicate a b = fst . V.unzip . V.filter predicate $ V.zip a b
sampledPeople = filterZip snd currPpl randBernoullis
unsampledPeople = filterZip (not . snd) currPpl randBernoullis
in return
( PopulationSample dsstrTime (People sampledPeople) False
, LogisticBDSDPopulation (People unsampledPeople))